【问题标题】:How to get the absolute node path in XML using XPath and Ruby?如何使用 XPath 和 Ruby 获取 XML 中的绝对节点路径?
【发布时间】:2010-12-30 00:39:18
【问题描述】:

基本上我想提取从节点到根的绝对路径并将其报告给控制台或文件。以下是目前的解决方案:

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']"

问题是它给了我"&lt;level2 value='B' key='12'/&gt;" 作为输出。期望的输出是&lt;root&gt;&lt;level1&gt;&lt;level2 value='B' key='12'/&gt;(格式可能不同,主要目标是有一个完整的路径)。我只有 XPath 的基本知识,如果能提供任何帮助/指导,以及如何实现这一点,我将不胜感激。

【问题讨论】:

  • 我想知道是否有任何使用 REXML 的解决方案。 Nokogiri 安装非常复杂,我没有对服务器框的 root 访问权限,脚本将运行。
  • Nokogiri 安装一点也不复杂。它需要 libxml,但许多 XML 解析器也需要它,所以它已经在很多系统上。试试locate libxml2 | grep /lib/看看;它应该出现在“/usr/lib/libxml2”之类的地方。如果您无权以 root 身份安装,您可以安装到备用目录,包括您自己的目录。我们很多人使用并推荐RVM 来管理本地 (~/.rvm) Ruby 安装。
  • 感谢您的帮助。不幸的是,我必须使用 REXML(现在在多个开发盒上更新这个旧的服务器盒和 cygwin 安装非常昂贵)。从长远来看,我肯定会改用 Nokogiri(主要是因为性能)。

标签: ruby xml rexml


【解决方案1】:

这应该让你开始:

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>

【讨论】:

    【解决方案2】:

    首先,请注意,我认为您的文档不是您想要的。我怀疑您不希望 &lt;level1&gt; 自动关闭,而是包含 &lt;level2&gt; 元素作为子元素。

    其次,我更喜欢并提倡 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"
    

    【讨论】:

      【解决方案3】:

      如果您选择使用 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 添加 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
      

      【讨论】:

        猜你喜欢
        • 2016-10-10
        • 1970-01-01
        • 2020-12-15
        • 1970-01-01
        • 2011-07-19
        • 2015-10-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多