XPath轴,获取所有后续节点,直到

时间:2011-01-22 11:03:45

标签: ruby xpath nokogiri

我有以下HTML示例:

<!-- lots of html -->
<h2>Foo bar</h2>
<p>lorem</p>
<p>ipsum</p>
<p>etc</p>

<h2>Bar baz</h2>
<p>dum dum dum</p>
<p>poopfiddles</p>
<!-- lots more html ... -->

我想要提取'Foo bar'标题后面的所有段落,直到我到达'Bar baz'标题('bar baz'标题的文字未知,所以很遗憾我无法使用答案由bougyman提供)。现在我可以使用类似//h2[text()='Foo bar']/following::p的内容,但当然会在此标题后面抓取所有段落。因此,我可以选择遍历节点集并将段落推送到数组,直到文本与下一个标题的文本匹配为止,但说实话,这绝不会像在XPath中那样酷。

有没有办法做到这一点,我错过了?

7 个答案:

答案 0 :(得分:18)

使用

(//h2[. = 'Foo bar'])[1]/following-sibling::p
   [1 = count(preceding-sibling::h2[1] | (//h2[. = 'Foo bar'])[1])]

如果保证每个h2都有不同的值,可以简化为:

//h2[. = 'Foo bar']/following-sibling::p
   [1 = count(preceding-sibling::h2[1] | ../h2[. = 'Foo bar'])]

这意味着:选择所有p个元素,这些元素遵循其字符串值为h2的{​​{1}}(文档中的第一个或仅一个)的兄弟姐妹并且所有这些'Foo bar'元素的前一个兄弟h2正好是h2 p'Foo bar'。

这里我们使用一种方法来查找两个节点是否相同

(first or only one in the document) whose string value is
当节点count($n1 | $n2) = 1 true()是同一节点时,

恰好是$n1

此表达式可以推广

$n2

选择$ x 指定的任何节点的所有“紧随其后的兄弟姐妹”。

答案 1 :(得分:3)

这个 XPATH 1.0 语句选择<p>所有<h2>个兄弟姐妹,这些兄弟姐妹跟随<h2>的字符串值等于“Foo bar”,后面跟着第一个兄弟<h2>之前的//p[preceding-sibling::h2[.='Foo bar']] [following-sibling::h2[ preceding-sibling::h2[1][.='Foo bar']]] 兄弟元素的字符串值为“Foo bar”。

{{1}}

答案 2 :(得分:3)

在XPath 2.0中(我知道这对你没有帮助......)最简单的解决方案可能是

  

H 2。 ='Foo   bar'] / follow-sibling :: *除外    H2 [。 ='酒吧   baz'] /(。| follow-sibling :: *)

但是和其他解决方案一样,这可能(在没有识别模式的优化器的情况下)在第二个h2之外的元素数量上是线性的,而你真的想要一个性能仅取决于选择的元素数量。我一直觉得有一个直到操作员会很好:

h2[. = 'Foo bar']/(following-sibling::* until . = 'Bar baz')

如果没有使用递归的XSLT或XQuery解决方案,当要选择的节点数量与后续兄弟节点数量相比较小时,可能会表现得更好。

答案 3 :(得分:3)

仅仅因为它不在答案之间,经典的XPath 1.0设置排除:

A - B = $A[count(.|$B)!=count($B)]

对于这种情况:

(//h2[.='Foo bar']
    /following-sibling::p)
       [count(.|../h2[.='Foo bar']
                     /following-sibling::h2[1]
                        /following-sibling::p)
        != count(../h2[.='Foo bar']
                     /following-sibling::h2[1]
                        /following-sibling::p)]

注意:这将是对Kaysian方法的否定。

答案 4 :(得分:2)

如果<<位于$node1 << $node2之前,则XPath 2.0具有运算符$node1$node2为真),这样您就可以使用//h2[. = 'Foo bar']/following-sibling::p[. << //h2[. = 'Bar baz']]。然而,我不知道nokogiri是否支持XPath 2.0。

答案 5 :(得分:2)

第二个匹配怎么样?如果您只想要顶部,请匹配第二部分并抓住它上面的所有内容 。
doc.xpath("//h2[text()='Bar baz']/preceding-sibling::p").map { |m| m.text } =&GT; [“lorem”,“ipsum”,“etc”]

或者如果您不知道第二个,请使用另一个级别: doc.xpath("//h2[text()='Foo bar']/following-sibling::h2/preceding-sibling::p").map { |it| it.text } =&GT; [“lorem”,“ipsum”,“etc”]

答案 6 :(得分:2)

require 'nokogiri'

doc = Nokogiri::XML <<ENDXML
<root>
  <h2>Foo</h2>
  <p>lorem</p>
  <p>ipsum</p>
  <p>etc</p>

  <h2>Bar</h2>
  <p>dum dum dum</p>
  <p>poopfiddles</p>
</root>
ENDXML

a = doc.xpath( '//h2[text()="Foo"]/following::p[not(preceding::h2[text()="Bar"])]' )
puts a.map{ |n| n.to_s }
#=> <p>lorem</p>
#=> <p>ipsum</p>
#=> <p>etc</p>

我怀疑使用next_sibling走DOM可能会更有效率,直到结束:

node = doc.at_xpath('//h2[text()="Foo bar"]').next_sibling
stop = doc.at_xpath('//h2[text()="Bar baz"]')
a = []
while node && node!=stop
  a << node unless node.type == 3 # skip text nodes
  node = node.next_sibling
end

puts a.map{ |n| n.to_s }
#=> <p>lorem</p>
#=> <p>ipsum</p>
#=> <p>etc</p>

但是,更快。在一些简单的测试中,我发现xpath-only(第一个解决方案)的速度是这个循环测试的2倍,即使在stop节点之后有很多段落也是如此。当有许多节点需要捕获时(停止后很少)它在6x-10x范围内表现更好。