Riding an XSLT Bike
I might be the only one but I've always found that XSLT is nothing like riding a bike; either I've been doing it lately and it's my friend or I haven't and it makes my head hurt.
I'm kinda into XML as a data transport format (JSON is cool too) right now. We've been writing transforms to produce some user friendly windows into our data warehouse CI process.
Not for the first time in my life, I find my self constrained to v1.0 but wanting to do some stuff that's much easier in v2.0. Hopefully the example below will help someone out one day (even if it's me in a year or two)...
I want to create a simple table from the following XML:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<things> | |
<thing value="Row 1" anotherValue="Hello Gary! Checkout [[myfile.txt]]"></thing> | |
<thing value="Row 2" anotherValue="Hello Bob! Checkout [[myfile.txt]]"></thing> | |
<thing value="Row 3" anotherValue="Hello Tracey! Checkout [[myotherfile.txt]]"></thing> | |
</things> |
I want to replace "Gary" with "John" and I want to inject links to the (conveniently) wrapped file names.
Here's my transform:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"> | |
<xsl:output method="html" indent="yes" encoding="US-ASCII" doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN"/> | |
<xsl:template match="things"> | |
<html> | |
<head> | |
<title>Title</title> | |
</head> | |
<body onload=""> | |
<xsl:call-template name="pageHeader"/> | |
</body> | |
</html> | |
</xsl:template> | |
<xsl:template name="pageHeader"> | |
<h1>Heading</h1> | |
<table> | |
<tr valign="top"> | |
<th>Column A</th> | |
<th>Column B</th> | |
</tr> | |
<xsl:for-each select="thing"> | |
<tr valign="top"> | |
<td><xsl:value-of select="@value"/></td> | |
<td> | |
<xsl:call-template name="tidyUp"> | |
<xsl:with-param name="text" select="@anotherValue"/> | |
</xsl:call-template> | |
</td> | |
</tr> | |
</xsl:for-each> | |
</table> | |
</xsl:template> | |
<xsl:template name="tidyUp"> | |
<xsl:param name="text"/> | |
<xsl:variable name="replacedName"> | |
<xsl:call-template name="replaceThisWithThat"> | |
<xsl:with-param name="text" select="$text"/> | |
<xsl:with-param name="replaceThis" select="'Gary'"/> | |
<xsl:with-param name="withThat" select="'John'"/> | |
</xsl:call-template> | |
</xsl:variable> | |
<xsl:variable name="injectedLinks"> | |
<xsl:call-template name="injectLinks"> | |
<xsl:with-param name="text" select="$replacedName"/> | |
</xsl:call-template> | |
</xsl:variable> | |
<xsl:copy-of select="$injectedLinks"/> | |
</xsl:template> | |
<xsl:template name="replaceThisWithThat"> | |
<xsl:param name="text"/> | |
<xsl:param name="replaceThis"/> | |
<xsl:param name="withThat"/> | |
<xsl:choose> | |
<xsl:when test="contains($text, $replaceThis)"> | |
<xsl:value-of select="substring-before($text, $replaceThis)"/> | |
<xsl:value-of select="$withThat"/> | |
<xsl:call-template name="replaceThisWithThat"> | |
<xsl:with-param name="text" select="substring-after($text, $replaceThis)"/> | |
<xsl:with-param name="replaceThis" select="$replaceThis"/> | |
<xsl:with-param name="withThat" select="$withThat"/> | |
</xsl:call-template> | |
</xsl:when> | |
<xsl:otherwise> | |
<xsl:value-of select="$text"/> | |
</xsl:otherwise> | |
</xsl:choose> | |
</xsl:template> | |
<xsl:template name="injectLinks"> | |
<xsl:param name="text"/> | |
<xsl:choose> | |
<xsl:when test="contains($text, '[[') and contains($text, ']]')"> | |
<xsl:variable name="linkPath"> | |
<xsl:copy-of select="substring-before(substring-after($text,'[['),']]')"/> | |
</xsl:variable> | |
<xsl:value-of select="substring-before($text, '[[')"/> | |
<a target="_blank"><xsl:attribute name="href">http://server/<xsl:copy-of select="$linkPath"/></xsl:attribute><xsl:copy-of select="$linkPath"/></a> | |
<xsl:call-template name="injectLinks"> | |
<xsl:with-param name="text" select="substring-after($text, ']]')"/> | |
</xsl:call-template> | |
</xsl:when> | |
<xsl:otherwise> | |
<xsl:value-of select="$text"/> | |
</xsl:otherwise> | |
</xsl:choose> | |
</xsl:template> | |
</xsl:stylesheet> |
Here's (a screenshot of) the output:

The following are worth pointing out:
1. The use of "copy-of" (instead of "value-of") whenever my "thing" contains markup that I want to keep as markup
2. The use of "xsl:attribute name=" to allow me to inject values into node attributes
3. The use of recursion for multiple replacements
4. The use of variables (in "tidyUp") to stack transformations