XSLT 1.0 doesn’t make it easy to pass lists, maps or arbitrary XML as template parameters. Fortunately most current transformer implementations support a solution out-of-the-box.
There are plenty of scenarios where it would be useful to supply a “dynamic” set of values or name-value pairs as a parameter into an XSL Stylesheet template.
Take, for example, converting disparate sources of stock price data into a common input format for another application. For sure, we could run a chain of transformations – a stylesheet which converts each input format into a generic, intermediate format and then a common second transformation which takes the intermediate format and renders the proper output. And, for sure, that’s probably what the purists would say is the way you should do it.
But we don’t live in Should Land, and sometimes the constraints of a tool-chain or execution environment might make that solution unworkable. Sometimes it would be nice to build a list or map as a stylesheet variable and pass it into a template to be processed. For that though, there are a couple of hurdles to jump.
Modelling lists and maps
Anyone who’s travelled any distance with XSLT will be painfully familiar with the limitations of stylesheet variables. It will come as no surprise then that they don’t readily support maps or arrays. But they support something just as good, and in many ways infinitely more flexible. They support XML.
We can model an array quite easily as an XML element containing a list of identically named elements:-
<xsl:variable name="myArray"> <array> <item>value1</item> <item>value2</item> <item>value3</item> </array> </xsl:variable>
A map is a little trickier but not much. We can model it as a list of elements each of which contain a name/value pair:-
<xsl:variable name="myMap"> <map> <mapEntry> <name>variable1</name> <value>value1</value> </mapEntry> <mapEntry> <name>variable2</name> <value>value2</value> </mapEntry> <mapEntry> <name>variable3</name> <value>value3</value> </mapEntry> </map> </xsl:variable>
Okay, so we’ve got a means of representing lists and maps in a stylesheet variable. Now, how do we process through them?
Simple right?
You might think that, since our variable contains XML we can just treat it as we would treat the XML in our source document, as in the following example:-
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template name="renderTable"> <xsl:param name="array"/> <table> <xsl:for-each select="$array//item"> <tr> <td><xsl:value-of select="."/></td> </tr> </xsl:for-each> </table> </xsl:template> <xsl:template match="/"> <xsl:variable name="myArray"> <array> <item>value1</item> <item>value2</item> <item>value3</item> </array> </xsl:variable> <xsl:call-template name="renderTable"> <xsl:with-param name="array" select="$myArray"/> </xsl:call-template> </xsl:template> </xsl:stylesheet>
Unfortunately, you’d be wrong:-
ERROR: 'Invalid conversion from 'com.sun.org.apache.xalan.internal.xsltc.dom.SAXImpl' to 'node-set'.'
The problem here is that the XSLT processor has absolutely no idea that our variable contains XML – it’s not been parsed like the source document has. And, unfortunately the for-each function is rather sniffy about its input being a properly parsed node-set.
But not difficult
What we need is a function that will take our “dynamic” XML list or map and turns it into a node set. Sadly XSLT 1.0 doesn’t offer one, but EXSLT does and it’s supported out-of-the-box by many standard XSLT processors. No funny settings to mess around with or libraries to add to your application, just a couple of tweaks to our stylesheet will do the job:-
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exslt="http://exslt.org/common"> <xsl:template name="renderTable"> <xsl:param name="array"/> <table> <xsl:for-each select="exslt:node-set($array)//item"> <tr> <td><xsl:value-of select="."/></td> </tr> </xsl:for-each> </table> </xsl:template> <xsl:template match="/"> <xsl:variable name="myArray"> <array> <item>value1</item> <item>value2</item> <item>value3</item> </array> </xsl:variable> <xsl:call-template name="renderTable"> <xsl:with-param name="array" select="$myArray"/> </xsl:call-template> </xsl:template> </xsl:stylesheet>
All we have to do is add the exslt namespace to our document and wrap our variable with a call to exslt:node-set() at the point we want to process it:-
<table> <tr> <td>value1</td> </tr> <tr> <td>value2</td> </tr> <tr> <td>value3</td> </tr> </table>
Finally, for completeness, here’s an example that works on our map of name/value pairs:-
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exslt="http://exslt.org/common"> <xsl:template name="renderTable"> <xsl:param name="entries"/> <table> <xsl:for-each select="exslt:node-set($entries)//mapEntry"> <tr> <td><xsl:value-of select="name"/></td> <td><xsl:value-of select="value"/></td> </tr> </xsl:for-each> </table> </xsl:template> <xsl:template match="/"> <xsl:variable name="myMap"> <map> <mapEntry> <name>variable1</name> <value>value1</value> </mapEntry> <mapEntry> <name>variable2</name> <value>value2</value> </mapEntry> <mapEntry> <name>variable3</name> <value>value3</value> </mapEntry> </map> </xsl:variable> <xsl:call-template name="renderTable"> <xsl:with-param name="entries" select="$myMap"/> </xsl:call-template> </xsl:template> </xsl:stylesheet>
Which, when executed, gives us the following:-
<table> <tr> <td>variable1</td> <td>value1</td> </tr> <tr> <td>variable2</td> <td>value2</td> </tr> <tr> <td>variable3</td> <td>value3</td> </tr> </table>
Nice, thanks for this !