我有以下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中那样酷。
有没有办法做到这一点,我错过了?
答案 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范围内表现更好。