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

时间:2011-12-13 16:08:09

标签: ruby xml xpath nokogiri

鉴于此XML / HTML:

<dl>
  <dt>Label1</dt><dd>Value1</dd>
  <dt>Label2</dt><dd>Value2</dd>
  <dt>Label3</dt><dd>Value3a</dd><dd>Value3b</dd>
  <dt>Label4</dt><dd>Value4</dd>
</dl>

我想找到所有<dt>,然后为每个<dd>找到以下<dt>,直到下一个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 Nokogiri我可以这样做:

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

但是,正如您所看到的,我正在Ruby中创建一个变量,然后使用它来编写XPath。如何编写一个具有相同功能的XPath表达式?

我猜到了:

self

但显然我不明白{{1}}的意思。

此问题类似于XPath : select all following siblings until another sibling,但“停止”节点没有唯一标识符。

此问题与xpath to find all following sibling adjacent nodes up til another type几乎相同,只是我要求提供仅限XPath的解决方案。

2 个答案:

答案 0 :(得分:5)

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

与此问题相关的XPath 1.0的功能

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

  1. 子表达式没有通用方法来访问在先前子表达式中计算的数据,或者将在此子表达式中计算的数据共享到其他子表达式
  2. 节点没有通用方法来引用在先前子表达式中用作上下文节点的节点
  3. 节点没有通用的方法来引用当前选中的其他节点。
  4. 如果必须将所选节点的每个节点与同一个特定节点进行比较,则该节点必须以对所有选定节点通用的方式唯一定义
  5. (嗯,实际上我并不是100%确定该列表在每种情况下都是绝对正确的。如果有人对XPath的怪癖有更好的了解,请通过编辑来评论或更正此答案。)< / p>

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

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

    XPath指定了引用变量的语法,但没有指定定义变量的语法,如何定义变量的方式取决于使用XPath的环境。实际上,因为建议声明&#34;用于评估子表达式的变量绑定总是与用于评估包含表达式的变量绑定相同,您还可以声称XPath明确禁止在XPath表达式中定义变量。 / p>

    重新制定的问题

    在您的问题中,当给定<dt>时,问题将是在切换上下文节点后识别以下<dd>元素或最初给定的节点。识别最初给定的<dt>是至关重要的,因为对于要过滤的节点集中的每个节点,使用该节点作为上下文节点来评估谓词表达式;因此,如果在上下文发生更改后无法识别它,则无法引用谓词中的原始<dt>。这同样适用于跟随给定<dd>的兄弟姐妹的<dt>元素。

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

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

    您可以使用<dd>(设置A)选择当前 <dt>元素后面的所有following-sibling::dd元素。您还可以选择 next <dd>元素后面<dt>元素的所有following-sibling::dt[1]/following-sibling::dd元素(设置为B)。现在,设置差异A\B会保留您实际想要的<dd>元素(元素集合在集合A中但不在集合B中)。如果变量$setA包含节点集A而变量$setB包含节点集B,则可以通过(修改)Kaysian技术获得集合差异:

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

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

    目前,您的方法是选择所有<dt>元素,然后尝试在单个操作中将每个此类元素的值与相应的<dd>元素的值耦合。是否可以转换该耦合逻辑以反过来工作?因此,您首先要选择所有<dd>元素,然后为每个<dd>找到相应的<dt>。这意味着您最终会多次访问相同的<dt>元素,并且每次操作时只会添加一个新的<dd>值。这可能会影响性能,Ruby代码可能会更复杂。

    好的一面是所需XPath的简单性。给定<dd>元素后,找到相应的<dt>非常简单:preceding-sibling::dt[1]

    适用于您当前的Ruby代码

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

答案 1 :(得分:3)

一种可能的解决方案:

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