Dougal Campbell's geek ramblings

WordPress, web development, and world domination.

XSLT-Fu

I’ve recently started learning XSLT. The basics aren’t too hard, but as usual, I’ve been trying to move faster than I can absorb things. I had a particularly tricky transform I needed to do, but I couldn’t figure how how to accomplish it for a day or two.

Here’s a simplified fragment of the XML I’m starting from:


<doc>
 <projects>
  <project id="P1">Playground cleanup</project>
  <project id="P2">Fall Festival</project>
  <project id="P3">Zoo Trip</project>
 </projects>
 <volunteers>
  <person id="V1">
   <vol proj="P1">Alice</vol>
   <vol proj="P3">Alice</vol>
  </person>
  <person id="V2">
   <vol proj="P2">Bob</vol>
   <vol proj="P3">Bob</vol>
  </person>
 </volunteers>
</doc>

Yes, I know the semantics of the volunteers elements are wacky. No, I can’t change the format. Ignore that, it’s just an example. The important part is that I want to transform the above into an HTML table like so:

Playground cleanup Fall Festival Zoo trip
Alice   Alice
  Bob Bob

The tricky part here is: 1) I have an element that determines the number of columns in the table. 2) I have separate elements defining the rows (<person>), and the cells within each row (<vol>). 3) for each row, not every column is represented, I must match them up using the proj attribute.

I puzzled over the documentation for keys and for-each for a while, and finally came up with a way to do it (I eventually abandoned the idea of using keys). It’s kind of brute-force, and there may be a more elegant solution, but this works for me.


<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
  
<xsl:template match="doc">
   <table>
    <tr>
     <xsl:apply-templates select="projects"/>
    </tr>
     <xsl:apply-templates select="volunteers"/>
   </table>
</xsl:template>

<xsl:template match="projects">
 <xsl:apply-templates select="project"/>
</xsl:template>

<xsl:template match="project">
 <th><xsl:apply-templates/></th>
</xsl:template>

<xsl:template match="volunteers">
 <xsl:apply-templates select="person"/>
</xsl:template>

<!--
 Here's where we do some magic.
 -->
<xsl:template match="person">
 <tr>
  <xsl:call-template name="make-row"/>
 </tr>
</xsl:template>

<!--
 make-row iterates through the columns/col elements every time
 it matches a row element. For each iteration, it passes the current
 col-id and row-id to make-cell. This allows make-cell to lookup the
 appropriate value for each possible cell in the output table.
 -->
<xsl:template name="make-row">
 <xsl:variable name="row-id" select="@id"/>
 <xsl:for-each select="//projects/project">
  <xsl:call-template name="make-cell">
   <xsl:with-param name="col-id" select="@id"/>
   <xsl:with-param name="row-id" select="$row-id"/>
  </xsl:call-template>
 </xsl:for-each>
</xsl:template>

<!--
 Brute-force lookup of a value for the appropriate row and column.
 -->
<xsl:template name="make-cell">
 <xsl:param name="col-id"/>
 <xsl:param name="row-id"/>
 <xsl:variable name="cell-value"
   select="//volunteers/person[@id = $row-id]/vol[@proj = $col-id]"/>
 <td>
 <!-- 
  This choose is so we can put a non-breakable space inside the
  empty table cells. This keeps some browsers from doing odd
  things when displaying empty cells.
 -->
  <xsl:choose>
   <xsl:when test="string($cell-value) = ''">
    <!-- 
     prevent double escaping of the amp entity, so we 
     can output the nbsp
     -->
    <xsl:text disable-output-escaping="yes">&amp;nbsp;</xsl:text>
   </xsl:when>
   <xsl:otherwise>
    <xsl:value-of select="$cell-value"/>
   </xsl:otherwise>
  </xsl:choose>
 </td>
</xsl:template>

</xsl:stylesheet>

I just wanted to document this for future reference, and leave it out there in case anybody else is looking for an example of how to do something similar. The main trick is using the variables to keep track of which table element we need to look up as we iterate through the rows and columns.

About Dougal Campbell

Dougal is a web developer, and a "Developer Emeritus" for the WordPress platform. When he's not coding PHP, Perl, CSS, JavaScript, or whatnot, he spends time with his wife, three children, a dog, and a cat in their Atlanta area home.
This entry was posted in Tech, Work. Bookmark the permalink.

3 Responses to XSLT-Fu

  1. Pingback: Citypress CVS

Leave a Reply