Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XPath to select the last node in nested XML structure?

Tags:

html

xml

xpath

Suppose I have this XML:

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

But my templates might change, and I want to be flexible in the depth in which the div element is located. Example:

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

So how I can get the innertext from the last element in the XML from this nested XML structure?

like image 677
macio Avatar asked Sep 02 '26 03:09

macio


1 Answers

So how I can get the innertext from the last element in the XML from this nested XML structure?

It seems from the posted XML document that what is actually asked is:

How can I get the string value of the innermost element in the XML document


I. XPath 1.0 / XSLT 1.0 solution:

This XPath expression, when evaluated:

//*[not(*)]

selects all elements in the document that don't have another element as a child.

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

selects the last such inner-most node.

It is not possible to find the "deepest element" with a single XPath 1.0 expression -- one could do this with a simple XSLT 1.0 transformation. The XSLT 1.0 transformation below copies to the output the last inner-most element of the XML document:

<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>

When this transformation is applied on the following XML document:

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

the wanted, correct result is produced:

<div>
 "Hello3"
</div>

If you want just the string value of this element, simply replace:

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

with

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

II. Pure XPath 2.0 solution

Use this XPath 2.0 expression:

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

XSLT 2.0 - based verification:

This transformation evaluates the above XPath 2.0 expression and copies to the output the result of this evaluation:

<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>

when applied to the same XML document (above), again the same correct, wanted result is produced:

"Hello3"
like image 130
Dimitre Novatchev Avatar answered Sep 04 '26 18:09

Dimitre Novatchev



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!