XSLT depth count

Depth of a Node

1. How to get the depth of a node
2. Depth of a node
3. Walking a node set.
4. How to find the deepest node?

1.

How to get the depth of a node

David Carlisle

To get the depth of all nodes, one can do a simpler and more
efficient method of just having a parameter with the current
depth.

so make every template have
<xsl:param name="x"/>

and have

<xsl:template match="/">
  <xsl:apply-templates>
   <xsl:with-param name="x" select="0"/>
  </xsl:apply-templates>
</xsl:template>


and make every other call to apply templates look like:

  <xsl:apply-templates>
   <xsl:with-param name="x" select="$x+1"/>
  </xsl:apply-templates>

Juergen Hermann added:

Using Xalan, you'll get this

DOCUMENT: 0
  HEADER: 1
    LINE: 2 toto
    LINE: 2 titi

  BODY: 1
    LINE: 2 the body text here

from this stylesheet

<?xml version="1.0"?>

<xsl:stylesheet xmlns:xsl
  ="http://www.w3.org/1999/XSL/Transform">

  <xsl:template match="node()">
    <xsl:value-of select="name(.)"/>: <
        xsl:value-of select="count(ancestor::*)"/>
    <xsl:apply-templates select="node()"/>
  </xsl:template>

</xsl:stylesheet>

2.

Depth of a node

Tony Graham

 count(ancestor::*)
    

That will get you the number of ancestor elements, but won't include the root node, which is also an ancestor. If you really want all of the ancestors, try:

count(ancestor::node())

Mike Kay rounds it out with:

count(ancestor::*) to give all the ancestor elements
count(ancestor::branch) to give all the ancestor branch elements
count(ancestor::node()) to give all the ancestor nodes, including the root

3.

Walking a node set.

Wendell Piez




  
 >I'd like to build a template that gives me "path" of parent nodes
 >for example:
 >
 >XML
 ><components>
 >   <component name="comp1">
 >     <some_node1>
 >       <component name="comp2">
 >       </component>
 >     </some_node1>
 >     <some_node2>
 >       <some_node3>
 >         <component name="comp3">
 >           <some_node>
 >         </component>
 >       </some_node3>
 >     </some_node2>
 >   </component>
 ></components>


 >output:
 >
 >"comp1/comp3"
 
 

The easiest way is to do it just the way your Subject line says. To iterate over a set of nodes, use xsl:for-each. Although often abused, for-each is perfect for this kind of thing.

So:

  <xsl:template match="some_node">
     <xsl:for-each select="ancestor::component">
     <!-- Walking through ancestors named 'component'.
          By default, for-each traverses the nodes in document order -->
       <xsl:if test="position() &gt; 1">
         <!-- add a '/' delimiter in front of all steps but the 
  first one -->
         <xsl:text>/</xsl:text>
       </xsl:if>
       <xsl:value-of select="@name"/>
     </xsl:for-each>
  </xsl:template>
 

Without xsl:for-each, you'd have to have a special set of templates that walked up (instead of down) the tree, and somehow reversed the order of the values reported. It could be done with a single recursive template. But this solution is easier.

4.

How to find the deepest node?

Mike Kay, David Carlisle and Dimitre Novatchev



> I have the following XML-file (see below) and I need to find the 
> deepest node of 'title'.
> The resultfile should only contain the titles 'Text 02' and 'Text 03', 
> because they are in this case the deepest nodes. The next XML-file I 
> receive may contain more nested levels of items and whatever the level 
> of nesting I only need to find the deepest titles.
> I know I have to do something with recurse, but I don't know how to 
> start.


> <?xml version="1.0" encoding="UTF-8"?> <document>
>   <item>
>     <item>
>       <title>Text 01</title>
>     </item>
>     <item>
>       <item>
>         <title>Text 02</title>
>       </item>
>     </item>
>   </item>
>   <item>
>     <item>
>       <item>
>         <title>Text 03</title>
>       </item>
>     </item>
>   </item>
>   <item>
>     <item>
>       <title>Text 04</title>
>     </item>
>   </item>
> </document>

> Resultfile should contain:

> <?xml version="1.0" encoding="UTF-8"?> <document>
>   <item>
>     <item>
>       <item>
>         <title>Text 02</title>
>       </item>
>     </item>
>   </item>
>   <item>
>     <item>
>       <item>
>         <title>Text 03</title>
>       </item>
>     </item>
>   </item>
> </document>

In 1.0 you can usually tackle this kind of problem by sorting nodes according to their depth (that is, count(ancestor::*)) and taking the first or last in sorted sequence. It's complicated here because you want all the nodes having that maximum depth. That suggests two passes, one to get the max depth and the second to select nodes with that depth

<xsl:variable name="max-depth">
  <xsl:for-each select="//title">
  <xsl:sort select="count(ancestor::*)" data-type="number"/>
  <xsl:if test="position()=last()">
   <xsl:copy-of select="count(ancestor::*)"/>
  </
</

<xsl:copy-of select="//title[count(ancestor::*) = $max-depth]"/>

and DC adds

This is a grouping problem (where you only want one group)

<xsl:key name="t" select="title" use="count(ancestor::*)"/>

...

<xsl:for-each select="//title">
<xsl:sort data-type="number" select="count(ancestor::*)"> 
  <xsl:if test="position()=last()"> 
    <xsl:apply-templates select="key('t',count(ancestor::*))"/>
  </xsl:if>
</xsl:for-each>

Dimitre adds

Using FXSL, one will write something as the following:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:f="http://fxsl.sf.net/"
 exclude-result-prefixes="f"
 >
 <xsl:import href="C:/CVS-DDN/fxsl-xslt2/f/func-maxDepth.xsl"/>

 <xsl:output omit-xml-declaration="yes"/>

 <xsl:template match="/">
   <xsl:sequence select=
   "//node()[count(ancestor::node()) = f:maxDepth(/)]/.."/>  
 </xsl:template> 
</xsl:stylesheet>

and applying this transformation on the originally specified xml source document produces the wanted result:

 <title>Text 02</title><title>Text 03</title>

Here's the code of func-maxDepth.xsl:


<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:f="http://fxsl.sf.net/"
xmlns:MyMaxDepth="MyMaxDepth"
exclude-result-prefixes="xs f MyMaxDepth"
>
   <xsl:import href="func-map.xsl"/>
   
    <xsl:variable name="vfunMaxDepth" 
                  select="document('')/*/MyMaxDepth:*[1]"/>
     
     <xsl:function name="f:maxDepth" as="xs:integer">
       <xsl:param name="pNode" as="node()"/>
       
       <xsl:value-of select=
       "if (not($pNode/node())) then 0
         else 
          max(f:map($vfunMaxDepth, $pNode/node())) + 1"/>
     </xsl:function>
     
     <MyMaxDepth:MyMaxDepth/>
     <xsl:template match="MyMaxDepth:*" mode="f:FXSL">
       <xsl:param name="arg1" as="node()"/>
       
       <xsl:sequence select="f:maxDepth($arg1)"/>
     </xsl:template>
</xsl:stylesheet>