views:

462

answers:

4

I know I'm missing something here. In the XSLT transformation below, the actual result doesn't match the desired result.

Inside the for-each, I want to apply the match="track" template to each selected track element. If I've understood XSLT properly, with the current setup only child nodes of each selected track element are matched against templates, not the track elements themselves.

How can I make the track elements go through the template as desired? Do I need to rethink my entire approach?

Note: The transformation is executed using PHP. XML declarations have been omitted for brevity.

XML Document:

<album>
 <title>Grave Dancers Union</title>
 <track id="shove">Somebody To Shove</track>
 <track id="gold">Black Gold</track>
 <track id="train">Runaway Train</track>
 <producer>Michael Beinhorn</producer>
</album>

XSL Stylesheet:

<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:template match="/album">
  <ol>
   <xsl:for-each select="track">
    <li><xsl:apply-templates/></li>
   </xsl:for-each>
  </ol>
 </xsl:template>

 <xsl:template match="track">
  <a href="{@id}"><xsl:apply-templates/></a>
 </xsl:template>
</xsl:stylesheet>

Result:

<ol>
 <li>Somebody To Shove</li>
 <li>Black Gold</li>
 <li>Runaway Train</li>
</ol>

Desired Result:

<ol>
 <li><a href="shove">Somebody To Shove</a></li>
 <li><a href="gold">Black Gold</a></li>
 <li><a href="train">Runaway Train</a></li>
</ol>
+3  A: 

I'd restructure it a little (if you do not need the sorting the for-each approach makes possible):

<xsl:template match="/album">
  <ol>
    <xsl:apply-templates select="track"/>
  </ol>
</xsl:template>

<xsl:template match="track">
  <li><a href="{@id}"><xsl:apply-templates/></a></li>
<xsl:template>

This looks shorter and more to the point, IMHO.

I guess your

    <xsl:for-each select="track">
       <li><xsl:apply-templates/></li>
    </xsl:for-each>

walks through all track elements with the for-each, and then applies the default rules to its descendants. So the content of the for-each has the same context node as the match="track" template has, and thus the match="track" template never matches.

If you really want to use the for-each in that way, you will need to change either of the following two things in your approach:

  1. Add a name="track" attribute to the match="track" template, and then use <xsl:call-template/> from within the for-each (my idea, and worse than Tim C's)
  2. Use Tim C's solution using <xsl:apply-templates select="."/>. This has the advantage of avoiding naming and keeping the possibility to sort the tracks.
ndim
The restructuring solution seems less modular than I would like (it doesn't allow me to also include tracks in a `<dl>` or `<table>` somewhere else on the page/site, at least not using the same stylesheet), but you're probably right in that it's the smoothest solution for this example.
Jakob
+3  A: 

I would agree with 'ndim' that you should probably restructure your XSLT to do away with the xsl:for-each loop.

Alternatively, you could amend the xsl:apply-templates to select the current track node within the xsl:for-each

<xsl:for-each select="track">
   <li>
      <xsl:apply-templates select="." />
   </li>
</xsl:for-each>

Keeping the xsl:for-each would, at least, allow you to sort the tracks in another order, if desired.

Tim C
Good catch on the sorting. I guess this is the best solution, due to the sorting.
ndim
I could have sworn I already tried this, but I guess not. This is exactly what I was looking for, though!
Jakob
A: 

I think using apply-templates and template modes is the cleaner solution:

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

  <xsl:template match="/">
    <body>
      <xsl:apply-templates select="album" mode="ol" />
    </body>
  </xsl:template>

  <xsl:template match="album" mode="ol">
    <ol>
      <xsl:apply-templates select="track" mode="li" />
    </ol>
  </xsl:template>

  <xsl:template match="track" mode="li">
    <li>
      <xsl:apply-templates select="." />
    </li>
  </xsl:template>

  <xsl:template match="track">
    <a href="{@id}">
      <xsl:value-of select="." />
    </a>
  </xsl:template>


</xsl:stylesheet>

results in:

<body>
  <ol>
    <li>
      <a href="shove">Somebody To Shove</a>
    </li>
    <li>
      <a href="gold">Black Gold</a>
    </li>
    <li>
      <a href="train">Runaway Train</a>
    </li>
  </ol>
</body>
Tomalak
A: 

The for-each statement changes the context node from album to track. The apply-templates call by default applies templates to the child nodes of the context node which in your case are the child nodes of the track element. Hence your template which matches 'track' never gets hit.

PJL