Setting and Using Variables and Parameters
February 7, 2001
A variable in XSLT has more in common with a variable in algebra than with a variable in a typical programming language. It's a name that represents a value and, within a particular application of a template, it will never represent any other value -- it can't be reset using anything described in the XSLT Recommendation. (Some XSLT processors offer a special extension function to allow the resetting of variables.)
XSLT variables actually have a lot more in common with constants in many programming languages and are used for a similar purpose. If you use the same value multiple times in your stylesheet, and there's a possibility that you'll have to change them all to a different value, it's better to assign that value to a variable and use references to the variable instead. Then, if you need to change the value when re-using the stylesheet, you only change the value assigned in the creation of that variable.
For example, imagine that we want to turn this XML
<wine grape="Cabernet"> <winery>Duckpond</winery> <product>Merit Selection</product> <year>1996</year> <price>11.99</price> </wine>
into this HTML
<b><font size="10pt">Duckpond Cabernet</font></b><br> <i><font size="10pt">Merit Selection</font></i><br> <font size="10pt">1996</font><br> <font size="10pt">11.99</font><br>
The following templates would accomplish this (all file names refer to files in this zip file),
<!-- xq340.xsl: converts xq338.xml into xq339.html --> <xsl:template match="winery"> <b><font size="10pt"><xsl:apply-templates/> <xsl:text> </xsl:text> <xsl:value-of select="../@grape"/></font></b><br/> </xsl:template> <xsl:template match="product"> <i><font size="10pt"><xsl:apply-templates/></font></i><br/> </xsl:template> <xsl:template match="year | price"> <font size="10pt"><xsl:apply-templates/></font><br/> </xsl:template>
but if you want to change the three font elements' size attribute to "12pt", it would be too easy to miss one -- especially if the template rules weren't next to each other in the stylesheet. The solution is to use a variable to represent this size value:
<!-- xq341.xsl: converts xq338.xml into xq339.html --> <xsl:variable name="bodyTextSize">10pt</xsl:variable> <xsl:template match="winery"> <b><font size="{$bodyTextSize}"><xsl:apply-templates/> <xsl:text> </xsl:text> <xsl:value-of select="../@grape"/></font></b><br/> </xsl:template> <xsl:template match="product"> <i><font size="{$bodyTextSize}"> <xsl:apply-templates/></font></i><br/> </xsl:template> <xsl:template match="year | price"> <font size="{$bodyTextSize}"><xsl:apply-templates/></font><br/> </xsl:template>
When referencing a variable or parameter from a literal result element's attribute, you want the XSLT processor to plug in the variable's value. You don't want a dollar sign followed by the variable's name at that point in the template. To do this, put the variable inside curly braces to make it an attribute value template. To plug a variable's value into the content of a result tree element, instead of an attribute value, use an xsl:value-of instruction.
In the example above, if the $bodyTextSize variables were not enclosed by curly braces, each font start-tag in the result would have looked like this: <font size="$bodyTextSize">.
The xsl:variable instruction creates a variable. Its name attribute identifies the variable's name, and the value can be specified either as the xsl:variable element's contents (like the "10pt" in the example) or as the value of an optional select attribute in the xsl:variable element's start-tag.
The value of the select attribute must be an expression. This offers two nice advantages:
-
It shows that the xsl:variable element isn't quite as limited as the constants used by popular programming languages because the variable's value doesn't need to be hardcoded when the stylesheet is written.
-
The attribute value doesn't need curly braces to tell the XSLT processor "this is an attribute value template, evaluate it as an expression," because it always evaluates an xsl:variable element's select attribute value as an expression.
The two xsl:variable elements below have the same effect as the one in the example above: they set the bodyTextSize variable to a value of "10pt". The bodyTextSize variable has its value assigned in a select attribute instead of in its element content; the value assigned will be the return value of a concat function that concatenates the string "pt" to the result of adding $baseFontSize+2. What's $baseFontSize? It's another variable, which is defined above the bodyTextSize variable's xsl:variable element. That value of "8" is added to 2 and concatenated to "pt" to create a value of "10pt" for the bodyTextSize variable, which can then be used just like the bodyTextSize variable in the previous example.
<!-- xq342.xsl: converts xq338.xml into xq339.html --> <xsl:variable name="baseFontSize" select="8"/> <xsl:variable name="bodyTextSize" select="concat($baseFontSize+2,'pt')"/>
The example above demonstrates some of the options available when using an expression in the select attribute to assign a variable's value. The second xsl:variable element references another variable, does some math, and makes a function call. Variables aren't as limited as many XSLT newcomers might think.
It also demonstrates another nice feature of variables: they don't have to be strings. Once baseFontSize is set to "8", the select value of the bodyTextSize variable's xsl:variable element adds "2" to it and comes up with 10. If the XSLT processor had treated these number as strings, putting "8" and "2" together would get us "82". Instead, the XSLT processor treats the baseFontSize variable as a number. It can treat a variable as any type of object that can be returned by an XSLT expression: a string, a number, a boolean value, or a node set. If an XSLT variable has a value assigned by an xsl:variable element's contents and by a select attribute, the XSLT processor uses the one in the select attribute.
The examples above show "top-level" variables. They're defined with xsl:variable elements that are children of the main xsl:stylesheet element, making them global variables that can be referenced anywhere in the stylesheet.
Variables can be "local" as well -- that is, defined inside of a template rule and only available for use within that template rule. For example, the following templates have the same result as the ones in the examples above except that the font start-tag before the result winery element's content has a value of "12pt" in its size attribute instead of "10pt".
<!-- xq343.xsl: converts xq338.xml into xq344.html --> <xsl:template match="wine"> <xsl:variable name="bodyTextSize">10pt</xsl:variable> <xsl:apply-templates select="winery"/> <i><font size="{$bodyTextSize}"> <xsl:apply-templates select="product"/> </font></i><br/> <font size="{$bodyTextSize}"><xsl:apply-templates select="year"/> </font><br/> <font size="{$bodyTextSize}"><xsl:apply-templates select="price"/> </font><br/> </xsl:template> <xsl:template match="winery"> <xsl:variable name="bodyTextSize">12pt</xsl:variable> <b><font size="{$bodyTextSize}"><xsl:apply-templates/> <xsl:text> </xsl:text> <xsl:value-of select="../@grape"/></font></b><br/> </xsl:template>
The way these templates assign these size values is different. Instead of one global bodyTextSize variable to use throughout the stylesheet, the two template rules each have their own bodyTextSize variables declared between their xsl:template tags. The first one sets bodyTextSize to a value of "10pt", and that's what gets plugged into the size attribute values for the font tags that start the product, year, and price elements. The second template sets bodyTextSize to "12pt", so the winery and grape element contents copied to the result tree by that template start with font tags that have a size value of "12pt":
<b><font size="12pt">Duckpond Cabernet</font></b><br> <i><font size="10pt">Merit Selection</font></i><br> <font size="10pt">1996</font><br> <font size="10pt">11.99</font><br>
That's just a toy example. The next stylesheet uses a selection of the string manipulation functions available in XSLT to right align the result tree versions of the color elements in this document.
<test> <color>red</color> <color>blue</color> <color>yellow</color> </test>
The fieldWidth global variable stores the desired column width; the goal is to add spaces before each color value so that the spaces plus the color name add up to this value.
<!-- xq346.xsl: converts xq345.xml into xq478.xml --> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output omit-xml-declaration="yes"/> <xsl:variable name="fieldWidth">12</xsl:variable> <xsl:template match="color"> <xsl:variable name="valueLength" select="string-length(.)"/> <xsl:variable name="padding" select="$fieldWidth - $valueLength"/> <xsl:value-of select="substring(' ',1,$padding)"/> <xsl:value-of select="."/> </xsl:template> </xsl:stylesheet>
The color element's template rule has two local variables:
Once the template rule knows how much space it needs to add to the result tree before adding the color element's contents, it adds that many spaces by using the substring() function to pull that many spaces out of a string of spaces passed to the substring() function as its first argument.
In the result, "red" has nine spaces before it, "blue" has eight, and "yellow" has six:
red blue yellow
I could have done this without any local variables; in fact, when I originally wrote this stylesheet, I did without them. As with any programming language, using local variables made it easier to break down the problem into pieces and to make the relationship of those pieces easier to understand.
Parameters
The xsl:param instruction is just like xsl:variable with one important difference: its value is only treated as a default value and can be overridden at runtime. All the stylesheet examples up to this point would work the same way if you substituted xsl:param elements for their xsl:variable elements, but you would have the option of overriding the values when calling their templates.
For example, let's take one of the earlier examples and make the substitution. Here is how it looks as a complete stylesheet:
<!-- xq348.xsl: converts xq338.xml into xq339.html. Compare this with xq340.xsl. --> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="html"/> <xsl:param name="bodyTextSize">10pt</xsl:param> <xsl:template match="winery"> <b><font size="{$bodyTextSize}"><xsl:apply-templates/> <xsl:text> </xsl:text> <xsl:value-of select="../@grape"/></font></b><br/> </xsl:template> <xsl:template match="product"> <i><font size="{$bodyTextSize}"> <xsl:apply-templates/></font></i><br/> </xsl:template> <xsl:template match="year | price"> <font size="{$bodyTextSize}"><xsl:apply-templates/></font><br/> </xsl:template> </xsl:stylesheet>
If we run it as shown with the same source document, it produces the same result as the previous section's version that used xsl:variable instead of xsl:param:
<b><font size="10pt">Duckpond Cabernet</font></b><br/> <i><font size="10pt">Merit Selection</font></i><br/> <font size="10pt">1996</font><br/> <font size="10pt">11.99</font><br/>
However, if we pass the stylesheet a value of "8pt" to use for bodyTextSize, it substitutes this new value for all uses of this parameter:
<b><font size="8pt">Duckpond Cabernet</font></b><br/> <i><font size="8pt">Merit Selection</font></i><br/> <font size="8pt">1996</font><br/> <font size="8pt">11.99</font><br/>
Of course, I'm skimming over one important detail here: how do you pass the alternative value for the parameter to the stylesheet? The XSLT Recommendation doesn't tell us. In fact, it deliberately tells us that it's not going to tell us. Just as the W3C's XSL Working Group wanted to leave the potential methods for giving input to and getting output from an XSLT processor as open as possible, they also didn't want to limit how the processors will be told a new value for a global parameter setting. (As we'll see, not all parameters are global like the bodyTextSize one above; they can also be local to template rules.) So, it's up the particular XSLT processor's designer. To pass the new value of "8pt" to the stylesheet when using the Saxon XSLT processor, the command line might look like this:
java com.icl.saxon.StyleSheet -x org.apache.xerces.parsers.SAXParser -y org.apache.xerces.parsers.SAXParser xq338.xml xq348.xsl bodyTextSize=8pt
(It's actually one command split over three lines to fit on the page here. When really using Saxon or any other Java-based XSLT processor, it makes your life easier to store everything before the "xq338.xml" in that command line in a Windows batch file, a UNIX shell script, or your operating system's equivalent. Then you can pass it the important parameters each time you run it with no need to type the full Java library names for the XSLT processor and XML parser.)
The only difference between applying the xq348.xsl stylesheet to the xq338.xml document this way and running it with the bodyTextSize default value is the addition of the "bodyTextSize=8pt" part at the end. Other XSLT processors may require a different syntax when passing a new parameter value along from the command line, but they would still create the same result when using this stylesheet and input.
Local parameters are even more useful in template rules than XSLT local variables are, because the flexibility of passing one or more values to a template lets that template adapt to different situations. Named templates that don't take advantage of this can still operate as functions or subroutines, but when you use named templates that do, you can start treating XSLT like a real programming language. For example, the ability of named templates to call themselves with parameters makes recursion and all the power associated with it possible.
How we pass a new value to a template rule's local parameter isn't quite the open question that it is with global parameters because XSLT provides the xsl:with-param instruction for just this purpose. You can use this element in an xsl:apply-templates element to assign a new value to a parameter in a template being applied, but it's more commonly used when calling a named template with the xsl:call-template instruction. For example, the first template rule in the following stylesheet has a name attribute and not a match attribute. Instead of the XSLT processor looking for nodes where it can apply this template, the processor will wait until the template is explicitly called with an xsl:call-template instruction.
<!-- xq352.xsl: converts xq353.xml into xq354.html --> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template name="titles"> <xsl:param name="headerElement">h4</xsl:param> <xsl:element name="{$headerElement}"> <xsl:apply-templates/> </xsl:element> </xsl:template> <xsl:template match="chapter/title"> <xsl:call-template name="titles"> <xsl:with-param name="headerElement">h1</xsl:with-param> </xsl:call-template> </xsl:template> <xsl:template match="section/title"> <xsl:call-template name="titles"> <xsl:with-param name="headerElement" select="'h2'"/> </xsl:call-template> </xsl:template> <xsl:template match="para"> <p><xsl:apply-templates/></p> </xsl:template> <xsl:template match="chapter"> <html><body><xsl:apply-templates/></body></html> </xsl:template> </xsl:stylesheet>
The second and third template rules, which have match patterns of "chapter/title" and "section/title", call the first template by its name of "titles" using xsl:call-templates elements. These xsl:call-templates elements don't need any children, but they have them here: xsl:with-param elements to pass parameter values to the named templates. The "titles" template rule will use these values to override the default value of "h4" when it's called. The with-param instruction in the "chapter/title" template rule is saying "pass along the value 'h1' for the headerElement parameter", and the one in the "section/title" template rule is passing the value 'h2'. For this input document,
<chapter><title>Chapter 1</title> <para>Then with expanded wings he steers his flight</para> <para author="ar">Aloft, incumbent on the dusky Air</para> <section><title>Chapter 1, Section 1</title> <para>That felt unusual weight, till on dry Land</para> <para>He lights, if it were Land that ever burned</para> </section> </chapter>
the "titles" template is called when the XSLT processor finds each of the two title element nodes. The "titles" named template uses the passed values to create the h1 and h2 elements in the result:
<html> <body> <h1>Chapter 1</h1> <p>Then with expanded wings he steers his flight</p> <p>Aloft, incumbent on the dusky Air</p> <h2>Chapter 1, Section 1</h2> <p>That felt unusual weight, till on dry Land</p> <p>He lights, if it were Land that ever burned</p> </body> </html>
Just as an xsl:param element can specify its default value as either content between its start- and end-tags or as the value of a select attribute, the xsl:with-param element can indicate the value to pass using either method. The two xsl:with-param elements in the example above use the two different methods to demonstrate this.
The XSLT processor evaluates the xsl:with-param element's select value as an expression just like it does with the xsl:param element's select attribute value. This is why the third template above needs single quotation marks around the value of "h2" even though it's also enclosed by double quotation marks. The double quotation marks serve a different purpose: to tell the XML parser where the select attribute value starts and ends. The inner single quotation marks tell the XSLT processor that the value is a literal string and not an expression to evaluate.
Note The name value specified in the xsl:call-template element cannot contain a variable reference. For example, if you declared a variable called templateName and stored the string "title" there, an xsl:call-template start-tag of <xsl:call-template name="$templateName"> would not work in the previous example's "chapter/title" or "section/title" template rules. |
You don't have to specify a hardcoded string like "h1" or "h2" as the value of the parameter to pass in an xsl:with-param element. You can put the result of one or more functions in there, or even an XPath expression that retrieves a value from somewhere in the document (or even from another document, using the document() function). This ability opens up an even broader range of possibilities for how you use parameter passing in XSLT.