Acknowledgment:Written By Dan Wahlin on Feb 22, 2002
In this sample chapter, Dan Wahlin discusses XSLT. Learn about the transformation process, the XSLT language, XSLT functions, and more. XSLT is very powerful tool in dynamic web site development.
During the development of the XML specification, the W3C working group
realized that for XML to reach its full potential, a method of transforming XML
documents into different formats needed to exist. At some time or another, an
application that has the capability to work with XML documents will need to
display or structure the data in a different format than specified in the
document. If the only method for accomplishing this task necessitates
programmatically transforming the XML document into the appropriate format by
using an XML parser paired with a programming language, the power of having a
cross-platform and language-independent XML language would be lost. Some method
of transforming XML documents into different formats such as HTML, flat files,
Wireless Markup Language (WML), and even other forms of XML needed to be devised
so that it could be used on any platform and with any language.
To accommodate this transformation process, Extensible Stylesheet Language
Transformations (XSLT) was created. Version 1.0 of the XSLT specification
reached recommended status at the W3C in November of 1999
(http://www.w3.org/TR/1999/REC-xslt-19991116)
and many XML parsers now provide full XSLT support. The .NET framework provides
100% compliance with the XSLT version 1.0 specification.
What exactly is XSLT useful for and why would you, as an ASP.NET developer,
want to learn about it? The answer boils down to the capability of XSLT to
transform XML documents into different formats that can be consumed by a variety
of devices, including browsers, Personal Digital Assistants (PDAs), Web-enabled
phones, and other devices that will appear in the near future.
Transformations can also be useful in situations where an XML document's
structure does not match up well with an application that will accept the data
within the document. An XML document may contain the appropriate data to be
imported into a database, for example, but may not be structured in a way that
the application performing the import expects. For example, the application may
be better prepared to handle element-based XML documents rather than ones with a
lot of attributes, as shown in the following document:
<?xml version="1.0"?>
<root>
<row id="1" fname="Dan" lname="Wahlin"/>
<row id="2" fname="Heedy" lname="Wahlin"/>
<row id="3" fname="Danny" lname="Wahlin"/>
<row id="4" fname="Jeffery" lname="Wahlin"/>
</root>
Using XSLT, this document can be transformed into a structure that the
application is better suited to work with:
<?xml version="1.0"?>
<root>
<row>
<id>1</id>
<fname>Dan</fname>
<lname>Wahlin</lname>
</row>
<row>
<id>2</id>
<fname>Heedy</fname>
<lname>Wahlin</lname>
</row>
<row>
<id>3</id>
<fname>Danny</fname>
<lname>Wahlin</lname>
</row>
<row>
<id>4</id>
<fname>Jeffery</fname>
<lname>Wahlin</lname>
</row>
</root>
This chapter teaches you how to perform this type of transformationas
well as many othersby covering the following topics:
The Transformation Process
The process of transforming an XML document into another format, such as HTML
or WML, relies on two types of processing engines. First, a parser capable of
loading an XML document must be present to load the source document into a DOM
tree structure (for more information about the DOM, refer to Chapter 6, "Programming
the Document Object Model (DOM) with ASP.NET." Next, the XSLT document
must be loaded and a tree structure will be created for it, as well. This tree
structure will normally be optimized to accommodate XSLT processing and is specific
to the processor being used. An XSLT processor is then needed to take the XML
document structure, match up nodes within the document against "templates"
found in the XSLT document, and then output the resulting document. The third
tree structure (the resulting document) is dynamically created based on information
contained in the XSLT document. A simple diagram of this transformation process
is shown in Figure 7.1.
Figure 7.1
The XSLT transformation process.
XSLT Templates
Before looking more closely at how to build XSLT documents, it's
important that you understand what the building blocks of these documents are.
Although XSLT stands for Extensible Stylesheet Language Transformations, an
alternative name for it could potentially be Extensible Template Language
Transformations. Why? The answer is because of its reliance on templates to
process and create a particular output structure. The W3C provides the following
statement about templates:
A stylesheet contains a set of template rules. A template rule has two
parts: a pattern which is matched against nodes in the source tree and a
template which can be instantiated to form part of the result tree. This allows
a stylesheet to be applicable to a wide class of documents that have similar
source tree structures.
If you have ever used templates in Excel, Word, or PowerPoint, you know that
they provide a basic structure that can be reused for specific purposes. For
example, every time you submit an expense report, you may be accustomed to
filling out a template in Word that is designed for this purpose. The template
likely has specific form fields built in so that every expense report being
submitted looks the same. There may be other templates that are used for
purchase orders. The point is that the templates are geared to match up with a
specific task, such as creating an expense report, a purchase order, or some
other activity.
Templates in XSLT function in much the same way, except that they are geared
to match up with nodes in an XML document. XSLT templates provide a way to
process and structure data contained within elements and attributes in the
source XML document. Their basic purpose is to provide a template structure that
can be processed when a particular node in the source XML document is
discovered.
So how do templates work? The XSLT processor described earlier is provided
with two tree structures to walk through. The first is the structure for the
source XML document and the second is the XSLT document itself. After these two
structures are provided, the XSLT processor attempts to match element or
attribute names found in the XML document with templates contained in the XSLT
tree structure. This matching process uses XPath expressions that are embedded
within the XSLT document. When a node found within the XML document matches a
template in the XSLT document, that template is processed.
Processing of templates found within an XSLT document normally starts with a
template that matches the root node of the XML document and proceeds down to its
children. When a template is processed, the output is added to the third tree
structure mentioned earlier that is used in building the output document.
Templates offer an efficient way to process a variety of XML document
structures and are very efficient in cases where an XML document contains
repetitive items. Each time an element, attribute, text node, and so on is
found, it is matched up with the appropriate template via XPath expressions. If
a given node does not have a matching template, no processing will occur on it,
and the next section of the XML document is processed. In cases where a matching
node is found, the template takes care of generating the proper output structure
based on data/nodes contained within the node.
So that you can see templates in action, the next section introduces you to a
simple XSLT document. The sections that follow describe in greater detail how to
use templates and other parts of the XSLT language.
Getting Your Feet Wet with XSLT
In this section we'll examine a simple XSLT document that transforms XML
into HTML for display in a browser. The sections that follow show how XSLT can
transform XML into many formats other than HTML. This example represents a
common task that you will likely use when developing ASP.NET applications that
require the presentation of XML data within a browser. Listing 7.1 shows an XML
document that contains information about different golfers.
Listing 7.1 Golfers XML Document
1: <?xml version="1.0" ?>
2: <golfers>
3: <golfer skill="excellent" handicap="10"
clubs="Taylor Made" id="1111">
4: <name>
5: <firstName>Heedy</firstName>
6: <lastName>Wahlin</lastName>
7: </name>
8: <favoriteCourses>
9: <course city="Pinetop" state="AZ" name="Pinetop Lakes CC"/>
10: <course city="Phoenix" state="AZ" name="Ocotillo"/>
11: <course city="Snowflake" state="AZ" name="Silver Creek"/>
12: </favoriteCourses>
13: </golfer>
14: <golfer skill="moderate" handicap="12"
clubs="Taylor Made" id="2222">
15: <name>
16: <firstName>Dan</firstName>
17: <lastName>Wahlin</lastName>
18: </name>
19: <favoriteCourses>
20: <course city="Pinetop" state="AZ" name="Pinetop Lakes CC"/>
21: <course city="Pinetop" state="AZ" name="White Mountain CC"/>
22: <course city="Springville" state="UT" name="Hobble Creek"/>
23: </favoriteCourses>
24: </golfer>
25: </golfers>
Listing 7.2 presents an XSLT document that can be used to transform the XML
just shown into HTML. The different elements used in this document are discussed
later.
Listing 7.2 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 indent="yes" method="html"/>
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;font-size:18pt;
11: color:#ffff00;}
12: .largeBlackText {font-family:arial;font-size:14pt;
13: 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"><b>Golfers: </b></span>
22: <p/>
23: <xsl:apply-templates/>
24: </body>
25: </html>
26: </xsl:template>
27: <xsl:template match="golfers">
28: <xsl:apply-templates/>
29: </xsl:template>
30: <xsl:template match="golfer">
31: <table class="borders" border="0" width="640" cellpadding="4"
32: cellspacing="0" bgcolor="#efefef">
33: <xsl:apply-templates select="name"/>
34: <tr class="blackText">
35: <td width="12%" align="left"><b>Skill: </b></td>
36: <td width="12%" align="left">
37: <xsl:value-of select="@skill"/>
38: </td>
39: <td width="12%" align="left"><b>Handicap: </b></td>
40: <td width="12%" align="left">
41: <xsl:value-of select="@handicap"/>
42: </td>
43: <td width="12%" align="left"><b>Clubs: </b></td>
44: <td width="40%" align="left">
45: <xsl:value-of select="@clubs"/>
46: </td>
47: </tr>
48: <tr>
49: <td colspan="6"> </td>
50: </tr>
51: <tr class="blackText">
52: <td colspan="6" class="largeBlackText">
53: Favorite Courses
54: </td>
55: </tr>
56: <tr>
57: <td colspan="2"><b>City: </b></td>
58: <td colspan="2"><b>State: </b></td>
59: <td colspan="2"><b>Course: </b></td>
60: </tr>
61: <xsl:apply-templates select="favoriteCourses"/>
62: </table>
63: <p/>
64: </xsl:template>
65: <xsl:template match="name">
66: <tr>
67: <td colspan="6" class="largeYellowText" bgcolor="#02027a">
68: <xsl:value-of select="firstName"/> 
69: <xsl:value-of select="lastName"/>
70: </td>
71: </tr>
72: </xsl:template>
73: <xsl:template match="favoriteCourses">
74: <xsl:apply-templates/>
75: </xsl:template>
76: <xsl:template match="course">
77: <tr class="blackText">
78: <td colspan="2" align="left">
79: <xsl:value-of select="@city"/>
80: </td>
81: <td colspan="2" align="left">
82: <xsl:value-of select="@state"/>
83: </td>
84: <td colspan="2" align="left">
85: <xsl:value-of select="@name"/>
86: </td>
87: </tr>
88: </xsl:template>
89: </xsl:stylesheet>
To transform the XML document shown in Listing 7.1 using the XSLT document
shown in Listing 7.2, the code shown in Listing 7.3 can be used:
Listing 7.3 Using the XslTransform Class
1: <%@ Import Namespace="System.Xml" %>
2: <%@ Import Namespace="System.Xml.Xsl" %>
3: <%@ Import Namespace="System.Xml.XPath" %>
4: <script language="C#" runat="server">
5: public void Page_Load(Object sender, EventArgs E) {
6: string xmlPath = Server.MapPath("listing7.1.xml");
7: string xslPath = Server.MapPath("listing7.2.xsl");
8:
9: //Instantiate the XPathDocument Class
10: XPathDocument doc = new XPathDocument(xmlPath);
11:
12: //Instantiate the XslTransform Class
13: XslTransform transform = new XslTransform();
14: transform.Load(xslPath);
15:
16: //Custom format the indenting of the output document
17: //by using an XmlTextWriter
18: XmlTextWriter writer = new XmlTextWriter(Response.Output);
19: writer.Formatting = Formatting.Indented;
20: writer.Indentation=4;
21: transform.Transform(doc, null, writer);
22: }
23: </script>
On executing the code in Listing 7.3, the XML document will magically be transformed
into HTML that can be rendered in a browser. The result of this transformation
is shown in Figure 7.2.
Figure 7.2
Transforming an XML document into HTML.
The code shown in Listings 7.2 and 7.3 may look quite foreign to you at this
point. Don't let that worry you, though, because each portion of the code
will be broken down to show how the different pieces work together. In addition
to covering the XSLT language, the examples that follow also demonstrate XPath
expressions as a point of review. For a detailed explanation of XPath, refer to
Chapter 3, "XPath, XPointer, and XLink." Before examining the .NET
classes involved in transforming XML to other structures, let's first
examine what pieces are involved in constructing an XSLT document.
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.
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.
XSLT Functions
In Chapter 3, "XPath, XPointer, and XLink," you were introduced to
several functions built in to the XPath language. Because XSLT relies on XPath
for creating expressions that locate nodes in an XML document, these functions
are available for use in your XSLT documents. In addition to these functions,
the XSLT language adds a few more. Table 7.3 shows these functions and provides
a description and example of using them in XSLT documents.
Table 7.3 XSLT Functions
|
Function
|
Description
|
|
current()
|
Returns a node set that contains the current node as its only member.
This function exists to help identify the current node when it is different
from the context node. In previous examples you have seen that the context
node can be represented by the . character. For example, to write
out the value of the context node being processed by a template, you can
do the following:
<xsl:value-of select="."/>
|
|
|
This code will provide the same result:
<xsl:value-of select="current()"/>
|
|
|
At this point in the template, the current node is the same as the context
node. When used within the square brackets of a predicate ([ and ]), however,
the current node is often different from the context node. An example
of this is shown next:
|
|
|
<xsl:template match="/">
<xsl:variable name="golferVar"
select="//golfer"/> <xsl:text>Other Comparable Golfers: </xsl:text>
<xsl:for-each select="$golferVar">
<xsl:if test="$golferVar[./@skill=
current()/@skill]"> <xsl:value-of select="$golferVar/name/
lastName"/> </xsl:if>
</xsl:for-each>
</xsl:template>
|
|
|
The predicate statement ([./@skill = current()/ @skill]) will
compare the value of the context node's skill attribute
(in this case, the golfer node being looped through) to the golfer
node being handled by the template (the current node). This is an example
of how the context node changes during a loop. However, the current node
is associated with the node being handled by the template and stays the
same during the looping process.
|
|
document(object,node-set?)
|
Although XSLT makes it easy to transform a single XML document into
other structures, what happens if the result tree needs to be created
from more than just one XML document? Using the document() function,
other XML documents can be pulled into an XSLT document for processing.
This can be useful in many situations, including when one XML document
contains a presentation structure and another contains the data to be
plugged into that structure. Using the document() function, these
types of activities can be accomplished relatively easily. An example
of using the document() function is shown next:
<xsl:variable name="xmlDoc"
select="document('data.xml')"/>
|
|
|
This will load data.xml into the variable named xmlDoc.
Referencing elements within the external document can be accomplished
in the following manner: <xsl:value-of select="$xmlDoc//root/data/@id"/>
|
|
|
Although the preceding document() function example targets
an external document, this function can also be used to target a node-set
within the main XML document. This can be useful when you want to work
with a lookup table structure embedded in the XML document.
|
|
|
Aside from providing a string URI value, a node-set can be passed to
access the remote document, as shown next:
|
|
|
For the XML document:
<golfers>
<golfer
href="http://www.ilikegolf.com/golfers/golfers.xm
l"/>
<handicaps href="handicapsWest.xml"/>
<handicaps href="handicapsEast.xml"/>
</golfer>
</golfers>
|
|
|
The following XSLT document creates a result tree containing the handicap nodes from the referenced documents:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<golfers>
<xsl:apply-templates
select="//handicaps"/>
</golfers>
</xsl:template>
<xsl:template match="handicaps">
<xsl:copy of select="document(@href)//handicap"/>
</xsl:template>
</xsl:stylesheet>
|
|
element-available(string)
|
This function is useful if you are writing an XSLT document that may
be processed using different XSLT processors. Before using elements that
you know may not be supported, a check can be made to see if the processor
does indeed support the element in question. This is helpful when testing
for XSLT elements found in a later version of XSLT or in checking for
vendor specific elements. The function will return a Boolean value:
<xsl:if test="element-
available('xsl:someNewElement')">
<xsl:someNewElement>HI!</xsl:someNewElement>
</xsl:if>
|
|
format-number(number, string,string?)
|
This function converts numbers to a string using a format pattern supplied
in the second parameter. Here are some examples of using this function:
|
|
|
The following function call returns 5,351:
format-number(5351,"#,###")
|
|
|
The following function call returns 5351.00:
format-number(5351, "#.00")
|
|
|
The following function call returns 53.5100:
format-number(53.51, "#.0000")
|
|
|
The following function call returns 0053.5100:
format-number(53.51, "0000.0000")
|
|
|
The following function call returns 0053.51:
format-number(53.51, "0000.####")
|
|
|
The following function call returns 53.6:
format-number(53.56, "0.0")
|
|
function-available(string)
|
Similar to element-available, although this function checks whether
specific functions are supported by the XSLT processor.
|
|
generate-id(node-set?)
|
This function generates a string that is guaranteed to uniquely identify
a node. The same string will always be returned for the same node. The
string that is generated will vary from processor to processor and will
start with an alphabetic character.
|
|
|
<xsl:attribute name="id">
<xsl:value-of select="generate-id(.)"/>
</xsl:attribute>
|
|
key(string,object)
|
This function is used in conjunction with the xsl:key element
to return a node-set that has a specific name and value defined by the
xsl:key statement. For example, the following code shows how
the xsl:key element can define a key:
<xsl:key name="course-city" match="course" use="@city"/>
|
|
|
This key can then be accessed by using the key() function:
<xsl:for-each select="key('course-city', 'Pinetop')">
...
</xsl:for-each>
|
|
system-property(string)
|
This function returns the value of the system property identified by
the name passed as the argument. Three different system properties must
be supported by a compliant XSLT processor, including xsl:version,
xsl:vendor, and xsl:vendor-url. An example of using
this function follows:
<xsl:value-of select="system-property ('xsl:version')"/>
|
|
unparsed-entity-uri()
|
This function returns declarations of unparsed entities in the DTD of
the source XML document.
|
|
|
Given the entity declaration:
<!ENTITY clubs SYSTEM
"http://www.lottaclubs.com/clubs.txt">
|
|
|
The following code:
<xsl:value-of select="unparsed-entity-uri('clubs')"/>
|
|
|
Would return a value of http://www.lottaclubs.com/clubs.txt.
|
.NET Classes Involved in Transforming XML
Now that you've seen the different XSLT elements and functions that are
at your disposal, it's time to learn about what classes in the .NET
framework can be used in your ASP.NET applications when XSL transformations are
necessary. After all, XSLT is simply a text-based language that is of little
utility without an XSLT processor.
Several classes built in to the System.Xml assembly can be used when
transforming XML into other structures via XSLT. Back in Listing 7.3, a preview
of a few of these classes interacting with each other was given that demonstrated
how to transform an XML document into HTML. In this section you'll learn
more about these classes and a few others so that you are fully armed with everything
you need to know to use XSLT in your ASP.NET applications. Figure
7.3 presents an overview of the main classes used in XSL transformations.
Figure 7.3
.NET Classes involved in XSL transformations.
Table 7.4 provides a description of each of these classes.
Table 7.4 .NET Classes Used in XSL Transformations
|
Class
|
Description
|
|
XmlDocument
|
The XmlDocument class implements the IXPathNavigable
interface and extends the XmlNode class, which provides the capability
to create nodes within a DOM structure. This class was discussed in Chapter
6. Because the XmlDocument class provides node-creation capabilities,
it will not provide the fastest throughput in XSL transformations. However,
in cases where a DOM structure must be edited first before being transformed,
this class can be used.
|
|
XmlDataDocument
|
The XmlDataDocument class extends the XmlDocument
class. The XmlDataDocument class can be used when working with
DataSets in ADO.NET. Chapter 8, "Leveraging ADO.NET's
XML Features Using ASP.NET," covers this class in more depth.
|
|
XPathDocument
|
The XPathDocument class implements the IXPathNavigable
interface like the XmlDocument class does. However, the XPathDocument
class does not extend the XmlNode class (as the XmlDocument
class does) and therefore provides the fastest option for transforming
XML via XSLT. You'll see this class used in the examples that follow.
|
|
|
Because the XPathDocument class implements the IXPathNavigable
interface, it is able to leverage features built in to the abstract XPathNavigator
class (which, in turn, uses the XPathNodeIterator abstract class
for iteration over node-sets) to provide cursor-style access to XML data,
resulting in fast and efficient XSL transformations.
|
|
XslTransform
|
The XslTransform class is used to transform XML data into other
structures. Using the XslTransform class involves instantiating
it, loading the proper style sheet with the Load() method, and
then passing specific parameters to its Transform() method. This
process will be detailed in the next few sections.
|
|
| |