Re: [xsl] Getting the deepest node and calculating relative paths

Subject: Re: [xsl] Getting the deepest node and calculating relative paths
From: Jeni Tennison <mail@xxxxxxxxxxxxxxxx>
Date: Fri, 26 Jan 2001 15:51:58 +0000
Linda wrote:
> Where IS Jeni?

How nice to be missed ;)  Just a very busy couple of weeks, I'm
afraid.  One of the points of XSLTDoc was to provide an
auto-documentation daemon ;)

Anyway, to describe David C.'s typically verbose solution:

David's using the translate() and string-length() functions in a very
cunning way to count the number of '/' characters in a string. He does
this by filtering out any of the *other* characters. The following
variable is set up to hold any name characters in the paths you're
using (i.e. everything aside from '/').

<xsl:variable
  name="l"
  select="'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@_'"/>

Given one of Iresh's paths, e.g.:

  /Catalog/Department/Product/SKU

Doing the following:

  translate('/Catalog/Department/Product/SKU',
            $l, '')

will replace any of the characters held by the $l variable with
nothing, i.e. turn the path to:

  '////'

Then using the string-length() function on this will tell you the
number of '/' characters in the path.  Of course you have to know
and specify *all* of the characters that might occur in a string to
use this: if hyphens were used in the element names, for example, then
you'd have to include them in the $l variable.

Once you understand that, the rest is fairly straight forward. The
main template locates the longest of the paths using the 'sort and
select' method. The paths are sorted according to the number of '/'
characters in them (identified through the translate() and
string-length() functions as above), and the last of these is selected
(they're sorted in ascending order, so the last is the longest).

<xsl:template match="Root">
  <Root>
    <xsl:for-each select="Paths/Path/@value">
      <xsl:sort select="string-length(translate(.,$l,''))" data-type="number"/>
      <xsl:if test="position()=last()">
        <LongestPath>
          <xsl:value-of select="."/>
        </LongestPath>
        ...
      </xsl:if>
    </xsl:for-each>
  </Root>
</xsl:template>

Constructing the relative paths involves cycling through each of the
paths in turn using an xsl:for-each. First, though, the longest path
is stored as $y and the number of steps in the longest path is stored
as $x.

  <RelPaths>
    <xsl:variable name="y" select="."/>
    <xsl:variable name="x"
          select="string-length(translate(.,$l,''))"/>
    <xsl:for-each select="/Root/Paths/Path">
      <RelPath>
        <xsl:attribute name="value">
          ...
        </xsl:attribute>
      </RelPath>
    </xsl:for-each>
  </RelPaths>

During the iterations, if the path is the same as the longest path,
then only the '.' is required.  If it's one of the other paths, you
need a number of '../' steps (provided through the 'r' template) and
the name element/attribute that's finally selected by the path
(provided through the 'l' template).

  <xsl:choose>
     <xsl:when test="@value=$y">.</xsl:when>
     <xsl:otherwise>
        <xsl:call-template name="r">
           <xsl:with-param name="x" select="$x -
                      string-length(translate(@value,$l,''))"/>
        </xsl:call-template>
        <xsl:call-template name="l"/>
     </xsl:otherwise>
  </xsl:choose>

The 'r' template repeats '../' a number of times, dependent on the $x
parameter that's passed into it.  It does this by recursion: as long
as $x is more than 0 then it calls itself with $x being one less:

<xsl:template name="r">
  <xsl:param name="x" />
  <xsl:if test="$x &gt;= 0">../<xsl:call-template name="r">
                                 <xsl:with-param name="x" select="$x -1"/>
                               </xsl:call-template>
  </xsl:if>
</xsl:template>

The 'l' template steps through the path to find the final
element/attribute through recursion. While the path contains a '/', it
selects the string after the '/'.

<xsl:template name="l">
  <xsl:param name="s" select="@value"/>
  <xsl:choose>
    <xsl:when test="contains($s,'/')">
      <xsl:call-template name="l">
        <xsl:with-param name="s" select="substring-after($s,'/')"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$s"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

I hope that helps,

Jeni

---
Jeni Tennison
http://www.jenitennison.com/



 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread