views:

234

answers:

3

I understand how to process this doc (below) with XSLT from the outermost person element to the innermost. But I was wondering if:
1) If its possible to work from the deepest element out. 2) What that would look like given my example.

<?xml version="1.0" encoding="utf-8" ?>
<container>
  <person name="Larry">
    <person name="Moe">
      <person name="Curly">
        <person name="Shemp">

        </person>
      </person>
    </person>
  </person>
</container>

UPDATE:
Look at Teun D's answer for directness... But I have to give the rep to Dimitre Novatchev for the DRY-nature of his answer. Very cool!

+11  A: 

You can use the parent:: axis for working up.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:template match="/">
      <xsl:apply-templates select="//person[not(person)]"/>
    </xsl:template>
  <xsl:template match="person">
    <xsl:value-of select="@name"/> -&gt; <xsl:apply-templates select="parent::person"/>
  </xsl:template>
</xsl:stylesheet>

Not tested, I may be off-syntax.

It should print Shemp -> Curly -> Moe -> Larry

Teun D
+1 Seems to work very nicely.
Andrew Hare
Would be worth +1 just for "//person[not(person)]". :-)
Ben Blank
+1  A: 

A solution similar to that of teun, but allowing "person" elements to be in not immediate parent-child relationship is the following:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output method="text"/>

    <xsl:template match="/">
      <xsl:apply-templates select="//person[not(person)]"/>
    </xsl:template>

    <xsl:template match="person" name="tPerson">
       <xsl:value-of select="concat(@name,'&#xA;')"/>

       <xsl:apply-templates select=
          "ancestor::person[1]"/>
    </xsl:template>
</xsl:stylesheet>

When applied on the originally provided XML document, the correct result is produced:

Shemp

Curly

Moe

Larry

It is good to know that many problems of this type do not require recursion at all! This transformation produces exactly the same result and only involves iteration:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output method="text"/>

    <xsl:template match="/">
       <xsl:for-each select="//person[not(person)]">
          <xsl:for-each select="ancestor::person | .">
            <xsl:sort select="position()" order="descending"/>
                <xsl:value-of select="concat(@name,'&#xA;')"/>
          </xsl:for-each>
       </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

Do note the sort that ensures the reverse order of processing.

Dimitre Novatchev
+2  A: 

Here is the most general way of performing "backwards recursion" that does not depend on this specific problem and can be used in a wide variety of problems.

This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output method="text"/>

    <xsl:template match="/">
      <xsl:call-template name="backwardsRecursion">
        <xsl:with-param name="pList" select="//person"/>
      </xsl:call-template>
    </xsl:template>

    <xsl:template name="backwardsRecursion">
      <xsl:param name="pList"/>

      <xsl:if test="$pList">
        <xsl:apply-templates select="$pList[last()]"/>

        <xsl:call-template name="backwardsRecursion">
          <xsl:with-param name="pList" select=
           "$pList[position() &lt; last()]"/>
        </xsl:call-template>
      </xsl:if>
    </xsl:template>

    <xsl:template match="person">
      <xsl:value-of select="concat(@name,'&#xA;')"/>
    </xsl:template>
</xsl:stylesheet>

when applied on the originally-provided XML document, produces the wanted result:

Shemp
Curly
Moe
Larry

Do note, that a generic template named "backwardsRecursion" is called, that really implements the backwards recursion. This template does not know anything about the nodes it processes, or how they will be processed.

Thus, this template can be used in every situation where backwards recursive processing is needed.

Dimitre Novatchev
+1. I'll have to test this strategy in my .NET app tomorrow. I think the .NET XSLT processor should handle all these tags.
tyndall
@Bruno Tyndall I have tested this transformation with .NET 2.0 + XslCompiledTransform. It should work OK with the older XslTransform, too.
Dimitre Novatchev