XPath轴,获取所有后续节点,直到
我有以下HTML示例:
Foo bar
lorem
ipsum
etc
Bar baz
dum dum dum
poopfiddles
我想要提取’Foo bar’标题后面的所有段落,直到我到达’Bar baz’标题(’bar baz’标题的文字未知,所以不幸的是我无法使用bougyman提供的答案)。 现在我当然可以使用类似//h2[text()='Foo bar']/following::p
但当然会抓住此标题后的所有段落。 因此,我可以选择遍历节点集并将段落推送到数组,直到文本与下一个标题的文本匹配为止,但说实话,这绝不像在XPath中那样酷。
有没有办法做到这一点,我错过了?
使用 :
(//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'])]
这意味着 :选择跟随h2
兄弟姐妹(文档中的第一个或仅一个)的所有p
元素,其字符串值为'Foo bar'
并且所有这些p
元素的第一个前一个兄弟h2
正好是h2 (first or only one in the document) whose string value is
‘Foo bar’。
这里我们使用一种方法来查找两个节点是否相同 :
count($n1 | $n2) = 1
当节点$n1
和$n2
是同一节点时,它们是true()
。
这个表达式可以推广 :
$x/following-sibling::p [1 = count(preceding-sibling::node()[name() = name($x)][1] | $x)]
选择$ x指定的任何节点的所有“紧随其后的兄弟姐妹” 。
在XPath 2.0中(我知道这对你没有帮助……)最简单的解决方案可能就是
H2 [。 =’Foo bar’] / follow-sibling :: *除了h2 [。 =’Bar baz’] /(。| following-sibling :: *)
但是和其他解决方案一样,这可能(在没有识别模式的优化器的情况下)在第二个h2之外的元素数量上是线性的,而你真的想要一个性能仅取决于元素数量的解决方案选择。 我一直觉得有一个直到操作员会很好:
h2[. = 'Foo bar']/(following-sibling::* until . = 'Bar baz')
在缺少的情况下,当要选择的节点数量与后续兄弟节点数量相比较时,使用递归的XSLT或XQuery解决方案可能会表现得更好。
这个XPATH 1.0语句选择所有
,它们是
后面的兄弟姐妹,其字符串值等于“Foo bar”,后面跟着一个
兄弟元素谁是第一个兄弟姐妹
字符串值“Foo bar”。
//p[preceding-sibling::h2[.='Foo bar']] [following-sibling::h2[ preceding-sibling::h2[1][.='Foo bar']]]
仅仅因为它不在答案之间,经典的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方法的否定。
XPath 2.0有运算符<<
(如果$node1
在$node2
之前,则$node1 << $node2
为真),这样你就可以使用//h2[. = 'Foo bar']/following-sibling::p[. << //h2[. = 'Bar baz']]
//h2[. = 'Foo bar']/following-sibling::p[. << //h2[. = 'Bar baz']]
//h2[. = 'Foo bar']/following-sibling::p[. << //h2[. = 'Bar baz']]
。 然而,我不知道nokogiri是否支持XPath 2.0。
require 'nokogiri' doc = Nokogiri::XML < Foo
lorem
ipsum
etc
Bar
dum dum dum
poopfiddles
ENDXML a = doc.xpath( '//h2[text()="Foo"]/following::p[not(preceding::h2[text()="Bar"])]' ) puts a.map{ |n| n.to_s } #=> lorem
#=> ipsum
#=> etc
我怀疑使用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 } #=> lorem
#=> ipsum
#=> etc
但是,这并不快。 在一些简单的测试中,我发现xpath-only(第一个解决方案)的速度是这个循环测试的2倍,即使在stop节点之后有很多段落也是如此。 当有许多节点要捕获时(停止后很少)它在6x-10x范围内表现更好。
如何匹配第二个? 如果您只想要顶部,请匹配第二部分并抓住它上面的所有内容。
doc.xpath("//h2[text()='Bar baz']/preceding-sibling::p").map { |m| m.text }
doc.xpath("//h2[text()='Bar baz']/preceding-sibling::p").map { |m| m.text }
=> [“lorem”,“ipsum”,“etc”]
或者如果您不知道第二个,请转到另一个级别: doc.xpath("//h2[text()='Foo bar']/following-sibling::h2/preceding-sibling::p").map { |it| it.text }
doc.xpath("//h2[text()='Foo bar']/following-sibling::h2/preceding-sibling::p").map { |it| it.text }
=> [“lorem”,“ipsum”,“etc”]