As in other programming languages, it is often desirable to set up a variable whose value is reused in several places throughout a stylesheet. If the title of a book is displayed repeatedly, then it makes sense to store that title in a variable rather than scan through the XML data and locate the title repeatedly. It can also be beneficial to set up a variable once and pass it as a parameter to one or more templates. These templates often use <xsl:if> or <xsl:choose> to produce different content depending on the value of the parameter that was passed.
Variables in XSLT are defined with the <xsl:variable> element and can be global or local. A global variable is defined at the "top-level" of a stylesheet, which means that it is defined outside of any templates as a direct child of the <xsl:stylesheet> element. Top-level variables are visible throughout the entire stylesheet, even in templates that occur before the variable declaration.
The other place to define a variable is inside of a template. These variables are visible only to elements that follow the <xsl:variable> declaration within that template and to their descendants. The code in Example 3-2 showed this form of <xsl:variable> as a mechanism to define the font color.
Variables can be defined in one of three ways:
<xsl:variable name="homePage">index.html</xsl:variable> <xsl:variable name="lastPresident"select="president[position() = last( )]/name"/> <xsl:variable name="empty"/>
In the first example, the content of <xsl:variable> specifies the variable value. In the simple example listed here, the text index.html is assigned to the homePage variable. More complex content is certainly possible, as shown earlier in Example 3-2.
The second way to define a variable relies on the select attribute. The value is an XPath expression, so in this case we are selecting the name of the last president in the list.
Finally, a variable without a select attribute or content is bound to an empty string. The example shown in item 3 is equivalent to:
<xsl:variable name="empty" select="''"/>
To use a variable, refer to the variable name with a $ character. In the following example, an XPath location path is used to select the name of the last president. This text is then stored in the lastPresident variable:
<xsl:variable name="lastPresident" select="president[position() = last( )]/name"/>
Later in the same stylesheet, the lastPresident variable can be displayed using the following fragment of code:
<xsl:value-of select="$lastPresident"/>
Since the select attribute of <xsl:value-of> expects to see an XPath expression, $lastPresident is treated as something dynamic, rather than as static text. To use a variable within an HTML attribute value, however, you must use the attribute value template (AVT) syntax, placing braces around the variable reference:
<a href="{$homePage}">Click here to return to the home page...</a>
Without the braces, the variable would be misinterpreted as literal text rather than treated dynamically.
The primary limitation of variables is that they cannot be changed. It is impossible, for example, to use a variable as a counter in an <xsl:for-each> loop. This can be frustrating to programmers accustomed to variables that can be changed, but can often be overcome with some ingenuity. It usually comes down to passing a parameter to a template instead of using a global variable and then recursively calling the template again with an incremented parameter value. An example of this technique will be presented shortly.
Another XSLT trick involves combining the variable initialization with <xsl:choose>. Since variables cannot be changed, you cannot first declare a variable and then assign its value later on. The workaround is to place the variable definition as a child of <xsl:variable>, perhaps using <xsl:choose> as follows:
<xsl:variable name="midName"> <xsl:choose> <xsl:when test="middleName"> <xsl:value-of select="middleName"/> </xsl:when> <xsl:otherwise> <xsl:text> </xsl:text> </xsl:otherwise> </xsl:choose> </xsl:variable>
This code defines a variable called midName. If the <middleName> element is present, its value is assigned to midName. Otherwise, a blank space is assigned.
Up until this point, all of the templates have been tightly coupled to the actual data in the XML source. For example, the following template matches an <employee> element; therefore, <employee> must be contained within your XML data:
<xsl:template match="employee"> ...content, perhaps display the name and SSN for the employee </xsl:template>
But in many cases, you may wish to use this template for types of elements other than <employee>. In addition to <employee> elements, you may want to use this same code to output information for a <programmer> or <manager> element. In these circumstances, <xsl:call-template> can be used to explicitly invoke a template by name, rather than matching a pattern in the XML data. The template will have the following form:
<xsl:template name="formatSSN"> ...content </xsl:template>
This template will be used to support the following XML data, in which both <manager> and <programmer> elements have ssn attributes. Using a single named template avoids the necessity to write one template for <manager> and another for <programmer>. We will see an example XSLT stylesheet when we discuss parameters.
<?xml version="1.0" encoding="UTF-8"?> <team> <manager ssn="230568737"> <name>Aidan Burke</name> </manager> <programmer ssn="393776766"> <name>Jennifer Burke</name> </programmer> <programmer ssn="993885777"> <name>Bill Tellam</name> </programmer> </team>
It is difficult to use named templates without parameters, and parameters can also be used for regular templates. Parameters allow the same template to take on different behavior depending on data the caller provides, resulting in more reusable code fragments. In the case of a named template, parameters allow data such as a social security number to be passed into the template. Example 3-3 contains a complete stylesheet that demonstrates how to pass the ssn parameter into a named template.
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html"/> <xsl:template match="/"> <html> <body> <h3>Team Members</h3> <ul> <xsl:for-each select="team/manager|team/programmer"> <xsl:sort select="name"/> <li> <xsl:value-of select="name"/> <xsl:text>, ssn = </xsl:text> <xsl:call-template name="formatSSN"> <xsl:with-param name="ssn" select="@ssn"/> </xsl:call-template> </li> </xsl:for-each> </ul> </body> </html> </xsl:template> <!-- a named template that formats a 9 digit SSN by inserting '-' characters --> <xsl:template name="formatSSN"> <xsl:param name="ssn"/> <xsl:value-of select="substring($ssn, 1, 3)"/> <xsl:text>-</xsl:text> <xsl:value-of select="substring($ssn, 4, 2)"/> <xsl:text>-</xsl:text> <xsl:value-of select="substring($ssn, 6)"/> </xsl:template> </xsl:stylesheet>
This stylesheet displays the managers and programmers in a list, sorted by name. The <xsl:for-each> element selects the union of team/manager and team/programmer, so all of the managers and programmers are listed. The pipe operator (|) computes the union of its two operands:
<xsl:for-each select="team/manager|team/programmer">
For each manager or programmer, the content of the <name> element is printed, followed by the value of the ssn attribute, which is passed as a parameter to the formatSSN template. Passing one or more parameters is accomplished by adding <xsl:with-param> as a child of <xsl:call-template> . To pass additional parameters, simply list additional <xsl:with-param> elements, all as children of <xsl:call-template>.
At the receiving end, <xsl:param> is used as follows:
<xsl:template name="formatSSN"> <xsl:param name="ssn"/> ...
In this case, the value of the ssn parameter defaults to an empty string if it is not passed. In order to specify a default value for a parameter, use the select attribute. In the following example, the zeros are in apostrophes in order to treat the default value as a string rather than as an XPath expression:
<xsl:param name="ssn" select="'000000000'"/>
Within the formatSSN template, you can see that the substring( ) function selects portions of the social security number string. More details on substring( ) and other string-formatting functions are discussed later in this chapter.
Unfortunately, there is no standard way to increment a variable in XSLT. Once a variable has been defined, it cannot be changed. This is comparable to a final field in Java. In some circumstances, however, recursion combined with template parameters can achieve similar results. The XML shown in Example 3-4 will be used to illustrate one such approach.
<?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet type="text/xsl" href="familyTree.xslt"?> <person name="Otto"> <person name="Sandra"> <person name="Jeremy"> <person name="Eliana"/> </person> <person name="Eric"> <person name="Aidan"/> </person> <person name="Philip"> <person name="Alex"/> <person name="Andy"/> </person> </person> </person>
As you can see, the XML is structured recursively. Each <person> element can contain any number of <person> children, which in turn can contain additional <person> children. This is certainly a simplified family tree, but this recursive pattern does occur in many XML documents. When displaying this family tree, it is desirable to indent the text according to the ancestry. Otto would be at the root, Sandra would be indented by one space, and her children would be indented by an additional space. This gives a visual indication of the relationships between the people. For example:
Otto Sandra Jeremy Eliana Eric Aidan Philip Alex Andy
The XSLT stylesheet that produces this output is shown in Example 3-5.
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html"/> <!-- processing begins here --> <xsl:template match="/"> <html> <body> <!-- select the top level person --> <xsl:apply-templates select="person"> <xsl:with-param name="level" select="'0'"/> </xsl:apply-templates> </body> </html> </xsl:template> <!-- Output information for a person and recursively select all children. --> <xsl:template match="person"> <xsl:param name="level"/> <!-- indent according to the level --> <div style="text-indent:{$level}em"> <xsl:value-of select="@name"/> </div> <!-- recursively select children, incrementing the level --> <xsl:apply-templates select="person"> <xsl:with-param name="level" select="$level + 1"/> </xsl:apply-templates> </xsl:template> </xsl:stylesheet>
As usual, this stylesheet begins by matching the document root and outputting a basic HTML document. It then selects the root <person> element, passing level=0 as the parameter to the template that matches person:
<xsl:apply-templates select="person"> <xsl:with-param name="level" select="'0'"/> </xsl:apply-templates>
The person template uses an HTML <div> tag to display each person's name on a new line and specifies a text indent in em s. In Cascading Style Sheets, one em is supposed to be equal to the width of the lowercase letter m in the current font. Finally, the person template is invoked recursively, passing in $level + 1 as the parameter. Although this does not increment an existing variable, it does pass a new local variable to the template with a larger value than before. Other than tricks with recursive processing, there is really no way to increment the values of variables in XSLT.
The final variation on templates is that of the mode. This feature is similar to parameters but a little simpler, sometimes resulting in cleaner code. Modes make it possible for multiple templates to match the same pattern, each using a different mode of operation. One template may display data in verbose mode, while another may display the same data in abbreviated mode. There are no predefined modes; you make them up. The mode attribute looks like this:
<xsl:template match="name" mode="verbose"> ...display the full name </xsl:template> <xsl:template match="name" mode="abbreviated"> ...omit the middle name </xsl:template>
In order to instantiate the appropriate template, a mode attribute must be added to <xsl:apply-templates> as follows:
<xsl:apply-templates select="president/name" mode="verbose"/>
If the mode attribute is omitted, then the processor searches for a matching template that does not have a mode. In the code shown here, both templates have modes, so you must include a mode on <xsl:apply-templates> in order for one of your templates to be instantiated.
A complete stylesheet is shown in Example 3-6. In this example, the name of a president may occur inside either a table or a list. Instead of passing a parameter to the president template, two modes of operation are defined. In table mode, the template displays the name as a row in a table. In list mode, the name is displayed as an HTML list item.
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html"/> <!-- ** Demonstrates how to use template modes --> <xsl:template match="/"> <html> <body> <h2>Presidents in an HTML Table</h2> <table border="1"> <tr> <th>Last Name</th> <th>First Name</th> </tr> <xsl:apply-templates select="//president" mode="table"/> </table> <h2>Presidents in an Unordered List</h2> <ul> <xsl:apply-templates select="//president" mode="list"/> </ul> </body> </html> </xsl:template> <!-- ** Display a president's name as a table row --> <xsl:template match="president" mode="table"> <tr> <td> <xsl:value-of select="name/last"/> </td> <td> <xsl:value-of select="name/first"/> </td> </tr> </xsl:template> <!-- ** Display a president's name as a list item --> <xsl:template match="president" mode="list"> <li> <xsl:value-of select="name/last"/> <xsl:text>, </xsl:text> <xsl:value-of select="name/first"/> </li> </xsl:template> </xsl:stylesheet>
Sorting through all of the possible variations of <xsl:template> is a seemingly difficult task, but we have really only covered three attributes:
The only attribute we have not discussed in detail is priority, which is used to resolve conflicts when more than one template matches. The XSLT specification defines a very specific set of steps for processors to follow when more than one template rule matches.[11] From a code maintenance perspective, it is a good idea to avoid conflicting template rules within a stylesheet. When combining multiple stylesheets, however, you may find yourself with conflicting template rules. In these cases, specifying a higher numeric priority for one of the conflicting templates can resolve the problem. Table 3-1 provides a few summarized examples of the various forms of <xsl:template>.
[11] See section 5.5 of the XSLT specification at http://www.w3.org/TR/xslt.
Template example |
Notes |
---|---|
<xsl:template match="president"> ... </xsl:template> |
Matches president nodes in the source XML document |
<xsl:template name="formatName"> <xsl:param name="style"/> ... </xsl:template> |
Defines a named template; used in conjunction with <xsl:call-template> and <xsl:with-param> |
<xsl:template match="customer" mode="myModeName"> ... </xsl:template> |
Matches customer nodes when <xsl:apply-templates> also uses mode="myModeName" |
Copyright © 2002 O'Reilly & Associates. All rights reserved.