Softcover - 484 Pages
Published by Sams
ISBN: 0672320398 Buy a Copy
Chapter 7: Transforming XML with XSLT and ASP.NET
In This Chapter:
What Is XSLT
The Transformation Process
Getting Your Feet Wet with XSLT
The XSLT Language
XSLT Functions
.NET Classes Involved in Transforming XML
Creating a Reusable XSLT Class
What Is XSLT?
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:
This chapter teaches you how to perform this type of transformationas
well as many othersby covering the following topics:
The transformation process
The XSLT language
.NET classes involved in transforming XML
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.
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.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.
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.
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 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:
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.
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 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:
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 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:
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:
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:
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.
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.
Listing 7.8 Instantiating an XPathDocument Class
<%@ Import Namespace="System.Xml" %>
<%@ Import Namespace="System.Xml.XPath" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Text" %>
<script language="C#" runat="server">
public void Page_Load(Object sender, EventArgs E) {
string xmlPath = Server.MapPath("listing7.1.xml");
string xslPath = Server.MapPath("listing7.2.xsl");
FileStream fs = new FileStream(xmlPath,FileMode.Open,
FileAccess.Read);
StreamReader reader = new StreamReader(fs,Encoding.UTF8);
XmlTextReader xmlReader = new XmlTextReader(reader);
//Instantiate the XPathDocument Class
XPathDocument doc = new XPathDocument(xmlReader);
Response.Write("XPathDocument successfully created!");
//Close Readers
reader.Close();
xmlReader.Close();
}
</script>
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.
Figure 7.5
The XSLT-generated results of the golfer selection.
To build this application, code-behind techniques were used in the ASP.NET
page. If you're not familiar with this mechanism in ASP.NET coding, it
allows the actual program code to be stored separately from the visual portion
(the HTML) found in the ASP.NET page. The technique of placing all the code
(programming code and HTML) into one ASP.NET page shown in many places
throughout the book was used simply to make listings easier to read and follow.
In practice, however, it's highly recommended that you leverage code-behind
techniques to keep your ASP.NET code more maintainable.
For this example, a file named xsltGolfer.aspx.cs contains all the
programming code for the listings that follow, and xsltGolfer.aspx
contains the HTML. The XSLT style sheet used for the application is named
xsltGolfer.xsl. Let's start by examining what code is executed
when the ASP.NET page first loads (the Page_Load event). As you'll
see in Listing 7.10, this code takes care of loading all the firstName
element values found in the XML document into a drop-down box.
Listing 7.10 The Page_Load Event and FillDropDown() Method
(xsltGolfer.aspx.cs)
You can see that the XmlTextReader and XmlNameTable classes
are used to efficiently parse the XML data and add it to the drop-down box
(ddGolferName). Both classes were discussed in Chapter 5, "Using
the XmlTextReader and XmlTextWriter Classes in
ASP.NET."
After the user selects a specific golfer from the drop-down box and clicks
the button, the btnSubmit_Click event is fired. The code within this
event takes care of getting the selected golfer name value from the drop-down
box and passes it into the XSLT style sheet by using the XsltArgumentList
class. The style sheet then takes care of transforming the selected golfer's
XML data into HTML, as shown earlier in Figure
7.5. Listing 7.11 shows the code involved in this process.
Listing 7.11 Transforming XML to HTML Using XSLT
(xsltGolfer.aspx.cs)
Although this doesn't show much in the way of new classes, it does show
how the different classes discussed in earlier sections can be tied together to
create an ASP.NET application that leverages XML and XSLT.
Using Extension Objects with XSLT
While looking through the methods exposed by the XsltArgumentList
class back in Table 7.8, you may have wondered how the extension object methods
could be used to enhance XSLT/ ASP.NET applications. Using these methods is
surprisingly easy and can provide your XSLT style sheets with even more power
and flexibility. Keep in mind that by using extension objects in XSLT, you may
render your XSLT unusable on other platforms or by other languages simply
because extensions are not a part of the XSLT 1.0 specification (the XSLT 1.1
working draft does include extension elements and functions, however). If your
application will be the only one that uses a particular XSLT style sheet and you
need additional functionality not in the XSLT 1.0 specification, extension
objects may be the answer. Some other benefits of using extension objects
include:
Methods on classes within other namespaces (other than System
namespaces) can be called.
Extension functions allow better encapsulation and reuse of
classes.
Style sheets can be kept smaller and more maintainable.
What exactly is an extension object? Think of it as an external class that
can be referenced and used within an XSLT style sheet. By using extension
objects, you can get the current date and time, query a database to do a lookup
based on a value found in the XML source document, hit a Web service, or trigger
another application to begin running. All of this and much more can be done from
within an XSLT style sheet.
To see how this works in practice, the next code sample shown in Listing 7.12
builds on the previous one shown in Listing 7.11 to add the capability to write
out the current date/time of the server from a specific location within the
style sheet. Let's first look at the class that will be instantiated and
used as an extension object.
Listing 7.12 The Date/Time Extension Class
(xsltDateObject.cs)
1: namespace XsltTransformation.ExternalObjects {
2: using System;
3:
4: public class XsltDateTime {
5: DateTime _date;
6: public XsltDateTime() {
7: _date = DateTime.Now;
8: }
9: public DateTime GetDateTime() {
10: return _date;
11: }
12: }
13: }
This class (named XsltDateTime) does nothing more than return the
current system date and time. It must be instantiated within an ASP.NET page and
then added to the external object collection of the XsltArgumentList
class.
You may be wondering if it would be easier to pass the date and time into the
style sheet using a regular XSLT parameter. The answer is "yes"; it
would be easier because no external objects would be needed from within the
style sheet. However, by calling the extension object from within the XSLT style
sheet, a more up-to-date date/time value will be returned (assuming that
accuracy matters in the application). And let's face it, the demo code
wouldn't be as cool if a regular XSLT parameter was used, especially
because you know all about those at this point!
Listing 7.13 demonstrates how to pass an extension object into an XSLT style
sheet using the XsltArgumentList class. The lines of code relating to
the extension object use are shown in bold.
Listing 7.13 Adding an External Object to the XsltArgumentList Class
(xsltExtension.aspx.cs)
1: protected void btnSubmit_Click(object sender, System.EventArgs e) {
2:
3: string xslPath = Server.MapPath("xsltExtension.xsl");
4: XsltDateTime xsltExtObj = new XsltDateTime(); //The Extension Object
5: XmlTextReader xmlReader = null;
6: StringBuilder sb = new StringBuilder();
7: StringWriter sw = new StringWriter(sb);
8:
9: try {
10: xmlReader = new XmlTextReader(xmlPath);
11:
12: //Instantiate the XPathDocument Class
13: XPathDocument doc = new XPathDocument(xmlReader);
14:
15: //Instantiate the XslTransform Classes
16: XslTransform transform = new XslTransform();
17: transform.Load(xslPath);
18:
19: //Add Parameters and Extension Object to the Collection
20: XsltArgumentList args = new XsltArgumentList();
21: args.AddParam("golferName","",
22: this.ddGolferName.SelectedItem.Value);
23: //Add the namespaceURI and object to the object collection
24: args.AddExtensionObject("urn:xsltExtension-DateTime",
25: xsltExtObj);
26:
27: //Call Transform() method
28: transform.Transform(doc, args, sw);
29:
30: //Hide ASP.NET Panels
31: this.pnlSelectGolfer.Visible = false;
32: this.pnlTransformation.Visible = true;
33: this.divTransformation.InnerHtml = sb.ToString();
34: }
35: catch (Exception excp) {
36: Response.Write(excp.ToString());
37: }
38: finally {
39: xmlReader.Close();
40: sw.Close();
41: }
42: }
The XSLT style sheet obviously needs to be able to reference the
XsltDateTime object that is passed in and call its
GetDateTime() method. This is accomplished by adding the proper
namespace prefix and URI into the style sheet. For this example, a namespace URI
of urn:xsltExtension-DateTime is used along with a namespace prefix of
dateTimeObj. Any namespace URI can be used as long as it is consistent
between the ASP.NET page and the XSLT style sheet. Listing 7.14 shows the
complete style sheet and highlights where the external object is referenced and
used.
Listing 7.14 Calling External Objects Within an XSLT Style Sheet
(xsltExtension.xsl)
How is the GetDateTime() method called on the XsltDateTime
object that is passed into the style sheet? This is accomplished by referencing
the namespace prefix (dateTimeObj) associated with the namespace URI
assigned to the object in the ASP.NET page (urn:xslt
Extension-DateTime). The prefix is declared in line 3 and then used to call
the GetDate Time() method in line 75. Although this is a fairly
straightforward example of using external objects within XSLT style sheets, much
more complicated functionality, such as querying databases or calling Web
services, can be accomplished with XSLT external objects.
Before this chapter ends, the next section will show how to create a reusable
XSLT class that can be used within ASP.NET applications and demonstrate the
asp:Xml Web control.
Creating a Reusable XSLT Class
By now you should feel comfortable working with the different classes within
the System.Xml assembly that can be used to perform XSL
transformations. However, wouldn't it be nice if you could write a generic
class that doesn't require any knowledge of the XPathDocument or
XslTransform classes (and their supporting classes) to be used? Not
only would such a class provide more productive programming, but it would also
allow your ASP.NET applications to leverage all the benefits offered by
following object-oriented programming techniques.
The code in Listing 7.15 shows a generic class that allows an ASP.NET
programmer to transform an XML document using XSLT without any knowledge of
XSLT-specific classes. This code leverages well-known classes such as
HashTable to accomplish the transformation.
Listing 7.15 A Reusable XSLT Transformation Class
(xsltTransform.cs)
1: namespace XsltTransformation {
2:
3: using System;
4: using System.Xml;
5: using System.Xml.XPath;
6: using System.Xml.Xsl;
7: using System.Collections;
8: using System.IO;
9: using System.Text;
10:
11: /// <summary>
12: /// A generic XSL Transformation Class for use in ASP.NET pages
13: /// </summary>
14: public class XsltTransform {
15:
16: public static string TransformXml(string xmlPath,string xsltPath,
17: Hashtable xsltParams,Hashtable xsltObjects) {
18:
19: StringBuilder sb = new StringBuilder();
20: StringWriter sw = new StringWriter(sb);
21: try {
22: XPathDocument doc = new XPathDocument(xmlPath);
23: XsltArgumentList args = new XsltArgumentList();
24: XslTransform xslDoc = new XslTransform();
25: xslDoc.Load(xsltPath);
26:
27: //Fill XsltArgumentList if necessary
28: if (xsltParams != null) {
29: IDictionaryEnumerator pEnumerator =
30: xsltParams.GetEnumerator();
31: while (pEnumerator.MoveNext()) {
32: args.AddParam(pEnumerator.Key.ToString(),"",
33: pEnumerator.Value);
34: }
35: }
36: if (xsltObjects != null) {
37: IDictionaryEnumerator pEnumerator =
38: xsltObjects.GetEnumerator();
39: while (pEnumerator.MoveNext()) {
40: args.AddExtensionObject(pEnumerator.Key.ToString(),
41: pEnumerator.Value);
42: }
43: }
44: xslDoc.Transform(doc,args,sw);
45: return sb.ToString();
46: }
47: catch (Exception exp) {
48: return exp.ToString();
49: }
50: finally {
51: sw.Close();
52: }
53: }
54: } //XsltTransform
55: } // XsltTransformation namespace
Having a reusable class for XSL transformations results in much cleaner
ASP.NET code, as Listing 7.16 shows.
Listing 7.16 Using the XsltTransform Class in ASP.NET
(xsltTransform.aspx)
public void Page_Load(object sender, System.EventArgs e) {
string xmlPath = Server.MapPath("Listing7.1.xml");
string xslPath = Server.MapPath("xsltExtension.xsl");
//Create the External Object
XsltDateTime xsltExtObj = new XsltDateTime();
//Create Hashtables to hold params and external objects
//If none are needed, pass null into the TransformXml() method instead
Hashtable xsltParams = new Hashtable();
Hashtable xsltObjects = new Hashtable();
//Fill the Hashtables with the params and external objects
xsltParams.Add("golferName","Heedy");
xsltObjects.Add("urn:xsltExtension-DateTime",xsltExtObj);
//Call the custom XsltTransform class's TransformXml method
string xsl = XsltTransform.TransformXml(xmlPath,xslPath,
xsltParams,xsltObjects);
Response.Write(xsl);
}
As this book was going to press, I wrote a new article demonstrating how the
techniques shown in Listing 7.15 can be used to create an ASP.NET server control
geared at targeting multiple devices using XML and XSLT. This article will
appear in the premier issue (September 2001) of Visual Studio Magazine
(http://www.devx.com), and the
code can be downloaded from the DevX site or from the CodeBank section of
http://www.TomorrowsLearning.com.
The Asp:Xml Web Control
The .NET framework also comes with a prebuilt Web control that can be used
for doing simple XSL transformations in ASP.NET pages. This control allows the
XML document source and XSLT style sheet source to be set using attributes.
Listing 7.17 shows how the control is used.
In situations where the values of the DocumentSource and/or
TransformSource attributes are not known until runtime, they can be
assigned dynamically, as Listing 7.18 shows.
Listing 7.18 Dynamically Assigning Source Documents to the asp:Xml Web
Control
Although XSLT is a very big topic that can't possibly be covered in a
single chapter, you have been exposed to some of the more important aspects of
the language that will get you started transforming XML documents in ASP.NET
applications. XSLT provides a cross-platform, language-independent solution
that can used to transform XML documents into a variety of structures.
The .NET platform provides several classes developed specifically for doing
XSLT transformations, including the XslTransform and
XPathDocument classes. By using these and other classes, you have the
ability to leverage the complete XSLT language in your ASP.NET applications.
In the next chapter, you'll be provided with an in-depth look at the
different XML features found in ADO.NET.