【问题标题】:Find position of a node within a nodeset using xpath使用 xpath 在节点集中查找节点的位置
【发布时间】:2011-02-06 01:32:36
【问题描述】:

在玩弄position() 无济于事之后,我在谷歌上搜索解决方案并到达this older stackoverflow question几乎描述了我的问题。

不同之处在于我想要在其中位置的节点集是动态的,而不是文档的连续部分。

为了说明,我将修改链接问题中的示例以符合我的要求。请注意,每个 <b> 元素都在不同的 <a> 元素中。这是关键。

<root>
    <a>
        <b>zyx</b>
    </a>
    <a>
        <b>wvu</b>
    </a>
    <a>
        <b>tsr</b>
    </a>
    <a>
        <b>qpo</b>
    </a>
</root>

现在,如果我查询,对a/b 使用XPath,我会得到四个&lt;b&gt; 节点的节点集。然后我想找到包含字符串'tsr' 的节点的位置在该节点集中。另一篇文章中的解决方案在这里分解:count(a/b[.='tsr']/preceding-sibling::*)+1 返回1,因为preceding-sibling 正在导航文档而不是上下文节点集。

是否可以在上下文节点集中工作?

【问题讨论】:

  • 您能使用 XSLT 2.0,还是坚持使用 1.0?

标签: xml xpath position nodesets


【解决方案1】:

从(即反对​​)根:

count(//a/b[.='tsr']/preceding::b)

如果你说另一个节点,例如:

<c>
    <b>qqq</b>
</c>

并且想忽略所有没有“a”父母的 b 元素,你可以做类似的事情

count(//a/b[.='tsr']/preceding::b[local-name(parent::node())='a'])

【讨论】:

    【解决方案2】:

    较早的 count-the-preceding(-sibling) 答案在某些情况下效果很好;您只是从所选项目的角度重新指定上下文节点集,然后将count(preceding:: ) 应用于它。

    但在其他情况下,正如您所暗示的那样,在您想要使用的节点集中进行计数确实很难。例如。假设您的工作节点集是 /html/body/div[3]//a(网页的第三个 &lt;div&gt; 中的所有 &lt;a&gt; 锚点),并且您想在该集合中找到 a[@href="foo.html"] 的位置.如果您尝试使用count(preceding::a),您可能会意外地计算来自其他 div 的 &lt;a&gt; 锚点,即在您的工作节点集之外。如果你尝试count(preceding-sibling::a),你不会得到它们,因为相关的&lt;a&gt; 元素可以在任何级别。

    您可以尝试使用preceding::a[ancestor::div[count(preceding-sibling::div) = 2]] 限制计数,但它很快就会变得非常尴尬,而且在所有情况下仍然不可能。此外,如果您曾经为您的工作集更新过 XPath 表达式,那么您将不得不重新编写此表达式,并且保持它们等效并非易事。

    但是,如果您使用的是 XSLT,则以下内容可以避免这些问题。如果您可以指定工作节点集,则可以在其中找到与提供的条件匹配的节点的位置。而且您不必指定节点集两次:

        <xsl:for-each select="/root/a/b">
            <xsl:if test=". = 'tsr'"><xsl:value-of select="position()"/></xsl:if>
        </xsl:for-each>
    

    这是因为在 for-each 中,上下文位置“标识了上下文项在正在处理的序列中的位置。”

    如果您不是在 XSLT 中工作,您处于什么环境中?那里可能有一个类似的结构用于迭代外部 XPath 表达式的结果,并且您可以在那里维护自己的计数器(如果没有可用的上下文位置),并根据您的内部标准测试每个项目。

    other guy's attempt on the older questiona/b[.='tsr']/position() 不起作用的原因是因为在每个斜杠处,都会将一个新的上下文压入堆栈,因此在调用 position() 时,上下文位置始终为 1。(顺便说一下,这种语法只适用于 XPath 2.0。)

    【讨论】:

    • 很好的解释(+1)。也许我的回答会让你感兴趣?
    • @Dimitre:我期待着消化它。并学习正确的答案。 :-)
    【解决方案3】:

    这是一个通用解决方案,适用于属于同一文档中任何节点集的任何节点

    我正在使用 XSLT 来实现该解决方案,但最终获得了可以与任何其他托管语言一起使用的单个 XPath 表达式。

    $vNodeSet 为节点集,$vNode 为该节点集中我们要查找其位置的节点。

    然后,让$vPrecNodes包含$vNode之前的XML文档中的所有节点。

    然后,让$vAncNodes 包含 XML 文档中所有作为 $vNode 祖先的节点。

    $vNodeSet 中按文档顺序排在$vNode 之前的节点集包括节点集中所有也属于$vPrecNodes 的节点,以及节点集中也属于$vAncNodes 的所有节点。

    我将使用著名的 Kaysian 公式计算两个节点集的交集:

    $ns1[count(.|$ns2) = count($ns2)]

    恰好包含$ns1$ns2 的交集处的节点。

    基于所有这些,让$vPrecInNodeSet$vNodeSet 中在文档顺序中位于$vNode 之前的节点集。下面的 XPath 表达式定义了$vPrecInNodeSet

    $vNodeSet
          [count(.|$vPrecNodes) = count($vPrecNodes)
          or
           count(.|$vAncNodes) = count($vAncNodes)
          ]
    

    最后,想要的职位是count($vPrecInNodeSet) +1

    这一切是如何协同工作的:

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
     <xsl:variable name="vNodeSet" select="/*/a/b"/>
    
     <xsl:variable name="vNode" select="$vNodeSet[. = 'tsr'][1]"/>
    
     <xsl:variable name="vPrecNodes" select="$vNode/preceding::node()"/>
    
     <xsl:variable name="vAncNodes" select="$vNode/ancestor::node()"/>
    
     <xsl:variable name="vPrecInNodeSet" select=
      "$vNodeSet
          [count(.|$vPrecNodes) = count($vPrecNodes)
          or
           count(.|$vAncNodes) = count($vAncNodes)
          ]
      "/>
    
     <xsl:template match="/">
       <xsl:value-of select="count($vPrecInNodeSet) +1"/>
     </xsl:template>
    </xsl:stylesheet>
    

    在提供的 XML 文档上应用上述转换时

    <root>
        <a>
            <b>zyx</b>
        </a>
        <a>
            <b>wvu</b>
        </a>
        <a>
            <b>tsr</b>
        </a>
        <a>
            <b>qpo</b>
        </a>
    </root>
    

    产生正确的结果

    3

    注意:此解决方案不依赖于 XSLT(仅用于说明目的)。您可以组装一个 XPath 表达式,用它们的定义替换变量,直到没有更多的变量可以替换。

    【讨论】:

    • 不错的答案,正如预期的那样。这些是我以前见过的技术,但不够流畅,无法在需要时提出。只是为了好玩,我尝试扩展变量以查看仅 XPath 等效项的外观:count(/*/a/b[count(. | /*/a/b[. = 'tsr'][1]/preceding::node()) = count(/*/a/b[. = 'tsr'][1]/preceding::node()) or count(. | /*/a/b[. = 'tsr'][1]/ancestor::node()) = count(/*/a/b[. = 'tsr'][1]/ancestor::node()) ]) + 1(大约 212 个字符)。为了安全起见,可能需要在 $vNodeSet 的值周围加上括号,例如如果它的最后一个轴是反向轴。
    【解决方案4】:

    我想我有一个可行的解决方案

    这个想法是计算文档中有多少元素在我们的目标元素之前,并计算节点集中有多少节点具有更少或相同数量的前面元素。在 XPath 中是:

    count(//a/b[count(./preceding::node()) &lt;= count(//a/b[.='tsr']/preceding::node())])
    

    您还可以在此表达式中使用变量来查找不同的节点集或匹配不同的文本内容。这里的重要部分是变量具有正确的类型。下面是一个 XSLT 示例和一个使用问题的示例文档作为输入文件的示例输出

    XSLT 文档

    <xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
        <xsl:output encoding="utf-8" method="text"/>
    
        <xsl:variable name="nodeset" select="//a/b"/>
        <xsl:variable name="path-string">//a/b</xsl:variable>
        <xsl:variable name="text">tsr</xsl:variable>
    
        <xsl:template match="/">
            <xsl:text>Find and print position of a node within a nodeset&#10;&#10;</xsl:text>
    
            <xsl:text>Position of "tsr" node in the nodeset = "</xsl:text>
            <xsl:value-of select="count(//a/b[count(./preceding::node()) &lt;= count(//a/b[.='tsr']/preceding::node()) ])"/>
            <xsl:text>"&#10;&#10;</xsl:text>
    
            <xsl:text>( Try the same using variables "$nodeset" and "$text" )&#10;</xsl:text>
            <xsl:text>Size of nodeset "$nodeset" = "</xsl:text>
            <xsl:value-of select="count($nodeset)"/>
            <xsl:text>"&#10;</xsl:text>
            <xsl:text>Variable "$text" = "</xsl:text>
            <xsl:value-of select="$text"/>
            <xsl:text>"&#10;</xsl:text>
            <xsl:text>Position of "</xsl:text>
            <xsl:value-of select="$text"/>
            <xsl:text>" node in the nodeset = "</xsl:text>
            <xsl:value-of select="count($nodeset[count(./preceding::node()) &lt;= count($nodeset[.=$text]/preceding::node()) ])"/>
            <xsl:text>"&#10;&#10;</xsl:text>
    
            <xsl:text>( Show that using a variable that has the path as a string does not work )&#10;</xsl:text>
            <xsl:text>Variable "$path-string" = "</xsl:text>
            <xsl:value-of select="$path-string"/>
            <xsl:text>"&#10;</xsl:text>
            <xsl:text>Result of "count($path-string)" = "</xsl:text>
            <xsl:value-of select="count($path-string)"/>
            <xsl:text>"&#10;&#10;</xsl:text>
    
            <xsl:text>End of tests&#10;</xsl:text>
        </xsl:template>
    
    </xsl:stylesheet>
    

    示例文档的输出

    Find and print position of a node within a nodeset
    
    Position of "tsr" node in the nodeset = "3"
    
    ( Try the same using variables "$nodeset" and "$text" )
    Size of nodeset "$nodeset" = "4"
    Variable "$text" = "tsr"
    Position of "tsr" node in the nodeset = "3"
    
    ( Show that using a variable that has the path as a string does not work )
    Variable "$path-string" = "//a/b"
    Result of "count($path-string)" = "1"
    
    End of tests
    

    没有对我的解决方案进行了广泛的测试,所以如果你使用它,请提供反馈。

    【讨论】:

    • +1 这是一个聪明的解决方案。对于大型节点集,它可能会变得很慢,O(m * n) 其中 m 是工作集中的节点,n 是文档中的节点。但是它应该适用于任何 XPath 表达式。您确实必须为工作节点集指定 XPath 两次,但您以完全相同的方式执行此操作,因此它不太容易出错。这种方法可能不起作用的唯一情况是,如果您选择的节点不是元素并且没有中间元素:例如在评论节点中选择文本节点。但是,如果您将 preceding::* 更改为 preceding::node(),您的状态会很好。
    • 我的意思是,这个答案值得注意,因为它只使用 XPath。但是,如果您可以使用 XSLT 或其他一些周边工具,我的回答可能更有效。 :-)
    • @LarsH 谢谢,我的目标是只有 XPath 的解决方案。一般来说,我也认为在 XSLT 模板中循环集合会是更好的解决方案。
    • 我编辑了解决方案以符合您的建议。最初我没有使用node(),因为如果选择还包含属性或命名空间节点,我开始对结果犹豫不决。我知道那些在 XML 文档中并没有真正的顺序,并且解析器/XSLT 处理器实现之间的位置可能会有所不同。例如,始终将 XML 作为规范读取会是解析器错误,这将按名称重新排列属性而不是文档中写入的属性。毕竟文档模型不会保持不变吗?
    • 在 XSLT 中属性和命名空间节点(在同一个元素上)确实没有定义的文档顺序。 (我对规范 XML 了解不多,我相信这是一个单独的问题。)但是,除非您尝试在包含其他属性的节点集中查找属性或命名空间节点的位置,否则这不会影响我们的问题/同一元素上的命名空间节点;在这种情况下,答案是未定义的,因此没有实现可以正确计算它。
    【解决方案5】:

    这个怎么样..

    count(a/b[.='tsr']/preceding-sibling::b) + count(a[b[.='tsr']]/preceding-sibling::a/b) + 1
    

    统计当前a元素中b元素的前兄弟,然后统计a元素所有前兄弟的b元素。或者类似的东西。

    【讨论】:

      【解决方案6】:

      你得到 1 的原因与上下文与文档无关,而是因为你只计算一个 a 节点中的 b 节点(所以你总是会得到 0 的计数,因为从来没有任何前面的“b”节点。

      您需要在包含您的“a”的“b”之前找到前面的“a”节点的计数。

      类似:

      count(a[b[.='tsr']]/preceding-sibling::a)
      

      【讨论】:

      • 好吧,在这种情况下,它是 'a's 的计数 - 但这并不是我真正想要计算的。我想计算上下文节点集中“b”的数量。如果您想象某些“a”节点包含多个“b”节点,这可能会导致不同的计数。
      • @Tomalak - 更接近 - 但这没有得到任何可能在当前 内(但在匹配节点之前)的额外 节点
      • @Phil:只需添加一个/b 以将前面as 的b 子代包含在计数中(在末尾)。这里的关键是您需要从b 的父级a 开始,而不是直接从b 开始..
      • 谢谢。仍在尝试理解这一点(我的思绪已经转移到其他事情上。我想我明白你在说什么,但仍然不知道如何拼凑起来)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-04-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-24
      • 1970-01-01
      相关资源
      最近更新 更多