Re: [xsl] Identify last node in nested nodeset with same name

Subject: Re: [xsl] Identify last node in nested nodeset with same name
From: "Sam D. Chuparkoff" <sdc@xxxxxxxxxx>
Date: Sat, 25 Jun 2005 00:04:28 -0700
On Thu, 2005-06-23 at 19:26 -0700, Mat Bergman wrote:
> I am working with XML data that shares the same
> element name for each node set, for example:
> 
> <menu name="link1"/>
> <menu name="link2">
>      <menu name="link2a"/>
>      <menu name="link2b"/>
> </menu>
> 
> My XSL stylesheet transforms this into an HTML
> unordered list, like this:
> <ul>
> <li>link1</li>
> <li>link2
>      <ul>
>      <li>link2a</li>
>      <li>link2b</li>
>      </ul>
> </li>
> </ul>

I think this is a good question as asked. I noticed both of the
previous answers started by wrapping the input in a new element and
defining a template for that new node. But sometimes it is necessary
to introduce structure that isn't present in the input around a
sequence of like siblings. Wrapping li s in ul s is a good example.

Here's a solution that uses modes a little much, but tries to make the
point that one could generalize this kind of logic, though it's not
done here.

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
  <xsl:output method="xml" indent="yes"/>
  <xsl:strip-space elements="*"/>

  <!-- identity -->
  <xsl:template match="@*|node()">
    <xsl:copy><xsl:apply-templates select="@*|node()"/></xsl:copy>
  </xsl:template>

  <!-- mode change on the first in a sequence of menu siblings -->
  <xsl:template match="menu[not(preceding-sibling::*[1]/self::menu)]">
    <xsl:apply-templates select="." mode="slurp"/>
  </xsl:template>

  <!-- spit this and, recursively, all consecutive menu siblings -->
  <xsl:template match="menu" mode="swish">
    <xsl:apply-templates select="." mode="spit"/>
    <xsl:apply-templates select="following-sibling::*[1]/self::menu"
      mode="swish"/>
  </xsl:template>

  <!-- here we can introduce some new structure -->
  <xsl:template match="menu" mode="slurp">
    <ul>
      <xsl:apply-templates select="." mode="swish"/>
    </ul>    
  </xsl:template>

  <!-- a menu is translated to an li -->
  <xsl:template match="menu" mode="spit">
    <li>
      <xsl:value-of select="@name"/>
      <xsl:apply-templates/>
    </li>
  </xsl:template>

  <!-- ignore any menu item that isn't the first in a series -->
  <xsl:template match="menu"/>

</xsl:stylesheet>

Here's some input:

<root>
  <random>Random text</random>
  <menu name="link1"/>
  <menu name="link2">
    <menu name="link2a"/>
    <menu name="link2b"/>
    <menu name="link2c"/>
    <random>Random text</random>
  </menu>
  <random>Random text</random>
</root>

And here's the output, with spaces added by me:

<root>
  <random>Random text</random>
  <ul>
    <li>link1</li>
    <li>
      link2
      <ul>
        <li>link2a</li>
        <li>link2b</li>
        <li>link2c</li>
      </ul>
      <random>Random text</random>
    </li>
  </ul>
  <random>Random text</random>
</root>

Comments/links welcome.

sdc

Current Thread