Как я могу получить XML-тэги, используя регулярное выражение для его содержимого, не зная его имени - PullRequest
3 голосов
/ 10 октября 2011

У меня есть 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.

Как я могу получить все эти теги, не зная его имени по регулярному выражению?

Ответы [ 2 ]

5 голосов
/ 10 октября 2011

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. Сопоставление списка с родительскими элементами каждого текстового узла.

Метод Enumerable#grep является сокращением для .select{ |text| regex === text }.

В качестве альтернативы , обратите внимание, что вы может определять ваши собственные пользовательские функции XPath в Nokogiri , которые обращаются к Ruby, так что вы можете притворяться, что используете XPath 2.0 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)

Вы можете ускорить его, кэшируяРегулярное выражение:

module FindWithRegex
  REs = {}
  def self.matches(nodes,pattern,flags=nil)
    nodes.grep(REs[pattern] ||= Regexp.new(pattern,flags))
  end
end

#=>                      user     system      total        real
#=> grep and map     0.437000   0.016000   0.453000 (  0.442044)
#=> cached regex     0.905000   0.000000   0.905000 (  0.896090)
2 голосов
/ 11 октября 2011

Вот чистое решение 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>
...