Как получить абсолютный путь к узлу в XML с использованием XPath и Ruby? - PullRequest
1 голос
/ 30 декабря 2010

В основном я хочу извлечь абсолютный путь от узла к корню и сообщить его на консоль или в файл.Ниже приведено текущее решение:

require "rexml/document"

include REXML

def get_path(xml_doc, key)
  XPath.each(xml_doc, key) do |node|
    puts "\"#{node}\""
    XPath.each(node, '(ancestor::#node)') do |el|
      #  puts  el
    end
  end
end

test_doc = Document.new <<EOF
  <root>
   <level1 key="1" value="B">
     <level2 key="12" value="B" />
     <level2 key="13" value="B" />
   </level1>
  </root>
EOF

get_path test_doc, "//*/[@key='12']"

Проблема в том, что он выдает мне "<level2 value='B' key='12'/>" в качестве вывода.Желаемый результат - <root><level1><level2 value='B' key='12'/> (формат может быть другим, главная цель - указать полный путь).Я обладаю только базовыми знаниями XPath и буду признателен за любую помощь / руководство, где искать и как этого достичь.

Ответы [ 3 ]

4 голосов
/ 30 декабря 2010

Это должно помочь вам начать:

require 'nokogiri'

test_doc = Nokogiri::XML <<EOF
  <root>
   <level1 key="1" value="B">
     <level2 key="12" value="B" />
     <level2 key="13" value="B" />
   </level1>
  </root>
EOF

node = test_doc.at('//level2')
puts [*node.ancestors.reverse, node][1..-1].map{ |n| "<#{ n.name }>" }
# >> <root>
# >> <level1>
# >> <level2>

Nokogiri действительно хорош, потому что он позволяет вам использовать средства доступа CSS вместо XPath, если вы выберете. CSS более понятен для некоторых людей и может быть чище, чем эквивалентный XPath:

node = test_doc.at('level2')
puts [*node.ancestors.reverse, node][1..-1].map{ |n| "<#{ n.name }>" }
# >> <root>
# >> <level1>
# >> <level2>
3 голосов
/ 30 декабря 2010

Во-первых, обратите внимание, что ваш документ, я думаю, не тот, который вы намеревались.Я подозреваю, что вы не хотели, чтобы <level1> был самозакрывающимся, но содержал элементы <level2> в качестве детей.

Во-вторых, я предпочитаю и защищаю Nokogiri вместо REXML.Приятно, что REXML поставляется с Ruby, но Nokogiri работает быстрее и удобнее, ИМХО.Итак:

require 'nokogiri'

test_doc = Nokogiri::XML <<EOF
  <root>
    <level1 key="1" value="B">
      <level2 key="12" value="B" />
      <level2 key="13" value="B" />
    </level1>
  </root>
EOF

def get_path(xml_doc, key)
  xml_doc.at_xpath(key).ancestors.reverse
end

path = get_path( test_doc, "//*[@key='12']" )
p path.map{ |node| node.name }.join( '/' )
#=> "document/root/level1"
2 голосов
/ 30 декабря 2010

Если вы настроили REXML, вот решение REXML:

require 'rexml/document'

test_doc = REXML::Document.new <<EOF
  <root>
    <level1 key="1" value="B">
      <level2 key="12" value="B" />
      <level2 key="13" value="B" />
    </level1>
  </root>
EOF

def get_path(xml_doc, key)
  node = REXML::XPath.first( xml_doc, key )
  path = []
  while node.parent
    path << node
    node = node.parent
  end
  path.reverse
end

path = get_path( test_doc, "//*[@key='12']" )
p path.map{ |el| el.name }.join("/")
#=> "root/level1/level2"

Или, если вы хотите использовать ту же реализацию get_path из другого ответа, вы можете добавить в REXML monkeypatch для добавленияancestors метод:

class REXML::Child
  def ancestors
    ancestors = []

    # Presumably you don't want the node included in its list of ancestors
    # If you do, change the following line to    node = self
    node = self.parent

    # Presumably you want to stop at the root node, and not its owning document
    # If you want the document included in the ancestors, change the following
    # line to just    while node
    while node.parent
      ancestors << node
      node = node.parent
    end

    ancestors.reverse
  end
end
...