XML.com: XML From the Inside Out
oreilly.comSafari Bookshelf.Conferences.

advertisement

Setting and Using Variables and Parameters

February 07, 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:

< div class="itemizedlist">
  • The value-length variable stores the length of the color name using the string-length() function.

  • The padding variable stores the number of spaces required to right-align the color name. It does this by subtracting the value of the local valueLength variable from the global fieldWidth variable.

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.

Pages: 1, 2

Next Pagearrow