The XSLT Language
Now that you've seen the transformation process and have been introduced to what an XSLT document looks like, let's break the different parts used in the document into individual pieces. First up: the XSLT document root element.
The XSLT Document Root Element
Looking back at Listing 7.2, you'll notice that the document follows all the rules specified in the XML specification described in Chapter 2, "XML for ASP.NET Basics." The case of each opening tag matches the case of the closing tag, all attributes are quoted, all tags are closed, and so on. XSLT documents are, in fact, well-formed XML documents. As a result, the first line of each document should contain the XML declaration. Although this line is optional, it's essential that you get into the practice of using it, especially because new versions of the XML specification will certainly be coming in the future.
Following the XML declaration, one of two elements specific to the XSLT language can be used for the document's root node. These elements are the following:
<xsl:stylesheet>
-
<xsl:transform>
Although you can use either element as the root of an XSLT document, the samples that follow throughout this chapter use the xsl:stylesheet element. You can certainly substitute the xsl:transform element instead if you feel more comfortable using it.
Two different items must also be included for an XSLT document to follow the guidelines found in the XSLT specification. These are a local namespace declaration as well as an attribute named version. The inclusion of the xsl:stylesheet element, the namespace declaration, and the version attribute are shown next:
<?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" > <!-- The bulk of the XSLT document goes here --> </xsl:stylesheet>
The namespace URI (http://www.w3.org/1999/XSL/Transform) must be listed exactly as shown, and the version attribute must have a value of 1.0 for the document to be conformant with the November 1999 XSLT specification. As different versions of the specification are released, this version number can be changed, depending on what features the XSLT document uses. Failure to list these parts correctly will result in an error being returned by the XSLT processor.
NOTE
XSLT version 1.1 was in Working Draft at the time this section was written. XSLT style sheets that use new features found in this version will need to let the XSLT processor know by changing the version attribute to 1.1.
XSLT Elements
If you've had the opportunity to work with HTML in the past, you're already aware of how elements are used to perform specific tasks. For example, the <table> element can be used along with the <tr> and <td> elements to construct a table for display in a browser. The <img> element can be used when an image needs to be displayed, and the <form> element can be used as a container for different form elements such as text boxes and radio buttons. Each of these elements have a specific purpose and when appropriate, can contain supporting child elements.
The XSLT version 1.0 specification lists several elements that can be used to transform XML documents. These elements can be used in a variety of ways, including determining the output format, performing if/then type logic, looping, and writing out data within a node contained in the XML document to the result tree structure. An XSLT element is distinguished from other elements that may be within an XSLT document by its association with a namespace that defines a URI of http://www.w3.org/1999/XSL/Transform. Declaring this namespace on the XSLT root element (xsl:stylesheet or xsl:transform) was shown in the previous section.
Table 7.1 contains a listing of all potential elements in version 1.0 of the XSLT specification. Notice that each element is prefixed by the xsl namespace.
Table 7.1 XSLT Elements
XSLT Element |
Description |
xsl:apply-imports |
Used in conjunction with imported style sheets to override templates within the source style sheet. Calls to xsl:apply-imports cause an imported template with lower precedence to be invoked instead of the source style sheet template with higher precedence. |
xsl:apply-templates |
When xsl:apply-templates is used, the XSLT processor finds the appropriate template to apply, based on the type and context of each selected node. |
xsl:attribute |
Creates an attribute node that is attached to an element that appears in the output structure. |
xsl:attribute-set |
Used when a commonly defined set of attributes will be applied to different elements in the style sheet. This is similar to named styles in CSS. |
xsl:call-template |
Used when processing is directed to a specific template. The template is identified by name. |
xsl:choose |
Used along with the xsl:otherwise and xsl:when elements to provide conditional testing. Similar to using a switch statement in C# or Select Case statement in VB.NET. |
xsl:comment |
Writes a comment to the output structure. |
xsl:copy |
Copies the current node from the source document to the result tree. The current node's children are not copied. |
xsl:copy-of |
Used to copy a result-tree fragment or node-set into the result tree. This performs a "deep copy," meaning that all descendants of the current node are copied to the result tree. |
xsl:decimal-format |
Declares a decimal-format that is used when converting numbers into strings with the format-number() function. |
xsl:element |
Creates an element with the specified name in the output structure. |
xsl:fallback |
Provides an alternative (or fallback) template when specific functionality is not supported by the XSLT processor being used for the transformation. This element provides greater flexibility during transformations as new XSLT versions come out in the future. |
xsl:for-each |
Iterates over nodes in a selected node-set and applies a template repeatedly. |
xsl:if |
Used to wrap a template body that will be used only when the if statement test returns a true value. |
xsl:import |
Allows an external XSLT style sheet to be imported into the current style sheet. The XSLT processor will give a lower precedence to imported templates as compared to templates in the original XSLT style sheet. |
xsl:include |
Allows for the inclusion of another XSLT style sheet into the current style sheet. The XSLT processor gives the same precedence to the included templates as templates in the original XSLT style sheet. |
xsl:key |
Declares a named key and is used in conjunction with the key() function in XPath expressions. |
xsl:message |
Used to output a text message and optionally terminate style sheet execution. |
xsl:namespace-alias |
Used to map a prefix associated with a given namespace to another prefix. This can be useful when a style sheet generates another style sheet. |
xsl:number |
Used to format a number before adding it to the result tree or to provide a sequential number to the current node. |
xsl:otherwise |
Used with the xsl:choose and xsl:when elements to perform conditional testing. Similar to using default in a switch statement. |
xsl:output |
Specifies options for use in serializing the result tree. |
xsl:param |
Used to declare a parameter with a local or global scope. Local parameters are scoped to the template in which they are declared. |
xsl:preserve-space |
Preserves whitespace in a document. Works in conjunction with the xsl:strip-space element. |
xsl:processing-instruction |
Writes a processing instruction to the result tree. |
xsl:sort |
Used with xsl:for-each or xsl:apply-templates to specify sort criteria for selected node lists. |
xsl:strip-space |
Causes whitespace to be stripped from a document. Works in conjunction with the xsl:preserve-space element. |
xsl:stylesheet |
This element must be the outermost element in an XSLT document and must contain a namespace associated with the XSLT specification and a version attribute. |
xsl:template |
Defines a reusable template for producing output for nodes that match a particular pattern. |
xsl:text |
Writes out the specified text to the result tree. |
xsl:transform |
Used in the same manner as the xsl:stylesheet element. |
xsl:value-of |
Writes out the value of the selected node to the result tree. |
xsl:variable |
Used to declare and assign variable values that can be either local or global in scope. |
xsl:when |
Used as a child element of xsl:choose to perform multiple conditional testing. Similar to using case in a switch or Select statement. |
xsl:with-param |
Used in passing a parameter to a template that is called via xsl:call-template. |
Although not every element listed in Table 7.1 is discussed in this section, you will be exposed to the more common elements and see how they can be used to transform XML into formats such as HTML, WML, and even EDI.
NOTE
The HTML generated by an XSLT document must conform to the rules outlined in the XML specification. All elements must be closed, including <img>, <input>, and all the other elements that normally do not need to be closed in HTML. Attributes used on elements must be quoted, a beginning and ending element tag's case must match, and so on. Keep in mind that the XSLT processor knows how to work only with well-formed XML and knows nothing about the tags used in HTML, WML, and so on. As a result, everything within the XSLT document must follow the XML rules.
Transforming to HTML Using XSLT Elements
One of the best ways to learn about the different XSLT elements is to see them in action. Listing 7.4 repeats the XSLT style sheet shown earlier in Listing 7.2 but adds additional functionality. After looking through the code, you'll see a step-by-step explanation of what the code is doing.
Listing 7.4 Golfers XSLT Document
1: <?xml version="1.0"?> 2: <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 3: version="1.0"> 4: <xsl:output method="html" indent="yes"/> 5: <xsl:template match="/"> 6: <html> 7: <head> 8: <style type="text/css"> 9: .blackText {font-family:arial;color:#000000;} 10: .largeYellowText {font-family:arial; 11: font-size:18pt;color:#ffff00;} 12: .largeBlackText {font-family:arial; 13: font-size:14pt;color:#000000;} 14: .borders {border-left:1px solid #000000; 15: border-right:1px solid #000000; 16: border-top:1px solid #000000; 17: border-bottom:1px solid #000000;} 18: </style> 19: </head> 20: <body bgcolor="#ffffff"> 21: <span class="largeBlackText"> 22: <b>List of</b> 23: <xsl:if test="count(//golfer) > 0"> 24:   25: <xsl:value-of select="count(//golfer)"/>  26: </xsl:if> 27: <b>Golfers</b> 28: </span> 29: <p/> 30: <xsl:apply-templates/> 31: </body> 32: </html> 33: </xsl:template> 34: <xsl:template match="golfers"> 35: <xsl:apply-templates select="golfer"/> 36: </xsl:template> 37: <xsl:template match="golfer"> 38: <table class="borders" border="0" width="640" 39: cellpadding="4" cellspacing="0" bgcolor="#efefef"> 40: <xsl:apply-templates select="name"/> 41: <tr class="blackText"> 42: <td width="12%" align="left"> 43: <b>Skill: </b> 44: </td> 45: <td width="12%" align="left"> 46: <xsl:attribute name="style"> 47: <xsl:choose> 48: <xsl:when test="@skill='excellent'"> 49: color:#ff0000;font-weight:bold; 50: </xsl:when> 51: <xsl:when test="@skill='moderate'"> 52: color:#005300; 53: </xsl:when> 54: <xsl:when test="@skill='poor'"> 55: color:#000000; 56: </xsl:when> 57: <xsl:otherwise> 58: color:#ffffff; 59: </xsl:otherwise> 60: </xsl:choose> 61: </xsl:attribute> 62: <xsl:value-of select="@skill"/> 63: </td> 64: <td width="12%" align="left"> 65: <b>Handicap: </b> 66: </td> 67: <td width="12%" align="left"> 68: <xsl:value-of select="@handicap"/> 69: </td> 70: <td width="12%" align="left"> 71: <b>Clubs: </b> 72: </td> 73: <td width="40%" align="left"> 74: <xsl:value-of select="@clubs"/> 75: </td> 76: </tr> 77: <tr> 78: <td colspan="6"> </td> 79: </tr> 80: <tr class="blackText"> 81: <td colspan="6" class="largeBlackText"> 82: Favorite Courses 83: </td> 84: </tr> 85: <tr> 86: <td colspan="2"> 87: <b>City: </b> 88: </td> 89: <td colspan="2"> 90: <b>State: </b> 91: </td> 92: <td colspan="2"> 93: <b>Course: </b> 94: </td> 95: </tr> 96: <xsl:apply-templates select="favoriteCourses"/> 97: </table> 98: <p/> 99: </xsl:template> 100: <xsl:template match="name"> 101: <tr> 102: <td colspan="6" class="largeYellowText" bgcolor="#02027a"> 103: <xsl:value-of select="firstName"/>  104: <xsl:value-of select="lastName"/> 105: </td> 106: </tr> 107: </xsl:template> 108: <xsl:template match="favoriteCourses"> 109: <xsl:apply-templates/> 110: </xsl:template> 111: <xsl:template match="course"> 112: <xsl:call-template name="writeComment"/> 113: <tr class="blackText"> 114: <td colspan="2" align="left"> 115: <xsl:value-of select="@city"/> 116: </td> 117: <td colspan="2" align="left"> 118: <xsl:value-of select="@state"/> 119: </td> 120: <td colspan="2" align="left"> 121: <xsl:value-of select="@name"/> 122: </td> 123: </tr> 124: </xsl:template> 125: <xsl:template name="writeComment"> 126: <xsl:comment>List Course Information</xsl:comment> 127: </xsl:template> 128: </xsl:stylesheet>
The following explanation walks through each element used in Listing 7.4.
Line 1:
1: <?xml version="1.0"?>
The XML declaration is used because this is a valid XML document.
Lines 23:
2: <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 3: version="1.0">
This line contains the first XSLT element used in the document: xsl:stylesheet. As shown earlier, this element has an associated namespace declaration and version attribute. The xsl:transform element could also be used here. One of these two elements will always be the root node of the XSLT document.
Line 4:
4: <xsl:output method="html" indent="yes"/>
The xsl:output element is used to specify what type of format will be created in the result tree. The method attribute can contain values of xml, html, or text. This element is a top-level element, meaning that it must be a child of the xsl:stylesheet element to be used properly. The different attributes that can be used to describe this element are the following: method, version, encoding, omit-xml-declaration, standalone, doctype-public, doctype-system, cdata-section-elements, indent, media-type. This element includes the indent attribute, which will indent the result tree to show its hierarchical structure. XSLT processors do not have to honor the indentation request. If the processor does support indentation, the manner in which the indentation is implemented is up to the processor.
Lines 533:
5: <xsl:template match="/"> 6: <html> 7: <head> 8: <style type="text/css"> 9: .blackText {font-family:arial;color:#000000;} 10: .largeYellowText {font-family:arial; 11: font-size:18pt;color:#ffff00;} 12: .largeBlackText {font-family:arial; 13: font-size:14pt;color:#000000;} 14: .borders {border-left:1px solid #000000; 15: border-right:1px solid #000000; 16: border-top:1px solid #000000; 17: border-bottom:1px solid #000000;} 18: </style> 19: </head> 20: <body bgcolor="#ffffff"> 21: <span class="largeBlackText"> 22: <b>List of</b> 23: <xsl:if test="count(//golfer) > 0"> 24:   25: <xsl:value-of select="count(//golfer)"/>  26: </xsl:if> 27: <b>Golfers</b> 28: </span> 29: <p/> 30: <xsl:apply-templates/> 31: </body> 32: </html> 33: </xsl:template>
Here's an example of the first template definition specified in the XSLT document. Templates contain structured information that will be processed and output to the result tree. They are processed when the XPath pattern found in the match attribute matches a node found in the source XML document.
This template has a match attribute with a value of /. The value (/) represents a pattern that matches the XML document (think of it as the position directly above the root XML element). The purpose of a pattern is to identify which nodes a template applies to. You can think of patterns as valid XPath statements, although the XSLT definition does define them separately.
If the pattern specified in the match attribute matches a node in the source XML document, the information located within the template will be processed and written out to the result tree. In this case, when the XML document is matched, the basic elements used to start an HTML document are added to the result tree.
NOTE
It's worth repeating that that the <html> and <body> elements contained within the template are simply standard XML elements to the XSLT processor. It knows nothing about HTML elements and simply cares that the elements follow the XML rules. Only when processing has completed and the result tree is rendered in an application that understands HTML tags, will the <html> and <body> elements have any presentation purpose.
The xsl:template element can have the attributes shown in Table 7.2.
Table 7.2 xsl:template Attributes
Attribute Name |
Description |
match |
The match attribute's value is a pattern defined by an XPath statement that states which nodes in the source XML document should be processed by the template it is associated with. Although optional, if this attribute is not included, the name attribute shown next must be included. |
name |
Applies a name to a template. This attribute is used by xsl:call- template. Although optional, if this attribute is not included, the match attribute shown next must be included. |
priority |
The value must be a number that says the priority of the template. The priority attribute's value will be taken into consideration if several templates match the same node. |
mode |
Applies a mode to a template. The mode is simply a name that can be used by the xsl:apply-templates element to hit a template that does a specific form of transformation. For example, if you need a node named chapter within an XML document to be processed differently, depending on its position within the document, you can create different templates that all match the chapter node. To differentiate the templates from each other (because they have the same match attribute value) a mode can be applied to each one. A call can then be made to the specific template that needs to be processed by using the xsl:apply-templates element and then specifying the template that has the desired mode. |
Lines 2326:
23: <xsl:if test="count(//golfer) > 0"> 24:   25: <xsl:value-of select="count(//golfer)"/>  26: </xsl:if>
This portion of the XSLT document shows how the xsl:if element can be used to test a particular condition. In this case, an XSLT function named count() is used. This function takes an XPath statement as input and returns the number of nodes as output. You'll learn more about XSLT functions in the next section.
The xsl:if element must have an attribute named test, which converts a valid XPath statement to a Boolean. In this case, the test checks to see whether the number of golfer nodes exceeds the value of 0. If the test returns true, the content (referred to as the template body) between the xsl:if start and end tags is processed.
Line 25 contains the xsl:value-of element, which is used frequently in XSLT documents to write out the value of a particular node to the result tree. This element must contain an attribute named select. The value of the attribute must contain a valid XPath expression. The node (or nodes) returned by the expression is converted to a string. In this case, the number of golfer nodes within the XML document is returned and placed in the result tree structure.
The xsl:value-of element may optionally include an attribute named disable-output-escaping that is useful when characters such as < or > need to be output without being escaped by using < or >. The attribute accepts a value of yes or no. Using it is especially useful when an XML element contains HTML tags that need to be written to the result tree structure without escaping the brackets.
Line 30:
30: <xsl:apply-templates/>
The xsl:apply-templates element provides a way to call templates that may match with other items contained in the source XML document. Before explaining what this element does in more detail, it's appropriate to introduce a term called the context node. The context node is defined as the source document node currently being processed by the XSLT processor as specified by a template. Because we are processing the document node in the current template, that node is considered the context node. Obviously, many other elements are below this node, including golfers and golfer, that may also have templates associated with them. By using xsl:apply-templates the XSLT code is saying (in more human terms), "Find all templates that match with child elements of the current node (the context node) and go out and process them." The first child node that will be found is the root node of the source document (golfers). The template that matches up with it is described next.
Lines 3436:
34: <xsl:template match="golfers"> 35: <xsl:apply-templates select="golfer"/> 36: </xsl:template>
The golfers node in the source XML document matches up with the template defined in these lines of the XSLT document. As the XSLT processor is attempting to match templates with nodes in the XML document, any node with a name of golfers will automatically be processed by this template. Within the template, you'll notice that the content is very sparse, except for the inclusion of an xsl:apply-templates element. With the context node now being the golfers node, calling xsl:apply-templates will send the processor looking for templates that match up with child nodes of the golfers node.
You'll notice that line 30 includes an attribute named select that applies to the xsl:apply-templates element. This attribute accepts a valid XPath expression as a value. In this case, it selects a template that matches up with a node named golfer. Because the golfers node contains golfer nodes only as children, including this attribute is unnecessary and is shown only to exemplify its use. If, however, the golfers node contained child nodes other than the golfer node and we wanted the golfer node template to be processed first, the inclusion of the select attribute would be more appropriate. This will become more clear as the next template is discussed.
Lines 3799:
37: <xsl:template match="golfer"> 38: <table class="borders" border="0" width="640" 39: cellpadding="4" cellspacing="0" bgcolor="#efefef"> 40: <xsl:apply-templates select="name"/> 41: <tr class="blackText"> 42: <td width="12%" align="left"> 43: <b>Skill: </b> 44: </td> 45: <td width="12%" align="left"> 46: <xsl:attribute name="style"> 47: <xsl:choose> 48: <xsl:when test="@skill='excellent'"> 49: color:#ff0000;font-weight:bold; 50: </xsl:when> 51: <xsl:when test="@skill='moderate'"> 52: color:#005300; 53: </xsl:when> 54: <xsl:when test="@skill='poor'"> 55: color:#000000; 56: </xsl:when> 57: <xsl:otherwise> 58: color:#000000; 59: </xsl:otherwise> 60: </xsl:choose> 61: </xsl:attribute> 62: <xsl:value-of select="@skill"/> 63: </td> 64: <td width="12%" align="left"> 65: <b>Handicap: </b> 66: </td> 67: <td width="12%" align="left"> 68: <xsl:value-of select="@handicap"/> 69: </td> 70: <td width="12%" align="left"> 71: <b>Clubs: </b> 72: </td> 73: <td width="40%" align="left"> 74: <xsl:value-of select="@clubs"/> 75: </td> 76: </tr> 77: <tr> 78: <td colspan="6"> </td> 79: </tr> 80: <tr class="blackText"> 81: <td colspan="6" class="largeBlackText"> 82: Favorite Courses 83: </td> 84: </tr> 85: <tr> 86: <td colspan="2"> 87: <b>City: </b> 88: </td> 89: <td colspan="2"> 90: <b>State: </b> 91: </td> 92: <td colspan="2"> 93: <b>Course: </b> 94: </td> 95: </tr> 96: <xsl:apply-templates select="favoriteCourses"/> 97: </table> 98: <p/> 99: </xsl:template>
This template does the bulk of the work in Listing 7.4 by matching up with all golfer nodes in the source XML document. When the template is processed, the shell structure for an HTML table is written out. This table will be used to present all the information about a specific golfer. Line 40 uses the xsl:apply-templates and provides a pattern for the template that should be called by using the select attribute. By providing a pattern equal to name, only a template that matches up with the pattern will be processed. Why didn't we simply call xsl:apply-templates and not worry about which of the context node's child node templates were called? The answer is that we want to ensure that the template with a pattern matching the name child node is processed before any other children of the context node (golfer, in this case).
After the template matching the name node is called, processing will be done on that template and then return to the golfers template. Specifically, the XSLT processor will jump back to the next statement in the golfer template that immediately follows the call to <xsl:apply-templates select="name"/>.
Lines 4674 exhibit several of the XSLT elements shown earlier in Table 7.1. To start things off, line 46 contains an xsl:attribute element named style. This XSLT element adds a style attribute to the <td> tag in line 45. The value of the attribute is dynamically assigned based on a series of conditional tests. To accomplish the tests, the xsl:choose, xsl:when, and xsl: otherwise elements are used. These elements function in a manner similar to the switch, case, and default keywords used when coding a switch statement in C#.
The conditional test starts with the xsl:choose element. It can be followed by as many xsl:when elements, as needed. The xsl:when element must contain a mandatory attribute named test that contains the expression to test. If the test returns true, the content between the xsl:when starting and ending tags will be assigned to the value of the style attribute. The test performed in line 48 checks to see whether an attribute of the context node (remember that the context node is golfer at this point) named skill has a value equal to excellent. If it does, the style attribute will have a value of color:#ff0000;font-weight:bold;. Assuming the skill attribute does have a value of excellent, the actual structure added on completion of the xsl:choose block testing will be the following:
<td width="12%" align="left" style="color:#ff0000;font-weight:bold;">
If the first xsl:when returns false, testing will continue down the line. If no xsl:when tests return true, the xsl:otherwise block will be hit and the style attribute will be assigned a value of color:#000000; (lines 5759).
After the style attribute has been added to the <td> tag, processing continues with line 62, which adds the value of the skill attribute to the table column by using the xsl:value-of element discussed earlier. Lines 6494 continue to add additional columns to the table and write out the value of attributes found on the context node (the golfer node).
When processing in the golfers template completes, the xsl:apply-templates element is again used along with a select attribute that points to a template pattern of favoriteCourses (line 96). This template will be discussed later.
Lines 100107:
100: <xsl:template match="name"> 101: <tr> 102: <td colspan="6" class="largeYellowText" bgcolor="#02027a"> 103: <xsl:value-of select="firstName"/>  104: <xsl:value-of select="lastName"/> 105: </td> 106: </tr> 107: </xsl:template>
The template declaration shown in line 100 matches up with all name nodes found within the XML document. This template is called from within the golfer template discussed earlier (see line 40). Processing of the template is limited to writing out a new row in the table (line 101) followed by a column containing the values of the firstName and lastName elements. These values are written to the result tree by using the xsl:value-of element(lines 103 and 104).
Lines 108127:
108: <xsl:template match="favoriteCourses"> 109: <xsl:apply-templates/> 110: </xsl:template> 111: <xsl:template match="course"> 112: <xsl:call-template name="writeComment"/> 113: <tr class="blackText"> 114: <td colspan="2" align="left"> 115: <xsl:value-of select="@city"/> 116: </td> 117: <td colspan="2" align="left"> 118: <xsl:value-of select="@state"/> 119: </td> 120: <td colspan="2" align="left"> 121: <xsl:value-of select="@name"/> 122: </td> 123: </tr> 124: </xsl:template> 125: <xsl:template name="writeComment"> 126: <xsl:comment>List Course Information</xsl:comment> 127: </xsl:template>
The template matching favoriteCourses contains no functionality other than to call xsl: apply-templates. This is because it contains no attributes and acts only as a parent container element. Because the favoriteCourses node contains only child nodes named course, calling xsl:apply-templates will result in only one template match.
Processing within the template that matches the course nodes is limited to adding a new row (line 113) with columns containing the values for attributes named city, state, and name. Each course node found within the source XML document will be matched with this template and the appropriate attribute values will be written out.
Line 112 introduces a new XSLT element that hasn't been covered to this point. The xsl:call-template element can be used to call a template in a similar manner as calling a function within C# or VB.NET. Calling a template in XSLT is accomplished by identifying the name of the template to call via a name attribute. The template that is called must, in turn, have a matching name attribute as shown in line 125.
Calling templates can be useful when a template doesn't match up with a given node in an XML document but needs to be accessible to process commonly used features or perform calculations. For example, if your XSLT code needs to walk through a list of pipe-delimited strings, a template can be called recursively until each piece of data within the string has been processed. You'll see a concrete example of using the xsl:call-template element in conjunction with the xsl:with-param and xsl:param elements toward the end of this chapter.
Line 128:
128: </xsl:stylesheet>
The XSLT language follows all the rules outlined in the XML specification. As such, the xsl:stylesheet element must be closed.
This example has shown how you can use some of the main elements found in the XSLT specification. For more information on some of the other elements not covered in the previous example, refer to the XSLT version 1.0 specification (http://www.w3.org/TR/1999/REC- xslt-19991116) or pick up a copy of a book titled XSLT Programmer's Reference (ISBN 1-861003-12-9).
Transforming XML into Another Form of XML Using XSLT Elements
HTML isn't the only structure that can be created by an XSLT document. Many cases may occur in which other formats, such as a comma-delimited, WML, or even another form of XML, need to be generated to integrate with an application. In this section we'll take a look at a simple example of transforming from XML to XML and show how a few of the XSLT elements can make this process easier. XML-to-XML transformations are useful when an XML document's structure needs to be changed before sending it off to a vendor or to another application that expects a different format.
Listing 7.5 shows the XML document that needs to be transformed, Listing 7.6 shows the result of the transformation, and Listing 7.7 shows the XSLT document used in processing the transformation.
Listing 7.5 The Source XML Document
1: <?xml version="1.0"?> 2: <root> 3: <row> 4: <id>1</id> 5: <name> 6: <fname>Dan</fname> 7: <lname>Wahlin</lname> 8: </name> 9: <address type="home"> 10: <street>1234 Anywhere St.</street> 11: <city>AnyTown</city> 12: <zip>85789</zip> 13: </address> 14: <address type="business"> 15: <street>1234 LottaWork Ave.</street> 16: <city>AnyTown</city> 17: <zip>85786</zip> 18: </address> 19: </row> 20: <row> 21: <id>2</id> 22: <name> 23: <fname>Elaine</fname> 24: <lname>Wahlin</lname> 25: </name> 26: <address type="home"> 27: <street>1234 Anywhere St.</street> 28: <city>AnyTown</city> 29: <zip>85789</zip> 30: </address> 31: <address type="business"> 32: <street>1233 Books Way</street> 33: <city>AnyTown</city> 34: <zip>85784</zip> 35: </address> 36: </row> 37: </root>
Listing 7.6 The Result of Transforming the XML Document in Listing 7.5
1: <?xml version="1.0"?> 2: <root> 3: <row id="1" fname="Dan" lname="Wahlin"> 4: <address type="home"> 5: <street>1234 Anywhere St.</street> 6: <city>AnyTown</city> 7: <zip>85789</zip> 8: </address> 9: <address type="business"> 10: <street>1234 LottaWork Ave.</street> 11: <city>AnyTown</city> 12: <zip>85786</zip> 14: </address> 15: </row> 16: <row id="2" fname="Elaine" lname="Wahlin"> 17: <address type="home"> 18: <street>1234 Anywhere St.</street> 19: <city>AnyTown</city> 20: <zip>85789</zip> 21: </address> 22: <address type="business"> 23: <street>1233 Books Way</street> 24: <city>AnyTown</city> 25: <zip>85784</zip> 26: </address> 27: </row> 28: </root>
Listing 7.7 The XSLT Document
1: <?xml version="1.0" ?> 2: <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 3: version="1.0"> 4: <xsl:output method="xml" indent="yes" encoding="utf-8" 5: omit-xml-declaration="no"/> 6: <xsl:template match="/"> 7: <root> 8: <xsl:apply-templates/> 9: </root> 10: </xsl:template> 11: <xsl:template match="row"> 12: <row> 13: <xsl:attribute name="id"> 14: <xsl:value-of select="id"/> 15: </xsl:attribute> 16: <xsl:attribute name="fname"> 17: <xsl:value-of select="name/fname"/> 18: </xsl:attribute> 19: <xsl:attribute name="lname"> 20: <xsl:value-of select="name/lname"/> 21: </xsl:attribute> 22: <xsl:for-each select="address"> 23: <xsl:copy-of select="."/> 24: </xsl:for-each> 25: </row> 26: </xsl:template> 27: </xsl:stylesheet>
Breaking the XSLT document down into individual pieces reveals a few new things not seen in previous examples. First, Line 4 uses the xsl:output element to specify an output format of xml. It also specifies that the XML declaration should be included. This is done by setting the omit-xml-declaration attribute to no. Because this is the attribute's default value, it could have been left out altogether.
Lines 610 take care of setting the starting template (the one that matches the document node) needed in the XSLT document. This template simply adds a node named root to the result tree and then triggers the process of looking for other templates that match up with nodes in the source XML document.
The template matching the row element node writes a row element to the result tree. The bulk of the transformation process occurs in lines 1324. To start, three different attributes are added to the row element by using the xsl:attribute element. The value of these attributes is obtained by using the xsl:value-of element to access the appropriate elements in the source XML document.
After the attributes are added, the xsl:for-each element is used to loop through all address elements. This element simply takes the name of the node-set to loop through as the value of the select attribute. Because the address elements (and their children) remain unchanged from the source to the result tree, the xsl:copy-of element is used to simply copy over the address element (and all its children) to the result tree. Had we wanted only to copy the address element itself and not the children, we could have used the xsl:copy element instead. However, utilizing the xsl:copy-of element prevents us from having to create each element (address, street, city, zip) dynamically by using the xsl:element or xsl:copy elements.
Now that you've had an opportunity to see some of the most common XSLT elements in action, let's take a look at a few more that can help make your XSLT documents more dynamic and flexible.
Using Variables and Parameters: The xsl:variable and xsl:param Elements
As programmers, we all take for granted the capability to use variables and pass parameters to functions. In fact, it's hard to imagine programming without variables and parameters. Most programmers would be hard pressed to eliminate them from their applications. Fortunately, there's no need to worry about variables or parameters being eliminated from C#, VB.NET, or even from languages such as XSLT. The XSLT specification includes the capability to use a variable or pass a parameter to a template. In this section, you are provided with a general overview of how variables and parameters can be used to make your XSLT documents more flexible. Let's first take a look at how variables can be used.
Variables in XSLT
XSLT variables are used to avoid calculating the same result multiple times. Although very similar to variables used in C# or any other programming language, XSLT variables can be set once but cannot be updated after they are set. The value assigned to a variable is retained until the variable goes out of scope. What, you say! XSLT variables can be set only once? Doesn't this make them the equivalent of a constant?
There's a method to the madness that makes perfect sense when analyzed. Because XSLT relies on templates that can be called randomly, depending on the structure of the source XML document, the capability to update a particular global variable could introduce problems. These types of problems are referred to as "side effects," and the XSLT specification eliminates the problem by allowing for no side effects. If you've ever stepped into a project that you didn't originally code that had bugs cropping up because of overuse of global variables, you'll appreciate this concept.
To illustrate the concept of side effects more, let's take a look at a simple example. If a template named template1 relies on another template named template2 to update the value of a global variable, what happens if template2 doesn't ever get processed? The rather obvious answer is that the global variable will never be updated and therefore can cause potential problems to template1 when it is processed. By not allowing variables in XSLT style sheets to be updated, this problem is avoided.
NOTE
Some XSLT processors do allow for variables to be updated by using extension functions. These functions are specific to the XSLT processor and are not part of the XSLT specification
It's important to realize that XSLT documents can be written without the use of variables. However, variables can aid in cleaning up the code and can also result in more efficient XSLT style sheets. The following example uses a portion of the code shown earlier in Listing 7.4 to demonstrate how a variable declared globally (as a child element of the xsl:stylesheet element) can be used in XSLT:
<xsl:variable name="count" select="count(//@handicap[. < 11])"/> <xsl:template match="golfer"> <!-- ...Other content here --> <td width="12%" align="left"> <xsl:value-of select="@handicap"/> <br/> <xsl:if test="@handicap > 11"> <xsl:value-of select="$count"/> golfers have lower handicaps </xsl:if> </td> <!-- ...Other content here --> </xsl:template>
The xsl:variable element can have a name and a select attribute. The name attribute is required and serves the obvious purpose of assigning a name that can be used to reference the variable. The select attribute is optional but when listed, must contain a valid XPath expression.
So what have we gained by using a variable in this template? The code has actually been made much more efficient as compared to writing the same code without using the variable. Instead of having to calculate the count of all the golfers in the XML document with handicaps less than 11 each time a golfer node template is matched, the variable obtains this value and stores it when the XSLT style sheet is first loaded. The variable can then be referenced in several places by adding the $ character to the beginning of the variable name ($count in this case). Doing this cuts out unnecessary processing during the transformation process.
At times, the value of an attribute may need to be dynamically generated and used in several places. In many cases, using a variable can make this process easier:
<xsl:variable name="color"> <xsl:choose> <xsl:when test="@handicap = 10"> #ff0000 </xsl:when> <xsl:when test="@handicap = 20"> #ffff00 </xsl:when> <xsl:otherwise> #ffffff </xsl:otherwise> </xsl:choose> </xsl:variable> <font color="{$color}"><xsl:value-of select="@handicap"/></font> <p> </p> <font color="{$color}" size="4"><xsl:value-of select="lastName"/></font>
Although the value of the color attribute found on the font tag could be added by using the xsl:attribute element multiple times, by defining the value once in the variable, the code is kept cleaner and the processing is more efficient.
TIP
The previous example shows a shortcut that can be used to embed data directly into attributes that will be written out to the result tree. By wrapping a variable (or other item) with the { and } brackets, it will dynamically be added to the result tree. This is in contrast to adding the attribute name and value to a tag by using the xsl:attribute element. As another example, if you had an attribute named width in an XML document, you could write it out to a table tag by doing the following: <table width="{@width}">. This is similar to doing something such as <table width="<%=myWidth%>"> in ASP.NET.
Variables can also be useful for storing values returned by calling a template. This process will be shown a little later after you're introduced to the xsl:param element.
Parameters in XSLT
Parameters are useful in a variety of programming languages, with XSLT being no exception. Parameters can be used in XSLT documents in two basic ways. First, parameter values can be passed in from an ASP.NET application. This allows data not found within the XML document or XSLT style sheet to be part of the transformation process. Second, parameter values can be passed between XSLT templates in much the same way that parameters can be passed between functions in C# or VB.NET. You'll see how parameters can be used in both ways later in the chapter.
Declaring a parameter is similar to declaring a variable. Simply name the parameter and add an optional select attribute:
<xsl:param name="myParam" select="'My Parameter Value'"/>
As with variables, parameters can be children of the xsl:stylesheet or xsl:transform elements and can also be children of the xsl:template element. So when would you want to use a parameter? Let's assume that the golfers XSLT document shown in Listing 7.4 needs to show a specific golfer based on user input in a Web form. To accomplish this task, the ASP.NET application can pass in the value entered by the user to a parameter within the XSLT document. This value can then be used to display the proper golfer's information. A simplified document that uses a parameter named golferNum is shown next:
<?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:param name="golferName" select="'Dan'"/> <xsl:template match="golfers"> <xsl:apply-templates select="//golfer[name/firstName=$golferName]"/> </xsl:template> <xsl:template match="golfer"> <xsl:apply-templates/> </xsl:template> <xsl:template match="name"> <xsl:value-of select="firstName"/>   <xsl:value-of select="lastName"/> </xsl:template> </xsl:stylesheet>
Having this parameter in the XSLT style sheet will cause a specific golfer's information to be transformed. Any other golfers in the XML document will simply be ignored. How is this accomplished? A small change was made to the xsl:apply-templates element in the golfers template. Instead of processing all golfer nodes in the XML document, the XPath expression in the select attribute specifies the specific golfer node to process:
<xsl:apply-templates select="//golfer[name/firstName=$golferName]"/>
Although this example hard-codes a value for the golferName parameter, an ASP.NET page would normally pass the value in using specific classes in the System.Xml assembly. You'll be introduced to these classes later in the chapter. If the value passed into the golferName parameter from the ASP.NET page does not match up with an existing golfer node in the XML document, no error will be raised.
Parameters can also be used in conjunction with the xsl:call-template element. Fortunately, from working with other programming languages, you already have a good understanding of how this works. Imagine calling a method named GetOrders() that accepts a single parameter as an argument. The method call would look something like the following:
GetOrders("ALFKI");
Now imagine that GetOrders is the name of an XSLT template used to transform an XML document containing customers and orders. Calling the template and passing the parameter can be accomplished by doing the following:
<xsl:template match="Customer"> <xsl:call-template name="GetOrders"> <xsl:with-param name="CustomerID" select="@CustomerID"/> </xsl:call-template> </xsl:template> <xsl:template name="GetOrders"> <xsl:param name="CustomerID" select="'ALFKI'"/> <xsl:value-of select="//Order[@CustomerID = $CustomerID]"/> </xsl:template>
This example shows the use of the xsl:call-template and xsl:with-param elements to initiate the template call. The xsl:call-template element has a single attribute that provides the name of the template to call. The xsl:with-param element has two attributes. One is used to name the parameter that data will be passed to and the other provides the value that is to be passed. The select attribute can contain any valid XPath expression. The xsl:with-param element can only be a child of the xsl:call-template or xsl:apply-templates element.
TIP
The xsl:param element named CustomerID (shown earlier) has a parameter value of ALFKI with single quotes around it because it is a string value rather than an XPath expression. Had the single quotes been omitted, the XSLT processor would try to find a node named ALFKI (which doesn't exist, of course). Although this seems fairly obvious, it's an easy mistake to make.
The xsl:param element in the template being called is updated by using the xsl:with-param element shown previously. It has two potential attributes, including name and select, as described earlier. In the previous example, the parameter named CustomerID is assigned a default value of ALFKI. This value will be overridden when the GetOrders template is called and a parameter value is passed to it using the xsl:with-param element.
Because variables cannot be updated in XSLT, parameters play a large role in allowing values to be passed between templates. A global parameter (declared as a child of the xsl:stylesheet or xsl:transform elements) can receive input from an ASP.NET page, but it cannot be updated more than once after processing of the XSLT document begins. However, the capability to place parameters within the scope of a specific template body offers the capability to call templates recursively. This is possible because a single template can call itself and pass a parameter value (or more than one value, in the case of multiple parameters within the template) that can then be processed as appropriate.
Although the inability to update variables and parameters may seem somewhat restrictive, the authors of the XSLT specification knew that it was necessary because XML documents can contain many different structures. You can't depend on one template being processed before another, especially in the case where one XSLT document is used to transform a variety of XML documentsall with different structures.
Accessing "Return" Values of XSLT Templates
You may have noticed that although a template can act somewhat like a method, it has no way to return a response...or does it? By wrapping the xsl:variable element around a template call made using the xsl:call-template element, the output normally written to the result tree can instead be captured by the variable. This process is shown next:
<xsl:template match="Customer"> <xsl:variable name="CID" select="@CustomerID"/> <xsl:variable name="Orders"> <xsl:call-template name="GetNames"> <xsl:with-param name="CustomerID" select="$CID"/> </xsl:call-template> </xsl:variable> <b>Customer</b><br/> ID: <xsl:value-of select="$CID"/><br/> <xsl:for-each select="$Orders//Order"/> <xsl:value-of select="@OrderID"/> </xsl:for-each> </xsl:template> <xsl:template name="GetNames"> <xsl:param name="CustomerID" select="'ALFKI'"/> <xsl:value-of select="//Orders[@CustomerID = $CustomerID]"/> </xsl:template>
The variable named Orders will be filled with a node-set generated by a call to the GetNames template. Doing this offers a powerful means for building more dynamic and efficient XSLT documents.
Now that you're familiar with several of the main elements used in creating XSLT style sheets, it's time to examine XSLT functions.