首页 > 解决方案 > XPath 选择嵌套 XML 结构中的最后一个节点?

问题描述

假设我有这个 XML:

<div>
    <div>
       <div>
          <div>
          "Hello2"
          </div>
       </div>
    </div>
</div>

但我的模板可能会改变,我希望div元素所在的深度灵活。例子:

<div>
    <div>
       <div>
        "Hello3"
       </div>
    </div>
</div>

那么如何从这个嵌套的 XML 结构中获取 XML 中最后一个元素的内部文本呢?

标签: htmlxmlxpath

解决方案


那么如何从这个嵌套的 XML 结构中获取 XML 中最后一个元素的内部文本呢?

从发布的 XML 文档看来,实际要求的是:

如何获取XML 文档中最内层元素的字符串值


一、XPath 1.0 / XSLT 1.0 解决方案:

此 XPath 表达式在求值时:

//*[not(*)]

选择文档中没有另一个元素作为子元素的所有元素。

(//*[not(*)])[last()]

选择最后一个这样的最内层节点。

用单个 XPath 1.0 表达式不可能找到“最深的元素”—— 可以通过简单的 XSLT 1.0 转换来做到这一点。下面的 XSLT 1.0 转换将 XML 文档的最后一个最里面的元素复制到输出:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 
 <xsl:key name="kElemByDepth" match="*" use="count(ancestor::*)"/>

  <xsl:template match="/">
    <xsl:variable name="vMaxDepth">
        <xsl:apply-templates select="//*[not(*)]" mode="getMax">
           <xsl:sort select="count(ancestor::*)" data-type="number" order="descending"/>
        </xsl:apply-templates>
    </xsl:variable>
    
    <xsl:copy-of select="key('kElemByDepth', $vMaxDepth)[last()]"/>
  </xsl:template>
  
  <xsl:template match="*" mode="getMax">
    <xsl:if test="position() = 1">
      <xsl:value-of select="count(ancestor::*)"/>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

当此转换应用于以下 XML 文档时:

<div>
    <div>
       <div>
          <div>
          "Hello2"
          </div>
          <div>
          "Hello3"
          </div>
       </div>
    </div>
   <div>
     "Hello1"
    </div>
</div>

产生了想要的正确结果:

<div>
 "Hello3"
</div>

如果您只需要此元素的字符串值,只需替换:

<xsl:copy-of select="key('kElemByDepth', $vMaxDepth)[last()]"/>

<xsl:copy-of select="normalize-space(key('kElemByDepth', $vMaxDepth)[last()])"/>

二、纯 XPath 2.0 解决方案

使用这个 XPath 2.0 表达式:

normalize-space(//*[not(*)]
                     [not(count(ancestor::*) < //*[not(*)]/count(ancestor::*))][last()])

基于 XSLT 2.0 的验证

此转换评估上述 XPath 2.0 表达式并将此评估的结果复制到输出:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

  <xsl:template match="/">
    <xsl:sequence select=
    "normalize-space(//*[not(*)]
                 [not(count(ancestor::*) &lt; //*[not(*)]/count(ancestor::*))][last()])"/>
  </xsl:template>
</xsl:stylesheet>

当应用于同一个 XML 文档(上图)时,同样会产生同样正确的、想要的结果:

"Hello3"

推荐阅读