如何在不知道其名称的情况下使用正则表达式获取XML子标记的内容

时间:2011-10-10 14:45:55

标签: ruby xml regex xpath nokogiri

我有简化时看起来像这样的XML:

node_set = Nokogiri::XML('
<PARENT>
   <SOME_TAG>12:12:1222</SOME_TAG>
   <HOLY_TAG>12:12:1222</HOLY_TAG>
   <MAJOR_TAG>12:12:1222</MAJOR_TAG>
   <FOO_FOO>12:12:1222</FOO_FOO>
</PARENT>'
)

我所知道的只是如何为此写一个正则表达式:

(\d+):(\d+):(\d+)

我在官方网站上阅读了一些关于正则表达式匹配的文章,但没有答案如何做到这一点。只有如何将用户函数调用到xpath方法的机制。

如何在不知道regexp名称的情况下获取所有这些标签?

2 个答案:

答案 0 :(得分:5)

Nokogiri不支持XPath 2.0 matches函数,因此您需要使用Ruby来执行正则表达式:

hits = node_set.xpath("//text()").grep(/\d+:\d+:\d+/).map(&:parent)
p hits.map(&:name)
#=> ["SOME_TAG", "HOLY_TAG", "MAJOR_TAG", "FOO_FOO"]

描述:

  1. 查找整个文档中的所有文本节点。
  2. 将列表缩小为仅匹配所需正则表达式的列表。
  3. 将列表映射到每个文本节点的父元素。
  4. Enumerable#grep方法是.select{ |text| regex === text }的简写。

    或者,请注意您可以定义自己调用Ruby的custom XPath functions in Nokogiri,这样您就可以假装使用XPath 2.0 {{1} }:

    matches

    但是,由于每个找到的节点都重新调用它(因此每次都从字符串重新创建一个新的正则表达式),因此效率不高:

    module FindWithRegex
      def self.matches(nodes,pattern,flags=nil)
        nodes.grep(Regexp.new(pattern,flags))
      end
    end
    
    hits = node_set.xpath('//*[matches(text(),"\d+:\d+:\d+")]',FindWithRegex)
    p hits.map(&:name)
    #=> ["SOME_TAG", "HOLY_TAG", "MAJOR_TAG", "FOO_FOO"]
    

    您可以通过缓存正则表达式加快速度:

    require 'benchmark'
    Benchmark.bm(15) do |x|
      N = 10000
      x.report('grep and map'){ N.times{
        node_set.xpath("//text()").grep(/\d+:\d+:\d+/).map(&:parent)
      }}
      x.report('custom function'){ N.times{
        node_set.xpath('//*[matches(text(),"\d+:\d+:\d+")]',FindWithRegex)
      }}
    end
    
    #=>                      user     system      total        real
    #=> grep and map     0.437000   0.016000   0.453000 (  0.442044)
    #=> custom function  1.653000   0.031000   1.684000 (  1.694170)
    

答案 1 :(得分:2)

这是一个纯XPath 1.0解决方案。虽然XPath 1.0中没有本机RegEx工具,但仍然可以使用标准XPath 1.0函数substring-before()substring-after()translate()来实现:

/*/*[not(translate(substring-before(.,':'),
                   '0123456789',
                    ''
                    )
         )
   and
     not(translate
           (substring-before(substring-after(.,':'),
                             ':'
                             ),
           '0123456789',
           ''
           )
          )
   and
     not(translate
           (substring-after(substring-after(.,':'),
                             ':'
                             ),
           '0123456789',
           ''
           )
          )
    ]

基于XSLT的验证

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="node()|@*">
     <xsl:copy-of select=
     "    /*/*[not(translate(substring-before(.,':'),
                       '0123456789',
                        ''
                        )
             )
       and
         not(translate
               (substring-before(substring-after(.,':'),
                                 ':'
                                 ),
               '0123456789',
               ''
               )
              )
       and
         not(translate
               (substring-after(substring-after(.,':'),
                                 ':'
                                 ),
               '0123456789',
               ''
               )
              )
        ]
"/>
 </xsl:template>

</xsl:stylesheet>

此XSLT转换只是使用上面的表达式选择并输出所选节点。当应用于此XML文档(提供的文档添加了“无效”元素)时:

<PARENT>
   <SOME_TAG>12:12:1222</SOME_TAG>
   <SOME_TAG2>12a:12:1222</SOME_TAG2>
   <HOLY_TAG>12:12:1222</HOLY_TAG>
   <HOLY_TAG2>12:12b:1222</HOLY_TAG2>
   <MAJOR_TAG>12:12:1222</MAJOR_TAG>
   <MAJOR_TAG2>12:12:1222c</MAJOR_TAG2>
   <FOO_FOO>12:12:1222</FOO_FOO>
</PARENT>

输出所需的,正确选择的节点

<SOME_TAG>12:12:1222</SOME_TAG>
<HOLY_TAG>12:12:1222</HOLY_TAG>
<MAJOR_TAG>12:12:1222</MAJOR_TAG>
<FOO_FOO>12:12:1222</FOO_FOO>