Mark Wilson I am the creator of TopXML. I am available for international and local (Australia) contracts. I am a Solution Architect/Business Analyst. I have worked in IT in several countries (NZ, Australia, South Africa, UK) building and training teams for government and very large non-governmental organizations. I am ex-Microsoft Consulting Services. I wrote the first book on Microsoft XML published in 2000 called XML Programming with VB and ASP. Most recently I have been building tools for the SEO industry. Ask me for a 37 point SEO health-checkup for your website.
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:
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.
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.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.
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.
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:
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.
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.
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.
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.
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.
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 46–74 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:
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 57–59).
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 64–94 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.
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).
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.
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 6–10 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 13–24. 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:
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:
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:
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:
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:
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:
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 documents—all 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:
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.
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:
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:
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:
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:
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.
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:
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:
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.
XsltArgumentList
The XsltArgumentList class is used to provide parameter
values to xsl:param elements defined in an XSLT style
sheet. It can be passed as an argument to the XslTransform
class's Transform() method.
The XPathDocument Class
Before looking at the XslTransform class, you need to
familiarize yourself with the XPathDocument class. To use
this class you must reference the System.Xml.XPath
namespace in your ASP.NET applications. As mentioned in Table 7.4,
this class provides the most efficient way to transform an XML
document using XSLT because it provides a read-only representation
of a DOM structure. The XPathDocument class is very simple
to use because it has only one XML-related method named
CreateNavigator() that can be used to create an instance of
the XPathNavigator class. However, it does have several
constructors that are worth mentioning. Table 7.5 shows the
different constructors.
Table 7.5 XPathDocument Constructors
Constructor
Description
Public XPathDocument(XmlReader, XmlSpace)
Accepts an XmlReader as well as an XmlSpace
enumeration.
Public XPathDocument(XmlReader)
Accepts an XmlReader.
Public XPathDocument(TextReader)
Accepts a TextReader.
Public XPathDocument(Stream)
Accepts a Stream.
Public XPathDocument(string,XmlSpace)
Accepts the string value of the path to an XML document and an
XmlSpace enumeration.
Public XPathDocument(string)
Accepts the string value of the path to an XML document.
Listing 7.3 used the last constructor shown in Table 7.5 that
accepts the path to the XML document to transform. You could also
load the XPathDocument with XML data contained in a
Stream (a FileStream for instance), an
XmlReader, or a TextReader. Having these different
constructors offers you complete control over how transformations
will be carried out in your ASP.NET applications. Which one you use
will depend on how you choose to access your application's XML
documents. Listing 7.8 instantiates an XPathDocument class
by passing in an XmlTextReader object.
Running the code shown in Listing 7.5 will write out
"XPathDocument successfully created!" to the browser. You'll
certainly agree that because it has simply readied the XML document
for transformation, this code doesn't buy you much. To actually
transform the XML document using XSLT, you'll need to use another
class named XslTranform.
The XslTransform Class
The XslTransform class is found in the
System.Xml.Xsl namespace. Using it is as easy as instantiating
it, loading the XSLT document, and then calling its
Transform() method. Tables 7.6 and 7.7 show the different
properties and methods found in the XslTransform
class.
Table 7.6 XslTransform Class Properties
Property
Description
XmlResolver
The XmlResolver property can be used to specify a
resolver class used to resolve external resources. For example, it
can be used to resolve resources identified in xsl:include
elements. If this property is not set, the XslTransform
class will use the relative path of the supplied XSLT style sheet
to resolve any included style sheets.
Chapter 5 showed an example of using the XmlUrlResolver
class to access authenticated documents.
Table 7.7 XslTransform Class Methods
Method
Description
Load()
Loads an XSLT document. This method can accept an
XmlReader, a document URL, or a variety of other objects.
Transform()
The Transform() method is overloaded and can therefore
accept a variety of parameters. The most common form of the method
that you'll likely use in your ASP.NET applications is shown next
(check the .NET SDK for the other overloaded versions of the
method):
In the next section, you'll see how you can pass in parameter
values to XSLT style sheets using the XsltArgumentList
class.
The XsltArgumentList Class
Earlier in the chapter, you saw how parameters could be used in
XSLT style sheets through the xsl:param element. As a
quick refresher, this XSLT element must have a name
attribute and optional select attribute:
<xsl:param name="customerID" select="'ALFKI'"/>
XSLT parameters allow your ASP.NET applications to pass in
values needed by the style sheet to properly process the source XML
document. In this section you'll see how to create an
XsltArgumentList class and add parameter name/value pairs to
it. It can also be used with extension objects. Table 7.8 shows the
different methods available on the XsltArgumentList class
(it has no properties).
Table 7.8 XsltArgumentList Methods
Method
Description
AddExtensionObject(namespaceURI, object)
Allows an extension object to be added to the collection of
extension objects. The namespace URI can be used to remove or
retrieve an object from the collection using either
GetExtensionObject() or RemoveExtension Object().
Similar to the addObject() method found on the
IXslProcessor interface in MSXML3.
AddParam(name,namespaceURI,value)
Allows a parameter name/value pair to be added to the collection
of parameters. If you do not want to assign a namespaceURI, the URI
can be empty strings. It is similar to the addParameter()
method found on the IXslProcessor interface in MSXML3.
GetExtensionObject(namespaceURI)
Allows an extension object to be retrieved from the collection
of extension objects based on the namespaceURI assigned to the
object in the AddExtensionObject() method.
GetParam(name,namespaceURI)
Allows a parameter name/value pair to be retrieved from the
collection of parameters based on a name and namespaceURI
combination. If a parameter name has no assigned namespaceURI, the
URI can be empty strings.
RemoveExtensionObject(namespaceURI)
Allows an extension object to be removed from the collection of
extension objects based on the namespaceURI assigned to the object
in the AddExtensionObject() method.
RemoveParam(name,namespaceURI)
Allows a parameter name/value pair to be removed from the
collection of parameters based on a name and namespaceURI
combination. If a parameter name has no assigned namespaceURI, the
URI can be empty strings.
The method that you'll use most frequently among those listed in
Table 7.8 is the AddParam() method. This method accepts
the name of the parameter, a namespace URI (optional), and the
value of the parameter. The following example shows how to add a
parameter named golferName to the
XsltArgumentList collection:
XSLT Code:
<xsl:param name="golferName"/>
ASP.NET Code:
XsltArgumentList args = new XsltArgumentList();
args.AddParam("golferName","","Dan");
This code allows the XSLT parameter named golferName to
be assigned a value of Dan. Although the value of
Dan was hard-coded into the AddParam() method, it
could just as easily be dynamically pulled from a text box or
drop-down box, as you'll see in the next example. Because the
XsltArgumentList class relies on the HashTable class
behind the scenes, multiple parameter name/value pairs can be added
and stored.
After an XsltArgumentList class has been instantiated
and filled with the proper name/value pairs, how do the parameters
in the XSLT style sheet get updated with the proper values? The
answer is to pass the XsltArgumentList into the
XslTransform class's Transform() method, as shown
next:
//Create the XPathDocument object
XPathDocument doc = new XPathDocument(Server.MapPath("Listing7.1.xml"));
//Create the XslTransform object
XslTransform xslDoc = new XslTransform();
xslDoc.Load(Server.MapPath("Listing 7.4.xsl"));
//Create the XsltArgumentList object
XsltArgumentList args = new XsltArgumentList();
args.AddParam("golferName","","Dan");
//Perform the transformation - pass in the parameters in the XsltArgumentList
xslDoc.Transform(doc,args,Response.Output);
In the next section you'll be presented with an ASP.NET
application that does this task.
Putting It All Together
You've now seen the main XSLT classes built in to the .NET
framework. In this section you'll see how these can be used to
build a simple ASP.NET application that allows a user to select a
specific golfer's information from an XML document. After the
golfer is chosen, XSLT will be used along with the
XPathDocument, XslTransform, and
XsltArgumentList classes to display the golfer's information.
Figures 7.4
and 7.5 show
screen shots of the two pages involved in the sample XSLT
application.