XPath以查找所有后续兄弟姐妹,直到特定类型的下一个兄弟

鉴于此XML / HTML:

Label1
Value1
Label2
Value2
Label3
Value3a
Value3b
Label4
Value4

我想找到所有

然后,为每个

找到以下

直到下一个

使用Ruby的Nokogiri我能够像这样完成:

 dl.xpath('dt').each do |dt| ct = dt.xpath('count(following-sibling::dt)') dds = dt.xpath("following-sibling::dd[count(following-sibling::dt)=#{ct}]") puts "#{dt.text}: #{dds.map(&:text).join(', ')}" end #=> Label1: Value1 #=> Label2: Value2 #=> Label3: Value3a, Value3b #=> Label4: Value4 

但是,你可以看到我在Ruby中创建一个变量,然后使用它编写一个XPath。 如何编写一个相同的XPath表达式?

我猜到:

 following-sibling::dd[count(following-sibling::dt)=count(self/following-sibling::dt)] 

但显然我不明白那里的self意味着什么。

这个问题类似于XPath:选择所有以下兄弟,直到另一个兄弟,除了’stop’节点没有唯一标识符。

这个问题与xpath几乎相同,除了我要求的XPath-only解决方案之外, 找到所有以下兄弟相邻节点直到另一个类型 。

一种可能的方案:

 dl.xpath('dt').each_with_index do |dt, i| dds = dt.xpath("following-sibling::dd[not(../dt[#{i + 2}]) or " + "following-sibling::dt[1]=../dt[#{i + 2}]]") puts "#{dt.text}: #{dds.map(&:text).join(', ')}" end 

这依赖于dt元素的比较,并且当存在重复时将失败。 以下(更复杂的)表达式不依赖于唯一的dt值:

 following-sibling::dd[not(../dt[$n]) or (following-sibling::dt[1] and count(following-sibling::dt[1]|../dt[$n])=1)] 

注意:您对self使用失败,因为您没有正确使用它作为轴( self:: 。 另外, self总是只包含上下文节点,因此它将引用由表达式检查的每个dd ,而不是返回到原始dt

这是个有趣的问题。 大多数问题已经在@ lwburk的回答和评论中提到过。 为了给随机读者打开隐藏在这个问题中的复杂性,我的答案可能比OP需要的更复杂或更冗长。

与此问题相关的XPath 1.0的function

在XPath中,每个步骤以及所选节点集中的每个节点都独立工作。 这意味着

  1. 子表达式没有通用的方法来访问在先前子表达式中计算的数据,或者将在此子表达式中计算的数据共享到其他子表达式
  2. 节点没有通用方法来引用在先前子表达式中用作上下文节点的节点
  3. 节点没有通用的方法来引用当前选中的其他节点。
  4. 如果必须将所选节点的每个节点与同一节点进行比较,则该节点必须以所有选定节点共有的方式唯一定义

(嗯,实际上我并不是100%确定这个列表在每种情况下都是绝对正确的。如果有人对XPath的怪癖有更好的了解,请通过编辑来评论或更正这个答案。)

尽管缺乏通用解决方案,但是如果对文档结构有适当的了解,则可以克服这些限制中的一些,和/或先前使用的轴可以与另一个用作反向链接的轴“还原”,即仅匹配使用的节点作为上一个表达式中的上下文节点。 一个常见的例子是在第一次使用child轴之后使用parent轴(相反的情况,从子级到父级,在没有附加信息的情况下不能唯一可恢复)。 在这种情况下,来自先前步骤的信息在后面的步骤中更精确地重新创建(而不是访问先前已知的信息)。

不幸的是,在这种情况下,除了使用XPath变量(需要事先定义)之外,我无法提出任何其他解决方案来引用以前已知的节点。

XPath指定了引用变量的语法,但没有指定定义变量的语法,定义变量的方式取决于使用XPath的环境。 实际上,因为建议声明“用于评估子表达式的变量绑定始终与用于评估包含表达式的变量绑定相同”,您还可以声称XPath明确禁止在XPath表达式中定义变量。

问题重新制定

在您的问题中,当给定

,问题将是在切换上下文节点之后识别以下

元素或最初给定的节点。 识别最初给定的

是至关重要的,因为对于要过滤的节点集中的每个节点,使用该节点作为上下文节点来评估谓词表达式; 因此,如果在上下文发生变化后无法识别它,则无法引用谓词中的原始

。 这同样适用于跟随给定

兄弟姐妹的

元素。

如果您正在使用变量,可以争论的是1)使用XPath变量语法和Nokogiri特定方式声明该变量或2)使用Nokogiri扩展XPath语法允许您在XPath表达式中使用Ruby变量之间存在重大差异。 在这两种情况下,变量都是以特定于环境的方式定义的,只有当变量的定义也可用时,XPath的含义才会明确。 使用XSLT可以看到类似的情况,在某些情况下,您可以在1)使用XPath表达式之前使用定义变量或2)使用current() (在XPath表达式中)之间做出选择。 XSLT扩展。

使用节点集变量和Kaysian方法的解决方案

您可以使用following-sibling::dd (set A)选择当前

元素后面的所有

元素。 您还可以使用following-sibling::dt[1]/following-sibling::dd (set B)选择下一个

元素后面的所有

元素。 现在,设置差异A\B

您实际想要的

元素(在集合A中但在集合B中不存在的元素)。 如果变量$setA包含节点集A并且变量$setB包含节点集B,则可以通过(修改)Kaysian技术获得集合差异:

 dds = $setA[count(.|$setB) != count($setB)] 

没有任何变量的简单解决方法

目前,您的方法是选择所有

元素,然后尝试在单个操作中将每个此类元素的值与相应

元素的值耦合。 是否有可能将该耦合逻辑转换为相反的方式? 因此,您首先要选择所有

元素,然后为每个

找到相应的

。 这意味着您最终会多次访问相同的

元素,并且每次操作时只添加一个新的

值。 这可能会影响性能,Ruby代码可能会更复杂。

好的一面是所需XPath的简单性。 当给出

元素时,找到相应的

非常简单: preceding-sibling::dt[1]

适用于您当前的Ruby代码

 dl.xpath('dd').each do |dd| dt = dd.xpath("preceding-sibling::dt[1]") ## Insert new Ruby magic here ## end 
Interesting Posts