XML for ASP.NET DevelopersBy Dan Wahlin Softcover – 484 Pages |
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:
<?xml version="1.0"?> <root> <row id="1" fname="Dan" lname="Wahlin"/> <row id="2" fname="Heedy" lname="Wahlin"/> <row id="3" fname="Danny" lname="Wahlin"/> <row id="4" fname="Jeffery" lname="Wahlin"/> </root>
Using XSLT, this document can be transformed into a structure that the application is better suited to work with:
<?xml version="1.0"?> <root> <row> <id>1</id> <fname>Dan</fname> <lname>Wahlin</lname> </row> <row> <id>2</id> <fname>Heedy</fname> <lname>Wahlin</lname> </row> <row> <id>3</id> <fname>Danny</fname> <lname>Wahlin</lname> </row> <row> <id>4</id> <fname>Jeffery</fname> <lname>Wahlin</lname> </row> </root>
This chapter teaches you how to perform this type of transformation—as well as many others—by 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.
XSLT Templates
Before looking more closely at how to build XSLT documents, it’s important that you understand what the building blocks of these documents are. Although XSLT stands for Extensible Stylesheet Language Transformations, an alternative name for it could potentially be Extensible Template Language Transformations. Why? The answer is because of its reliance on templates to process and create a particular output structure. The W3C provides the following statement about templates:
A stylesheet contains a set of template rules. A template rule has two parts: a pattern which is matched against nodes in the source tree and a template which can be instantiated to form part of the result tree. This allows a stylesheet to be applicable to a wide class of documents that have similar source tree structures.
If you have ever used templates in Excel, Word, or PowerPoint, you know that they provide a basic structure that can be reused for specific purposes. For example, every time you submit an expense report, you may be accustomed to filling out a template in Word that is designed for this purpose. The template likely has specific form fields built in so that every expense report being submitted looks the same. There may be other templates that are used for purchase orders. The point is that the templates are geared to match up with a specific task, such as creating an expense report, a purchase order, or some other activity.
Templates in XSLT function in much the same way, except that they are geared to match up with nodes in an XML document. XSLT templates provide a way to process and structure data contained within elements and attributes in the source XML document. Their basic purpose is to provide a template structure that can be processed when a particular node in the source XML document is discovered.
So how do templates work? The XSLT processor described earlier is provided with two tree structures to walk through. The first is the structure for the source XML document and the second is the XSLT document itself. After these two structures are provided, the XSLT processor attempts to match element or attribute names found in the XML document with templates contained in the XSLT tree structure. This matching process uses XPath expressions that are embedded within the XSLT document. When a node found within the XML document matches a template in the XSLT document, that template is processed.
Processing of templates found within an XSLT document normally starts with a template that matches the root node of the XML document and proceeds down to its children. When a template is processed, the output is added to the third tree structure mentioned earlier that is used in building the output document.
Templates offer an efficient way to process a variety of XML document structures and are very efficient in cases where an XML document contains repetitive items. Each time an element, attribute, text node, and so on is found, it is matched up with the appropriate template via XPath expressions. If a given node does not have a matching template, no processing will occur on it, and the next section of the XML document is processed. In cases where a matching node is found, the template takes care of generating the proper output structure based on data/nodes contained within the node.
So that you can see templates in action, the next section introduces you to a simple XSLT document. The sections that follow describe in greater detail how to use templates and other parts of the XSLT language.
Getting Your Feet Wet with XSLT
In this section we’ll examine a simple XSLT document that transforms XML into HTML for display in a browser. The sections that follow show how XSLT can transform XML into many formats other than HTML. This example represents a common task that you will likely use when developing ASP.NET applications that require the presentation of XML data within a browser. Listing 7.1 shows an XML document that contains information about different golfers.
Listing 7.1 Golfers XML Document
1: <?xml version="1.0" ?> 2: <golfers> 3: <golfer skill="excellent" handicap="10" clubs="Taylor Made" id="1111"> 4: <name> 5: <firstName>Heedy</firstName> 6: <lastName>Wahlin</lastName> 7: </name> 8: <favoriteCourses> 9: <course city="Pinetop" state="AZ" name="Pinetop Lakes CC"/> 10: <course city="Phoenix" state="AZ" name="Ocotillo"/> 11: <course city="Snowflake" state="AZ" name="Silver Creek"/> 12: </favoriteCourses> 13: </golfer> 14: <golfer skill="moderate" handicap="12" clubs="Taylor Made" id="2222"> 15: <name> 16: <firstName>Dan</firstName> 17: <lastName>Wahlin</lastName> 18: </name> 19: <favoriteCourses> 20: <course city="Pinetop" state="AZ" name="Pinetop Lakes CC"/> 21: <course city="Pinetop" state="AZ" name="White Mountain CC"/> 22: <course city="Springville" state="UT" name="Hobble Creek"/> 23: </favoriteCourses> 24: </golfer> 25: </golfers>
Listing 7.2 presents an XSLT document that can be used to transform the XML just shown into HTML. The different elements used in this document are discussed later.
Listing 7.2 Golfers XSLT Document
1: <?xml version="1.0" ?> 2: <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 3: version="1.0"> 4: <xsl:output indent="yes" method="html"/> 5: <xsl:template match="/"> 6: <html> 7: <head> 8: <style type="text/css"> 9: .blackText {font-family:arial;color:#000000;} 10: .largeYellowText {font-family:arial;font-size:18pt; 11: color:#ffff00;} 12: .largeBlackText {font-family:arial;font-size:14pt; 13: color:#000000;} 14: .borders {border-left:1px solid #000000; 15: border-right:1px solid #000000; 16: border-top:1px solid #000000; 17: border-bottom:1px solid #000000;} 18: </style> 19: </head> 20: <body bgcolor="#ffffff"> 21: <span class="largeBlackText"><b>Golfers: </b></span> 22: <p/> 23: <xsl:apply-templates/> 24: </body> 25: </html> 26: </xsl:template> 27: <xsl:template match="golfers"> 28: <xsl:apply-templates/> 29: </xsl:template> 30: <xsl:template match="golfer"> 31: <table class="borders" border="0" width="640" cellpadding="4" 32: cellspacing="0" bgcolor="#efefef"> 33: <xsl:apply-templates select="name"/> 34: <tr class="blackText"> 35: <td width="12%" align="left"><b>Skill: </b></td> 36: <td width="12%" align="left"> 37: <xsl:value-of select="@skill"/> 38: </td> 39: <td width="12%" align="left"><b>Handicap: </b></td> 40: <td width="12%" align="left"> 41: <xsl:value-of select="@handicap"/> 42: </td> 43: <td width="12%" align="left"><b>Clubs: </b></td> 44: <td width="40%" align="left"> 45: <xsl:value-of select="@clubs"/> 46: </td> 47: </tr> 48: <tr> 49: <td colspan="6"> </td> 50: </tr> 51: <tr class="blackText"> 52: <td colspan="6" class="largeBlackText"> 53: Favorite Courses 54: </td> 55: </tr> 56: <tr> 57: <td colspan="2"><b>City: </b></td> 58: <td colspan="2"><b>State: </b></td> 59: <td colspan="2"><b>Course: </b></td> 60: </tr> 61: <xsl:apply-templates select="favoriteCourses"/> 62: </table> 63: <p/> 64: </xsl:template> 65: <xsl:template match="name"> 66: <tr> 67: <td colspan="6" class="largeYellowText" bgcolor="#02027a"> 68: <xsl:value-of select="firstName"/>  69: <xsl:value-of select="lastName"/> 70: </td> 71: </tr> 72: </xsl:template> 73: <xsl:template match="favoriteCourses"> 74: <xsl:apply-templates/> 75: </xsl:template> 76: <xsl:template match="course"> 77: <tr class="blackText"> 78: <td colspan="2" align="left"> 79: <xsl:value-of select="@city"/> 80: </td> 81: <td colspan="2" align="left"> 82: <xsl:value-of select="@state"/> 83: </td> 84: <td colspan="2" align="left"> 85: <xsl:value-of select="@name"/> 86: </td> 87: </tr> 88: </xsl:template> 89: </xsl:stylesheet>
To transform the XML document shown in Listing 7.1 using the XSLT document shown in Listing 7.2, the code shown in Listing 7.3 can be used:
Listing 7.3 Using the XslTransform Class
1: <%@ Import Namespace="System.Xml" %> 2: <%@ Import Namespace="System.Xml.Xsl" %> 3: <%@ Import Namespace="System.Xml.XPath" %> 4: <script language="C#" runat="server"> 5: public void Page_Load(Object sender, EventArgs E) { 6: string xmlPath = Server.MapPath("listing7.1.xml"); 7: string xslPath = Server.MapPath("listing7.2.xsl"); 8: 9: //Instantiate the XPathDocument Class 10: XPathDocument doc = new XPathDocument(xmlPath); 11: 12: //Instantiate the XslTransform Class 13: XslTransform transform = new XslTransform(); 14: transform.Load(xslPath); 15: 16: //Custom format the indenting of the output document 17: //by using an XmlTextWriter 18: XmlTextWriter writer = new XmlTextWriter(Response.Output); 19: writer.Formatting = Formatting.Indented; 20: writer.Indentation=4; 21: transform.Transform(doc, null, writer); 22: } 23: </script>
On executing the code in Listing 7.3, the XML document will magically be transformed into HTML that can be rendered in a browser. The result of this transformation is shown in Figure 7.2.
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:whenelements 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-spaceelement. |
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:stylesheetelement. |
xsl:value-of | Writes out the value of the selected node to the result tree. |
xsl:variable | Used to declare and assign variable values that can be either local or global in scope. |
xsl:when | Used as a child element of xsl:choose to perform multiple conditional testing. Similar to using case in a switch or Select statement. |
xsl:with-param | Used in passing a parameter to a template that is called via xsl:call-template. |
Although not every element listed in Table 7.1 is discussed in this section, you will be exposed to the more common elements and see how they can be used to transform XML into formats such as HTML, WML, and even EDI.
Note
The HTML generated by an XSLT document must conform to the rules outlined in the XML specification. All elements must be closed, including <img>, <input>, and all the other elements that normally do not need to be closed in HTML. Attributes used on elements must be quoted, a beginning and ending element tag’s case must match, and so on. Keep in mind that the XSLT processor knows how to work only with well-formed XML and knows nothing about the tags used in HTML, WML, and so on. As a result, everything within the XSLT document must follow the XML rules.
Transforming to HTML Using XSLT Elements
One of the best ways to learn about the different XSLT elements is to see them in action. Listing 7.4 repeats the XSLT style sheet shown earlier in Listing 7.2 but adds additional functionality. After looking through the code, you’ll see a step-by-step explanation of what the code is doing.
Listing 7.4 Golfers XSLT Document
1: <?xml version="1.0"?> 2: <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 3: version="1.0"> 4: <xsl:output method="html" indent="yes"/> 5: <xsl:template match="/"> 6: <html> 7: <head> 8: <style type="text/css"> 9: .blackText {font-family:arial;color:#000000;} 10: .largeYellowText {font-family:arial; 11: font-size:18pt;color:#ffff00;} 12: .largeBlackText {font-family:arial; 13: font-size:14pt;color:#000000;} 14: .borders {border-left:1px solid #000000; 15: border-right:1px solid #000000; 16: border-top:1px solid #000000; 17: border-bottom:1px solid #000000;} 18: </style> 19: </head> 20: <body bgcolor="#ffffff"> 21: <span class="largeBlackText"> 22: <b>List of</b> 23: <xsl:if test="count(//golfer) > 0"> 24:   25: <xsl:value-of select="count(//golfer)"/>  26: </xsl:if> 27: <b>Golfers</b> 28: </span> 29: <p/> 30: <xsl:apply-templates/> 31: </body> 32: </html> 33: </xsl:template> 34: <xsl:template match="golfers"> 35: <xsl:apply-templates select="golfer"/> 36: </xsl:template> 37: <xsl:template match="golfer"> 38: <table class="borders" border="0" width="640" 39: cellpadding="4" cellspacing="0" bgcolor="#efefef"> 40: <xsl:apply-templates select="name"/> 41: <tr class="blackText"> 42: <td width="12%" align="left"> 43: <b>Skill: </b> 44: </td> 45: <td width="12%" align="left"> 46: <xsl:attribute name="style"> 47: <xsl:choose> 48: <xsl:when test="@skill='excellent'"> 49: color:#ff0000;font-weight:bold; 50: </xsl:when> 51: <xsl:when test="@skill='moderate'"> 52: color:#005300; 53: </xsl:when> 54: <xsl:when test="@skill='poor'"> 55: color:#000000; 56: </xsl:when> 57: <xsl:otherwise> 58: color:#ffffff; 59: </xsl:otherwise> 60: </xsl:choose> 61: </xsl:attribute> 62: <xsl:value-of select="@skill"/> 63: </td> 64: <td width="12%" align="left"> 65: <b>Handicap: </b> 66: </td> 67: <td width="12%" align="left"> 68: <xsl:value-of select="@handicap"/> 69: </td> 70: <td width="12%" align="left"> 71: <b>Clubs: </b> 72: </td> 73: <td width="40%" align="left"> 74: <xsl:value-of select="@clubs"/> 75: </td> 76: </tr> 77: <tr> 78: <td colspan="6"> </td> 79: </tr> 80: <tr class="blackText"> 81: <td colspan="6" class="largeBlackText"> 82: Favorite Courses 83: </td> 84: </tr> 85: <tr> 86: <td colspan="2"> 87: <b>City: </b> 88: </td> 89: <td colspan="2"> 90: <b>State: </b> 91: </td> 92: <td colspan="2"> 93: <b>Course: </b> 94: </td> 95: </tr> 96: <xsl:apply-templates select="favoriteCourses"/> 97: </table> 98: <p/> 99: </xsl:template> 100: <xsl:template match="name"> 101: <tr> 102: <td colspan="6" class="largeYellowText" bgcolor="#02027a"> 103: <xsl:value-of select="firstName"/>  104: <xsl:value-of select="lastName"/> 105: </td> 106: </tr> 107: </xsl:template> 108: <xsl:template match="favoriteCourses"> 109: <xsl:apply-templates/> 110: </xsl:template> 111: <xsl:template match="course"> 112: <xsl:call-template name="writeComment"/> 113: <tr class="blackText"> 114: <td colspan="2" align="left"> 115: <xsl:value-of select="@city"/> 116: </td> 117: <td colspan="2" align="left"> 118: <xsl:value-of select="@state"/> 119: </td> 120: <td colspan="2" align="left"> 121: <xsl:value-of select="@name"/> 122: </td> 123: </tr> 124: </xsl:template> 125: <xsl:template name="writeComment"> 126: <xsl:comment>List Course Information</xsl:comment> 127: </xsl:template> 128: </xsl:stylesheet>
The following explanation walks through each element used in Listing 7.4.
Line 1:
1: <?xml version="1.0"?>
The XML declaration is used because this is a valid XML document.
Lines 2–3:
2: <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 3: version="1.0">
This line contains the first XSLT element used in the document: xsl:stylesheet. As shown earlier, this element has an associated namespace declaration and version attribute. The xsl:transform element could also be used here. One of these two elements will always be the root node of the XSLT document.
Line 4:
4: <xsl:output method="html" indent="yes"/>
The xsl:output element is used to specify what type of format will be created in the result tree. The method attribute can contain values of xml, html, or text. This element is a top-level element, meaning that it must be a child of the xsl:stylesheet element to be used properly. The different attributes that can be used to describe this element are the following: method, version, encoding, omit-xml-declaration, standalone, doctype-public, doctype-system, cdata-section-elements, indent, media-type. This element includes the indent attribute, which will indent the result tree to show its hierarchical structure. XSLT processors do not have to honor the indentation request. If the processor does support indentation, the manner in which the indentation is implemented is up to the processor.
Lines 5–33:
5: <xsl:template match="/"> 6: <html> 7: <head> 8: <style type="text/css"> 9: .blackText {font-family:arial;color:#000000;} 10: .largeYellowText {font-family:arial; 11: font-size:18pt;color:#ffff00;} 12: .largeBlackText {font-family:arial; 13: font-size:14pt;color:#000000;} 14: .borders {border-left:1px solid #000000; 15: border-right:1px solid #000000; 16: border-top:1px solid #000000; 17: border-bottom:1px solid #000000;} 18: </style> 19: </head> 20: <body bgcolor="#ffffff"> 21: <span class="largeBlackText"> 22: <b>List of</b> 23: <xsl:if test="count(//golfer) > 0"> 24:   25: <xsl:value-of select="count(//golfer)"/>  26: </xsl:if> 27: <b>Golfers</b> 28: </span> 29: <p/> 30: <xsl:apply-templates/> 31: </body> 32: </html> 33: </xsl:template>
Here’s an example of the first template definition specified in the XSLT document. Templates contain structured information that will be processed and output to the result tree. They are processed when the XPath pattern found in the match attribute matches a node found in the source XML document.
This template has a match attribute with a value of /. The value (/) represents a pattern that matches the XML document (think of it as the position directly above the root XML element). The purpose of a pattern is to identify which nodes a template applies to. You can think of patterns as valid XPath statements, although the XSLT definition does define them separately.
If the pattern specified in the match attribute matches a node in the source XML document, the information located within the template will be processed and written out to the result tree. In this case, when the XML document is matched, the basic elements used to start an HTML document are added to the result tree.
Note
It’s worth repeating that that the <html> and <body> elements contained within the template are simply standard XML elements to the XSLT processor. It knows nothing about HTML elements and simply cares that the elements follow the XML rules. Only when processing has completed and the result tree is rendered in an application that understands HTML tags, will the <html> and <body> elements have any presentation purpose.
The xsl:template element can have the attributes shown in Table 7.2.
Table 7.2 xsl:template Attributes
Attribute Name | Description |
match | The match attribute’s value is a pattern defined by an XPath statement that states which nodes in the source XML document should be processed by the template it is associated with. Although optional, if this attribute is not included, the name attribute shown next must be included. |
name | Applies a name to a template. This attribute is used by xsl:call- template. Although optional, if this attribute is not included, thematch attribute shown next must be included. |
priority | The value must be a number that says the priority of the template. The priority attribute’s value will be taken into consideration if several templates match the same node. |
mode | Applies a mode to a template. The mode is simply a name that can be used by the xsl:apply-templates element to hit a template that does a specific form of transformation. For example, if you need a node named chapter within an XML document to be processed differently, depending on its position within the document, you can create different templates that all match the chapter node. To differentiate the templates from each other (because they have the same match attribute value) a mode can be applied to each one. A call can then be made to the specific template that needs to be processed by using the xsl:apply-templates element and then specifying the template that has the desired mode. |
Lines 23–26:
23: <xsl:if test="count(//golfer) > 0"> 24:   25: <xsl:value-of select="count(//golfer)"/>  26: </xsl:if>
This portion of the XSLT document shows how the xsl:if element can be used to test a particular condition. In this case, an XSLT function named count() is used. This function takes an XPath statement as input and returns the number of nodes as output. You’ll learn more about XSLT functions in the next section.
The xsl:if element must have an attribute named test, which converts a valid XPath statement to a Boolean. In this case, the test checks to see whether the number of golfer nodes exceeds the value of 0. If the test returns true, the content (referred to as the template body) between the xsl:if start and end tags is processed.
Line 25 contains the xsl:value-of element, which is used frequently in XSLT documents to write out the value of a particular node to the result tree. This element must contain an attribute named select. The value of the attribute must contain a valid XPath expression. The node (or nodes) returned by the expression is converted to a string. In this case, the number of golfer nodes within the XML document is returned and placed in the result tree structure.
The xsl:value-of element may optionally include an attribute named disable-output-escaping that is useful when characters such as < or > need to be output without being escaped by using < or >. The attribute accepts a value of yes or no. Using it is especially useful when an XML element contains HTML tags that need to be written to the result tree structure without escaping the brackets.
Line 30:
30: <xsl:apply-templates/>
The xsl:apply-templates element provides a way to call templates that may match with other items contained in the source XML document. Before explaining what this element does in more detail, it’s appropriate to introduce a term called the context node. The context node is defined as the source document node currently being processed by the XSLT processor as specified by a template. Because we are processing the document node in the current template, that node is considered the context node. Obviously, many other elements are below this node, including golfers and golfer, that may also have templates associated with them. By using xsl:apply-templates the XSLT code is saying (in more human terms), “Find all templates that match with child elements of the current node (the context node) and go out and process them.” The first child node that will be found is the root node of the source document (golfers). The template that matches up with it is described next.
Lines 34–36:
34: <xsl:template match="golfers"> 35: <xsl:apply-templates select="golfer"/> 36: </xsl:template>
The golfers node in the source XML document matches up with the template defined in these lines of the XSLT document. As the XSLT processor is attempting to match templates with nodes in the XML document, any node with a name of golfers will automatically be processed by this template. Within the template, you’ll notice that the content is very sparse, except for the inclusion of an xsl:apply-templates element. With the context node now being the golfers node, calling xsl:apply-templates will send the processor looking for templates that match up with child nodes of the golfers node.
You’ll notice that line 30 includes an attribute named select that applies to the xsl:apply-templates element. This attribute accepts a valid XPath expression as a value. In this case, it selects a template that matches up with a node named golfer. Because the golfersnode contains golfer nodes only as children, including this attribute is unnecessary and is shown only to exemplify its use. If, however, the golfers node contained child nodes other than the golfer node and we wanted the golfer node template to be processed first, the inclusion of the select attribute would be more appropriate. This will become more clear as the next template is discussed.
Lines 37–99:
37: <xsl:template match="golfer"> 38: <table class="borders" border="0" width="640" 39: cellpadding="4" cellspacing="0" bgcolor="#efefef"> 40: <xsl:apply-templates select="name"/> 41: <tr class="blackText"> 42: <td width="12%" align="left"> 43: <b>Skill: </b> 44: </td> 45: <td width="12%" align="left"> 46: <xsl:attribute name="style"> 47: <xsl:choose> 48: <xsl:when test="@skill='excellent'"> 49: color:#ff0000;font-weight:bold; 50: </xsl:when> 51: <xsl:when test="@skill='moderate'"> 52: color:#005300; 53: </xsl:when> 54: <xsl:when test="@skill='poor'"> 55: color:#000000; 56: </xsl:when> 57: <xsl:otherwise> 58: color:#000000; 59: </xsl:otherwise> 60: </xsl:choose> 61: </xsl:attribute> 62: <xsl:value-of select="@skill"/> 63: </td> 64: <td width="12%" align="left"> 65: <b>Handicap: </b> 66: </td> 67: <td width="12%" align="left"> 68: <xsl:value-of select="@handicap"/> 69: </td> 70: <td width="12%" align="left"> 71: <b>Clubs: </b> 72: </td> 73: <td width="40%" align="left"> 74: <xsl:value-of select="@clubs"/> 75: </td> 76: </tr> 77: <tr> 78: <td colspan="6"> </td> 79: </tr> 80: <tr class="blackText"> 81: <td colspan="6" class="largeBlackText"> 82: Favorite Courses 83: </td> 84: </tr> 85: <tr> 86: <td colspan="2"> 87: <b>City: </b> 88: </td> 89: <td colspan="2"> 90: <b>State: </b> 91: </td> 92: <td colspan="2"> 93: <b>Course: </b> 94: </td> 95: </tr> 96: <xsl:apply-templates select="favoriteCourses"/> 97: </table> 98: <p/> 99: </xsl:template>
This template does the bulk of the work in Listing 7.4 by matching up with all golfer nodes in the source XML document. When the template is processed, the shell structure for an HTML table is written out. This table will be used to present all the information about a specific golfer. Line 40 uses the xsl:apply-templates and provides a pattern for the template that should be called by using the select attribute. By providing a pattern equal to name, only a template that matches up with the pattern will be processed. Why didn’t we simply call xsl:apply-templates and not worry about which of the context node’s child node templates were called? The answer is that we want to ensure that the template with a pattern matching the name child node is processed before any other children of the context node (golfer, in this case).
After the template matching the name node is called, processing will be done on that template and then return to the golfers template. Specifically, the XSLT processor will jump back to the next statement in the golfer template that immediately follows the call to <xsl:apply-templates select="name"/>.
Lines 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 thexsl:when starting and ending tags will be assigned to the value of the style attribute. The test performed in line 48 checks to see whether an attribute of the context node (remember that the context node is golfer at this point) named skill has a value equal to excellent. If it does, the style attribute will have a value of color:#ff0000;font-weight:bold;. Assuming the skill attribute does have a value of excellent, the actual structure added on completion of the xsl:choose block testing will be the following:
<td width="12%" align="left" style="color:#ff0000;font-weight:bold;">
If the first xsl:when returns false, testing will continue down the line. If no xsl:when tests return true, the xsl:otherwise block will be hit and the style attribute will be assigned a value of color:#000000; (lines 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.
Lines 100–107:
100: <xsl:template match="name"> 101: <tr> 102: <td colspan="6" class="largeYellowText" bgcolor="#02027a"> 103: <xsl:value-of select="firstName"/>  104: <xsl:value-of select="lastName"/> 105: </td> 106: </tr> 107: </xsl:template>
The template declaration shown in line 100 matches up with all name nodes found within the XML document. This template is called from within the golfer template discussed earlier (see line 40). Processing of the template is limited to writing out a new row in the table (line 101) followed by a column containing the values of the firstName and lastName elements. These values are written to the result tree by using the xsl:value-of element(lines 103 and 104).
Lines 108–127:
108: <xsl:template match="favoriteCourses"> 109: <xsl:apply-templates/> 110: </xsl:template> 111: <xsl:template match="course"> 112: <xsl:call-template name="writeComment"/> 113: <tr class="blackText"> 114: <td colspan="2" align="left"> 115: <xsl:value-of select="@city"/> 116: </td> 117: <td colspan="2" align="left"> 118: <xsl:value-of select="@state"/> 119: </td> 120: <td colspan="2" align="left"> 121: <xsl:value-of select="@name"/> 122: </td> 123: </tr> 124: </xsl:template> 125: <xsl:template name="writeComment"> 126: <xsl:comment>List Course Information</xsl:comment> 127: </xsl:template>
The template matching favoriteCourses contains no functionality other than to call xsl: apply-templates. This is because it contains no attributes and acts only as a parent container element. Because the favoriteCourses node contains only child nodes named course, calling xsl:apply-templates will result in only one template match.
Processing within the template that matches the course nodes is limited to adding a new row (line 113) with columns containing the values for attributes named city, state, and name. Each course node found within the source XML document will be matched with this template and the appropriate attribute values will be written out.
Line 112 introduces a new XSLT element that hasn’t been covered to this point. The xsl:call-template element can be used to call a template in a similar manner as calling a function within C# or VB.NET. Calling a template in XSLT is accomplished by identifying the name of the template to call via a name attribute. The template that is called must, in turn, have a matching name attribute as shown in line 125.
Calling templates can be useful when a template doesn’t match up with a given node in an XML document but needs to be accessible to process commonly used features or perform calculations. For example, if your XSLT code needs to walk through a list of pipe-delimited strings, a template can be called recursively until each piece of data within the string has been processed. You’ll see a concrete example of using the xsl:call-template element in conjunction with the xsl:with-param and xsl:param elements toward the end of this chapter.
Line 128:
128: </xsl:stylesheet>
The XSLT language follows all the rules outlined in the XML specification. As such, the xsl:stylesheet element must be closed.
This example has shown how you can use some of the main elements found in the XSLT specification. For more information on some of the other elements not covered in the previous example, refer to the XSLT version 1.0 specification (http://www.w3.org/TR/1999/REC- xslt-19991116) or pick up a copy of a book titled XSLT Programmer’s Reference (ISBN 1-861003-12-9).
Transforming XML into Another Form of XML Using XSLT Elements
HTML isn’t the only structure that can be created by an XSLT document. Many cases may occur in which other formats, such as a comma-delimited, WML, or even another form of XML, need to be generated to integrate with an application. In this section we’ll take a look at a simple example of transforming from XML to XML and show how a few of the XSLT elements can make this process easier. XML-to-XML transformations are useful when an XML document’s structure needs to be changed before sending it off to a vendor or to another application that expects a different format.
Listing 7.5 shows the XML document that needs to be transformed, Listing 7.6 shows the result of the transformation, and Listing 7.7 shows the XSLT document used in processing the transformation.
Listing 7.5 The Source XML Document
1: <?xml version="1.0"?> 2: <root> 3: <row> 4: <id>1</id> 5: <name> 6: <fname>Dan</fname> 7: <lname>Wahlin</lname> 8: </name> 9: <address type="home"> 10: <street>1234 Anywhere St.</street> 11: <city>AnyTown</city> 12: <zip>85789</zip> 13: </address> 14: <address type="business"> 15: <street>1234 LottaWork Ave.</street> 16: <city>AnyTown</city> 17: <zip>85786</zip> 18: </address> 19: </row> 20: <row> 21: <id>2</id> 22: <name> 23: <fname>Elaine</fname> 24: <lname>Wahlin</lname> 25: </name> 26: <address type="home"> 27: <street>1234 Anywhere St.</street> 28: <city>AnyTown</city> 29: <zip>85789</zip> 30: </address> 31: <address type="business"> 32: <street>1233 Books Way</street> 33: <city>AnyTown</city> 34: <zip>85784</zip> 35: </address> 36: </row> 37: </root>
Listing 7.6 The Result of Transforming the XML Document in Listing 7.5
1: <?xml version="1.0"?> 2: <root> 3: <row id="1" fname="Dan" lname="Wahlin"> 4: <address type="home"> 5: <street>1234 Anywhere St.</street> 6: <city>AnyTown</city> 7: <zip>85789</zip> 8: </address> 9: <address type="business"> 10: <street>1234 LottaWork Ave.</street> 11: <city>AnyTown</city> 12: <zip>85786</zip> 14: </address> 15: </row> 16: <row id="2" fname="Elaine" lname="Wahlin"> 17: <address type="home"> 18: <street>1234 Anywhere St.</street> 19: <city>AnyTown</city> 20: <zip>85789</zip> 21: </address> 22: <address type="business"> 23: <street>1233 Books Way</street> 24: <city>AnyTown</city> 25: <zip>85784</zip> 26: </address> 27: </row> 28: </root>
Listing 7.7 The XSLT Document
1: <?xml version="1.0" ?> 2: <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 3: version="1.0"> 4: <xsl:output method="xml" indent="yes" encoding="utf-8" 5: omit-xml-declaration="no"/> 6: <xsl:template match="/"> 7: <root> 8: <xsl:apply-templates/> 9: </root> 10: </xsl:template> 11: <xsl:template match="row"> 12: <row> 13: <xsl:attribute name="id"> 14: <xsl:value-of select="id"/> 15: </xsl:attribute> 16: <xsl:attribute name="fname"> 17: <xsl:value-of select="name/fname"/> 18: </xsl:attribute> 19: <xsl:attribute name="lname"> 20: <xsl:value-of select="name/lname"/> 21: </xsl:attribute> 22: <xsl:for-each select="address"> 23: <xsl:copy-of select="."/> 24: </xsl:for-each> 25: </row> 26: </xsl:template> 27: </xsl:stylesheet>
Breaking the XSLT document down into individual pieces reveals a few new things not seen in previous examples. First, Line 4 uses the xsl:output element to specify an output format of xml. It also specifies that the XML declaration should be included. This is done by setting the omit-xml-declaration attribute to no. Because this is the attribute’s default value, it could have been left out altogether.
Lines 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:copyelement instead. However, utilizing the xsl:copy-of element prevents us from having to create each element (address, street, city, zip) dynamically by using the xsl:element or xsl:copy elements.
Now that you’ve had an opportunity to see some of the most common XSLT elements in action, let’s take a look at a few more that can help make your XSLT documents more dynamic and flexible.
Using Variables and Parameters: The xsl:variable and xsl:param Elements
As programmers, we all take for granted the capability to use variables and pass parameters to functions. In fact, it’s hard to imagine programming without variables and parameters. Most programmers would be hard pressed to eliminate them from their applications. Fortunately, there’s no need to worry about variables or parameters being eliminated from C#, VB.NET, or even from languages such as XSLT. The XSLT specification includes the capability to use a variable or pass a parameter to a template. In this section, you are provided with a general overview of how variables and parameters can be used to make your XSLT documents more flexible. Let’s first take a look at how variables can be used.
Variables in XSLT
XSLT variables are used to avoid calculating the same result multiple times. Although very similar to variables used in C# or any other programming language, XSLT variables can be set once but cannot be updated after they are set. The value assigned to a variable is retained until the variable goes out of scope. What, you say! XSLT variables can be set only once? Doesn’t this make them the equivalent of a constant?
There’s a method to the madness that makes perfect sense when analyzed. Because XSLT relies on templates that can be called randomly, depending on the structure of the source XML document, the capability to update a particular global variable could introduce problems. These types of problems are referred to as “side effects,” and the XSLT specification eliminates the problem by allowing for no side effects. If you’ve ever stepped into a project that you didn’t originally code that had bugs cropping up because of overuse of global variables, you’ll appreciate this concept.
To illustrate the concept of side effects more, let’s take a look at a simple example. If a template named template1 relies on another template named template2 to update the value of a global variable, what happens if template2 doesn’t ever get processed? The rather obvious answer is that the global variable will never be updated and therefore can cause potential problems to template1 when it is processed. By not allowing variables in XSLT style sheets to be updated, this problem is avoided.
It’s important to realize that XSLT documents can be written without the use of variables. However, variables can aid in cleaning up the code and can also result in more efficient XSLT style sheets. The following example uses a portion of the code shown earlier in Listing 7.4 to demonstrate how a variable declared globally (as a child element of the xsl:stylesheet element) can be used in XSLT:
<xsl:variable name="count" select="count(//@handicap[. < 11])"/> <xsl:template match="golfer"> <!-- ...Other content here --> <td width="12%" align="left"> <xsl:value-of select="@handicap"/> <br/> <xsl:if test="@handicap > 11"> <xsl:value-of select="$count"/> golfers have lower handicaps </xsl:if> </td> <!-- ...Other content here --> </xsl:template>
The xsl:variable element can have a name and a select attribute. The name attribute is required and serves the obvious purpose of assigning a name that can be used to reference the variable. The select attribute is optional but when listed, must contain a valid XPath expression.
So what have we gained by using a variable in this template? The code has actually been made much more efficient as compared to writing the same code without using the variable. Instead of having to calculate the count of all the golfers in the XML document with handicaps less than 11 each time a golfer node template is matched, the variable obtains this value and stores it when the XSLT style sheet is first loaded. The variable can then be referenced in several places by adding the $ character to the beginning of the variable name ($count in this case). Doing this cuts out unnecessary processing during the transformation process.
At times, the value of an attribute may need to be dynamically generated and used in several places. In many cases, using a variable can make this process easier:
<xsl:variable name="color"> <xsl:choose> <xsl:when test="@handicap = 10"> #ff0000 </xsl:when> <xsl:when test="@handicap = 20"> #ffff00 </xsl:when> <xsl:otherwise> #ffffff </xsl:otherwise> </xsl:choose> </xsl:variable> <font color="{$color}"><xsl:value-of select="@handicap"/></font> <p> </p> <font color="{$color}" size="4"><xsl:value-of select="lastName"/></font>
Although the value of the color attribute found on the font tag could be added by using the xsl:attribute element multiple times, by defining the value once in the variable, the code is kept cleaner and the processing is more efficient.
Tip
The previous example shows a shortcut that can be used to embed data directly into attributes that will be written out to the result tree. By wrapping a variable (or other item) with the { and } brackets, it will dynamically be added to the result tree. This is in contrast to adding the attribute name and value to a tag by using the xsl:attribute element. As another example, if you had an attribute named width in an XML document, you could write it out to a table tag by doing the following: <table width="{@width}">. This is similar to doing something such as <table width="<%=myWidth%>"> in ASP.NET.
Variables can also be useful for storing values returned by calling a template. This process will be shown a little later after you’re introduced to the xsl:param element.
Parameters in XSLT
Parameters are useful in a variety of programming languages, with XSLT being no exception. Parameters can be used in XSLT documents in two basic ways. First, parameter values can be passed in from an ASP.NET application. This allows data not found within the XML document or XSLT style sheet to be part of the transformation process. Second, parameter values can be passed between XSLT templates in much the same way that parameters can be passed between functions in C# or VB.NET. You’ll see how parameters can be used in both ways later in the chapter.
Declaring a parameter is similar to declaring a variable. Simply name the parameter and add an optional select attribute:
<xsl:param name="myParam" select="'My Parameter Value'"/>
As with variables, parameters can be children of the xsl:stylesheet or xsl:transform elements and can also be children of the xsl:template element. So when would you want to use a parameter? Let’s assume that the golfers XSLT document shown in Listing 7.4 needs to show a specific golfer based on user input in a Web form. To accomplish this task, the ASP.NET application can pass in the value entered by the user to a parameter within the XSLT document. This value can then be used to display the proper golfer’s information. A simplified document that uses a parameter named golferNum is shown next:
<?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:param name="golferName" select="'Dan'"/> <xsl:template match="golfers"> <xsl:apply-templates select="//golfer[name/firstName=$golferName]"/> </xsl:template> <xsl:template match="golfer"> <xsl:apply-templates/> </xsl:template> <xsl:template match="name"> <xsl:value-of select="firstName"/>   <xsl:value-of select="lastName"/> </xsl:template> </xsl:stylesheet>
Having this parameter in the XSLT style sheet will cause a specific golfer’s information to be transformed. Any other golfers in the XML document will simply be ignored. How is this accomplished? A small change was made to the xsl:apply-templates element in the golfers template. Instead of processing all golfer nodes in the XML document, the XPath expression in the select attribute specifies the specific golfer node to process:
<xsl:apply-templates select="//golfer[name/firstName=$golferName]"/>
Although this example hard-codes a value for the golferName parameter, an ASP.NET page would normally pass the value in using specific classes in the System.Xml assembly. You’ll be introduced to these classes later in the chapter. If the value passed into the golferNameparameter from the ASP.NET page does not match up with an existing golfer node in the XML document, no error will be raised.
Parameters can also be used in conjunction with the xsl:call-template element. Fortunately, from working with other programming languages, you already have a good understanding of how this works. Imagine calling a method named GetOrders() that accepts a single parameter as an argument. The method call would look something like the following:
GetOrders("ALFKI");
Now imagine that GetOrders is the name of an XSLT template used to transform an XML document containing customers and orders. Calling the template and passing the parameter can be accomplished by doing the following:
<xsl:template match="Customer"> <xsl:call-template name="GetOrders"> <xsl:with-param name="CustomerID" select="@CustomerID"/> </xsl:call-template> </xsl:template> <xsl:template name="GetOrders"> <xsl:param name="CustomerID" select="'ALFKI'"/> <xsl:value-of select="//Order[@CustomerID = $CustomerID]"/> </xsl:template>
This example shows the use of the xsl:call-template and xsl:with-param elements to initiate the template call. The xsl:call-template element has a single attribute that provides the name of the template to call. The xsl:with-param element has two attributes. One is used to name the parameter that data will be passed to and the other provides the value that is to be passed. The select attribute can contain any valid XPath expression. The xsl:with-param element can only be a child of the xsl:call-template or xsl:apply-templateselement.
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:
<xsl:template match="Customer"> <xsl:variable name="CID" select="@CustomerID"/> <xsl:variable name="Orders"> <xsl:call-template name="GetNames"> <xsl:with-param name="CustomerID" select="$CID"/> </xsl:call-template> </xsl:variable> <b>Customer</b><br/> ID: <xsl:value-of select="$CID"/><br/> <xsl:for-each select="$Orders//Order"/> <xsl:value-of select="@OrderID"/> </xsl:for-each> </xsl:template> <xsl:template name="GetNames"> <xsl:param name="CustomerID" select="'ALFKI'"/> <xsl:value-of select="//Orders[@CustomerID = $CustomerID]"/> </xsl:template>
The variable named Orders will be filled with a node-set generated by a call to the GetNames template. Doing this offers a powerful means for building more dynamic and efficient XSLT documents.
Now that you’re familiar with several of the main elements used in creating XSLT style sheets, it’s time to examine XSLT functions.
XSLT Functions
In Chapter 3, “XPath, XPointer, and XLink,” you were introduced to several functions built in to the XPath language. Because XSLT relies on XPath for creating expressions that locate nodes in an XML document, these functions are available for use in your XSLT documents. In addition to these functions, the XSLT language adds a few more. Table 7.3 shows these functions and provides a description and example of using them in XSLT documents.
Table 7.3 XSLT Functions
Function | Description |
current() | Returns a node set that contains the current node as its only member. This function exists to help identify the current node when it is different from the context node. In previous examples you have seen that the context node can be represented by the . character. For example, to write out the value of the context node being processed by a template, you can do the following:
<xsl:value-of select="."/> |
This code will provide the same result:
<xsl:value-of select="current()"/> |
|
At this point in the template, the current node is the same as the context node. When used within the square brackets of a predicate ([ and ]), however, the current node is often different from the context node. An example of this is shown next: | |
<xsl:template match="/"> <xsl:variable name="golferVar" select="//golfer"/> <xsl:text>Other Comparable Golfers: </xsl:text> <xsl:for-each select="$golferVar"> <xsl:if test="$golferVar[./@skill= current()/@skill]"> <xsl:value-of select="$golferVar/name/ lastName"/> </xsl:if> </xsl:for-each> </xsl:template> |
|
The predicate statement ([./@skill = current()/ @skill]) will compare the value of the context node’s skill attribute (in this case, the golfer node being looped through) to the golfer node being handled by the template (the current node). This is an example of how the context node changes during a loop. However, the current node is associated with the node being handled by the template and stays the same during the looping process. | |
document(object,node-set?) | Although XSLT makes it easy to transform a single XML document into other structures, what happens if the result tree needs to be created from more than just one XML document? Using the document() function, other XML documents can be pulled into an XSLT document for processing. This can be useful in many situations, including when one XML document contains a presentation structure and another contains the data to be plugged into that structure. Using the document() function, these types of activities can be accomplished relatively easily. An example of using the document() function is shown next:
<xsl:variable name="xmlDoc" |
This will load data.xml into the variable named xmlDoc. Referencing elements within the external document can be accomplished in the following manner:
<xsl:value-of select="$xmlDoc//root/data/@id"/> |
|
Although the preceding document() function example targets an external document, this function can also be used to target a node-set within the main XML document. This can be useful when you want to work with a lookup table structure embedded in the XML document. | |
Aside from providing a string URI value, a node-set can be passed to access the remote document, as shown next: | |
For the XML document:
<golfers> <golfer href="http://www.ilikegolf.com/golfers/golfers.xm l"/> <handicaps href="handicapsWest.xml"/> <handicaps href="handicapsEast.xml"/> </golfer> </golfers> |
|
The following XSLT document creates a result tree containing the handicap nodes from the referenced documents:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"version="1.0"> <xsl:template match="/"> <golfers> <xsl:apply-templates select="//handicaps"/> </golfers> </xsl:template> <xsl:template match="handicaps"> <xsl:copy of select="document(@href)//handicap"/> </xsl:template> </xsl:stylesheet> |
|
element-available(string) | This function is useful if you are writing an XSLT document that may be processed using different XSLT processors. Before using elements that you know may not be supported, a check can be made to see if the processor does indeed support the element in question. This is helpful when testing for XSLT elements found in a later version of XSLT or in checking for vendor specific elements. The function will return a Boolean value:
<xsl:if test="element- available('xsl:someNewElement')"> <xsl:someNewElement>HI!</xsl:someNewElement> </xsl:if> |
format-number(number, string,string?) | This function converts numbers to a string using a format pattern supplied in the second parameter. Here are some examples of using this function: |
The following function call returns 5,351:
format-number(5351,"#,###") |
|
The following function call returns 5351.00:
format-number(5351, "#.00") |
|
The following function call returns 53.5100:
format-number(53.51, "#.0000") |
|
The following function call returns 0053.5100:
format-number(53.51, "0000.0000") |
|
The following function call returns 0053.51:
format-number(53.51, "0000.####") |
|
The following function call returns 53.6:
format-number(53.56, "0.0") |
|
function-available(string) | Similar to element-available, although this function checks whether specific functions are supported by the XSLT processor. |
generate-id(node-set?) | This function generates a string that is guaranteed to uniquely identify a node. The same string will always be returned for the same node. The string that is generated will vary from processor to processor and will start with an alphabetic character. |
<xsl:attribute name="id"> <xsl:value-of select="generate-id(.)"/> </xsl:attribute> |
|
key(string,object) | This function is used in conjunction with the xsl:key element to return a node-set that has a specific name and value defined by the xsl:key statement. For example, the following code shows how the xsl:key element can define a key:
<xsl:key name="course-city" match="course" use="@city"/> |
This key can then be accessed by using the key() function:
<xsl:for-each select="key('course-city', 'Pinetop')"> ... </xsl:for-each> |
|
system-property(string) | This function returns the value of the system property identified by the name passed as the argument. Three different system properties must be supported by a compliant XSLT processor, including xsl:version, xsl:vendor, and xsl:vendor-url. An example of using this function follows:
<xsl:value-of select="system-property ('xsl:version')"/> |
unparsed-entity-uri() | This function returns declarations of unparsed entities in the DTD of the source XML document. |
Given the entity declaration:
<!ENTITY clubs SYSTEM "http://www.lottaclubs.com/clubs.txt"> |
|
The following code:
<xsl:value-of select="unparsed-entity-uri('clubs')"/> |
|
Would return a value of http://www.lottaclubs.com/clubs.txt. |
.NET Classes Involved in Transforming XML
Now that you’ve seen the different XSLT elements and functions that are at your disposal, it’s time to learn about what classes in the .NET framework can be used in your ASP.NET applications when XSL transformations are necessary. After all, XSLT is simply a text-based language that is of little utility without an XSLT processor.
Several classes built in to the System.Xml assembly can be used when transforming XML into other structures via XSLT. Back in Listing 7.3, a preview of a few of these classes interacting with each other was given that demonstrated how to transform an XML document into HTML. In this section you’ll learn more about these classes and a few others so that you are fully armed with everything you need to know to use XSLT in your ASP.NET applications. Figure 7.3 presents an overview of the main classes used in XSL transformations.
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 IXPathNavigableinterface 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 withDataSets 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 IXPathNavigableinterface 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 XmlSpaceenumeration. |
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): |
xsl.Transform(XpathDocument,XsltArgumentList,Stream) |
Listing 7.9 builds on Listing 7.8 by adding in the XslTransform class.
Listing 7.9 Using the XslTransform Class
1: <%@ Import Namespace="System.Xml" %> 2: <%@ Import Namespace="System.Xml.Xsl" %> 3: <%@ Import Namespace="System.Xml.XPath" %> 4: <%@ Import Namespace="System.IO" %> 5: <%@ Import Namespace="System.Text" %> 6: <script language="C#" runat="server"> 7: public void Page_Load(Object sender, EventArgs E) { 8: string xmlPath = Server.MapPath("listing7.1.xml"); 9: string xslPath = Server.MapPath("listing7.2.xsl"); 10: 11: FileStream fs = new FileStream(xmlPath,FileMode.Open, 12: FileAccess.Read); 13: StreamReader reader = new StreamReader(fs,Encoding.UTF8); 14: XmlTextReader xmlReader = new XmlTextReader(reader); 15: 16: //Instantiate the XPathDocument Class 17: XPathDocument doc = new XPathDocument(xmlReader); 18: 19: //Instantiate the XslTransform Class 20: XslTransform xslDoc = new XslTransform(); 21: xslDoc.Load(xslPath); 22: xslDoc.Transform(doc,null,Response.Output); 23: 24: //Close Readers 25: reader.Close(); 26: xmlReader.Close(); 27: } 28: </script>
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 namedgolferName 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.
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)
1: private void Page_Load(object sender, System.EventArgs e) { 2: if (!Page.IsPostBack) { 3: FillDropDown("firstName"); 4: } 5: } 6: 7: private void FillDropDown(string element) { 8: string name = ""; 9: this.ddGolferName.Items.Clear(); 10: XmlTextReader reader = new XmlTextReader(xmlPath); 11: object firstNameObj = reader.NameTable.Add("firstName"); 12: while (reader.Read()) { 13: if (reader.Name.Equals(firstNameObj)) { 14: name = reader.ReadString(); 15: ListItem item = new ListItem(name,name); 16: this.ddGolferName.Items.Add(item); 17: } 18: } 19: reader.Close(); 20: }
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)
1: protected void btnSubmit_Click(object sender, System.EventArgs e) { 2: string xslPath = Server.MapPath("xsltGolfer.xsl"); 3: XmlTextReader xmlReader = null; 4: StringBuilder sb = new StringBuilder(); 5: StringWriter sw = new StringWriter(sb); 6: 7: try { 8: xmlReader = new XmlTextReader(xmlPath); 9: //Instantiate the XPathDocument Class 10: XPathDocument doc = new XPathDocument(xmlReader); 11: 12: //Instantiate the XslTransform Classes 13: XslTransform transform = new XslTransform(); 14: transform.Load(xslPath); 15: 16: //Add Parameters 17: XsltArgumentList args = new XsltArgumentList(); 18: args.AddParam("golferName","", 19: this.ddGolferName.SelectedItem.Value); 20: 21: //Call Transform() method 22: transform.Transform(doc, args, sw); 23: 24: //Hide-Show ASP.NET Panels in xsltGolfer.aspx 25: this.pnlSelectGolfer.Visible = false; 26: this.pnlTransformation.Visible = true; 27: this.divTransformation.InnerHtml = sb.ToString(); 28: } 29: catch (Exception excp) { 30: Response.Write(excp.ToString()); 31: } 32: finally { 33: xmlReader.Close(); 34: sw.Close(); 35: } 36: }
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)
1: <?xml version="1.0"?> 2: <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 3: xmlns:dateTimeObj="urn:xsltExtension-DateTime" version="1.0"> 4: <xsl:output method="html" indent="yes"/> 5: <xsl:param name="golferName" select="'Dan'"/> 6: <xsl:template match="/"> 7: <xsl:apply-templates 8: select="//golfer[name/firstName=$golferName]"/> 9: </xsl:template> 10: <xsl:template match="golfers"> 11: <xsl:apply-templates select="golfer"/> 12: </xsl:template> 13: <xsl:template match="golfer"> 14: <table class="borders" border="0" width="640" cellpadding="4" 15: cellspacing="0" bgcolor="#efefef"> 16: <xsl:apply-templates select="name"/> 17: <tr class="blackText"> 18: <td width="12%" align="left"> 19: <b>Skill: </b> 20: </td> 21: <td width="12%" align="left"> 22: <xsl:attribute name="style"> 23: <xsl:choose> 24: <xsl:when test="@skill='excellent'"> 25: color:#ff0000;font-weight:bold; 26: </xsl:when> 27: <xsl:when test="@skill='moderate'"> 28: color:#005300; 29: </xsl:when> 30: <xsl:when test="@skill='poor'"> 31: color:#000000; 32: </xsl:when> 33: <xsl:otherwise> 34: color:#000000; 35: </xsl:otherwise> 36: </xsl:choose> 37: </xsl:attribute> 38: <xsl:value-of select="@skill"/> 39: </td> 40: <td width="12%" align="left"> 41: <b>Handicap: </b> 42: </td> 43: <td width="12%" align="left"> 44: <xsl:value-of select="@handicap"/> 45: </td> 46: <td width="12%" align="left"> 47: <b>Clubs: </b> 48: </td> 49: <td width="40%" align="left"> 50: <xsl:value-of select="@clubs"/> 51: </td> 52: </tr> 53: <tr> 54: <td colspan="6"> </td> 55: </tr> 56: <tr class="blackText"> 57: <td colspan="6" class="largeBlackText"> 58: Favorite Courses 59: </td> 60: </tr> 61: <tr> 62: <td colspan="2"> 63: <b>City: </b> 64: </td> 65: <td colspan="2"> 66: <b>State: </b> 67: </td> 68: <td colspan="2"> 69: <b>Course: </b> 70: </td> 71: </tr> 72: <xsl:apply-templates select="favoriteCourses"/> 73: </table> 74: <p/> 75: <xsl:value-of select="dateTimeObj:GetDateTime()"/> 76: </xsl:template> 77: <xsl:template match="name"> 78: <tr> 79: <td colspan="6" class="largeYellowText" bgcolor="#02027a"> 80: <xsl:value-of select="firstName"/> 81:   82: <xsl:value-of select="lastName"/> 83: </td> 84: </tr> 85: </xsl:template> 86: <xsl:template match="favoriteCourses"> 87: <xsl:apply-templates/> 88: </xsl:template> 89: <xsl:template match="course"> 90: <xsl:call-template name="writeComment"/> 91: <tr class="blackText"> 92: <td colspan="2" align="left"> 93: <xsl:value-of select="@city"/> 94: </td> 95: <td colspan="2" align="left"> 96: <xsl:value-of select="@state"/> 97: </td> 98: <td colspan="2" align="left"> 99: <xsl:value-of select="@name"/> 100: </td> 101: </tr> 102: </xsl:template> 103: <xsl:template name="writeComment"> 104: <xsl:comment>List Course Information</xsl:comment> 105: </xsl:template> 106: </xsl:stylesheet>
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 orXslTransform 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.
Listing 7.17 Using the asp:Xml Web Control
1: <html> 2: <body> 3: <asp:Xml ID="xslTransform" Runat="server" 4: DocumentSource="Listing7.1.xml" 5: TransformSource="Listing7.2.xsl"> 6: </asp:Xml> 7: </body> 8: </html>
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
1: <script language="C#" runat="server"> 2: void Page_Load(object sender, System.EventArgs e) { 3: xslTransform.DocumentSource = "Listing7.1.xml"; 4: xslTransform.TransformSource = "Listing7.2.xsl"; 5: } 6: </script> 7: <html> 8: <body> 9: <asp:Xml ID="xslTransform" Runat="server"></asp:Xml> 10: </body> 11: </html>
The asp:Xml server control also allows DOM structures to be passed into it programmatically using the Document and Transform properties:
1: <%@Import Namespace="System.Xml"%> 2: <%@Import Namespace="System.Xml.Xsl"%> 3: <script language="C#" runat="server"> 4: void Page_Load(object sender, System.EventArgs e) { 5: XmlDocument doc = new XmlDocument(); 6: doc.Load(Server.MapPath("Listing7.1.xml")); 7: XslTransform trans = new XslTransform(); 8: trans.Load(Server.MapPath("Listing7.2.xsl")); 9: xslTransform.Document = doc; 10: xslTransform.Transform = trans; 11: } 12: </script> 13: <html> 14: <body> 15: <asp:Xml ID="xslTransform" Runat="server"> 16: </asp:Xml> 17: </body> 18: </html>
Summary
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.