- Chapter 7: Transforming XML with XSLT and ASP.NET
- 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
- Summary
.NET Classes Involved in Transforming XML
Now that you've seen the different XSLT elements and functions that are at your disposal, it's time to learn about what classes in the .NET framework can be used in your ASP.NET applications when XSL transformations are necessary. After all, XSLT is simply a text-based language that is of little utility without an XSLT processor.
Several classes built in to the System.Xml assembly can be used when transforming XML into other structures via XSLT. Back in Listing 7.3, a preview of a few of these classes interacting with each other was given that demonstrated how to transform an XML document into HTML. In this section you'll learn more about these classes and a few others so that you are fully armed with everything you need to know to use XSLT in your ASP.NET applications. Figure 7.3 presents an overview of the main classes used in XSL transformations.
Figure 7.3 .NET Classes involved in XSL transformations.
Table 7.4 provides a description of each of these classes.
Table 7.4 .NET Classes Used in XSL Transformations
Class |
Description |
XmlDocument |
The XmlDocument class implements the IXPathNavigable interface and extends the XmlNode class, which provides the capability to create nodes within a DOM structure. This class was discussed in Chapter 6. Because the XmlDocument class provides node-creation capabilities, it will not provide the fastest throughput in XSL transformations. However, in cases where a DOM structure must be edited first before being transformed, this class can be used. |
XmlDataDocument |
The XmlDataDocument class extends the XmlDocument class. The XmlDataDocument class can be used when working with DataSets in ADO.NET. Chapter 8, "Leveraging ADO.NET's XML Features Using ASP.NET," covers this class in more depth. |
XPathDocument |
The XPathDocument class implements the IXPathNavigable interface like the XmlDocument class does. However, the XPathDocument class does not extend the XmlNode class (as the XmlDocument class does) and therefore provides the fastest option for transforming XML via XSLT. You'll see this class used in the examples that follow. |
|
Because the XPathDocument class implements the IXPathNavigable interface, it is able to leverage features built in to the abstract XPathNavigator class (which, in turn, uses the XPathNodeIterator abstract class for iteration over node-sets) to provide cursor-style access to XML data, resulting in fast and efficient XSL transformations. |
XslTransform |
The XslTransform class is used to transform XML data into other structures. Using the XslTransform class involves instantiating it, loading the proper style sheet with the Load() method, and then passing specific parameters to its Transform() method. This process will be detailed in the next few sections. |
XsltArgumentList |
The XsltArgumentList class is used to provide parameter values to xsl:param elements defined in an XSLT style sheet. It can be passed as an argument to the XslTransform class's Transform() method. |
The XPathDocument Class
Before looking at the XslTransform class, you need to familiarize yourself with the XPathDocument class. To use this class you must reference the System.Xml.XPath namespace in your ASP.NET applications. As mentioned in Table 7.4, this class provides the most efficient way to transform an XML document using XSLT because it provides a read-only representation of a DOM structure. The XPathDocument class is very simple to use because it has only one XML-related method named CreateNavigator() that can be used to create an instance of the XPathNavigator class. However, it does have several constructors that are worth mentioning. Table 7.5 shows the different constructors.
Table 7.5 XPathDocument Constructors
Constructor |
Description |
Public XPathDocument(XmlReader, XmlSpace) |
Accepts an XmlReader as well as an XmlSpace enumeration. |
Public XPathDocument(XmlReader) |
Accepts an XmlReader. |
Public XPathDocument(TextReader) |
Accepts a TextReader. |
Public XPathDocument(Stream) |
Accepts a Stream. |
Public XPathDocument(string,XmlSpace) |
-Accepts the string value of the path to an XML document and an XmlSpace enumeration. |
Public XPathDocument(string) |
-Accepts the string value of the path to an XML document. |
Listing 7.3 used the last constructor shown in Table 7.5 that accepts the path to the XML document to transform. You could also load the XPathDocument with XML data contained in a Stream (a FileStream for instance), an XmlReader, or a TextReader. Having these different constructors offers you complete control over how transformations will be carried out in your ASP.NET applications. Which one you use will depend on how you choose to access your application's XML documents. Listing 7.8 instantiates an XPathDocument class by passing in an XmlTextReader object.
Listing 7.8 Instantiating an XPathDocument Class
<%@ Import Namespace="System.Xml" %> <%@ Import Namespace="System.Xml.XPath" %> <%@ Import Namespace="System.IO" %> <%@ Import Namespace="System.Text" %> <script language="C#" runat="server"> public void Page_Load(Object sender, EventArgs E) { string xmlPath = Server.MapPath("listing7.1.xml"); string xslPath = Server.MapPath("listing7.2.xsl"); FileStream fs = new FileStream(xmlPath,FileMode.Open, FileAccess.Read); StreamReader reader = new StreamReader(fs,Encoding.UTF8); XmlTextReader xmlReader = new XmlTextReader(reader); //Instantiate the XPathDocument Class XPathDocument doc = new XPathDocument(xmlReader); Response.Write("XPathDocument successfully created!"); //Close Readers reader.Close(); xmlReader.Close(); } </script>
Running the code shown in Listing 7.5 will write out "XPathDocument successfully created!" to the browser. You'll certainly agree that because it has simply readied the XML document for transformation, this code doesn't buy you much. To actually transform the XML document using XSLT, you'll need to use another class named XslTranform.
The XslTransform Class
The XslTransform class is found in the System.Xml.Xsl namespace. Using it is as easy as instantiating it, loading the XSLT document, and then calling its Transform() method. Tables 7.6 and 7.7 show the different properties and methods found in the XslTransform class.
Table 7.6 XslTransform Class Properties
Property |
Description |
XmlResolver |
The XmlResolver property can be used to specify a resolver class used to resolve external resources. For example, it can be used to resolve resources identified in xsl:include elements. If this property is not set, the XslTransform class will use the relative path of the supplied XSLT style sheet to resolve any included style sheets. |
|
Chapter 5 showed an example of using the XmlUrlResolver class to access authenticated documents. |
Table 7.7 XslTransform Class Methods
Method |
Description |
Load() |
Loads an XSLT document. This method can accept an XmlReader, a document URL, or a variety of other objects. |
Transform() |
The Transform() method is overloaded and can therefore accept a variety of parameters. The most common form of the method that you'll likely use in your ASP.NET applications is shown next (check the .NET SDK for the other overloaded versions of the method): |
|
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 named golferName to the XsltArgumentList collection:
XSLT Code:
<xsl:param name="golferName"/>
ASP.NET Code:
XsltArgumentList args = new XsltArgumentList(); args.AddParam("golferName","","Dan");
This code allows the XSLT parameter named golferName to be assigned a value of Dan. Although the value of Dan was hard-coded into the AddParam() method, it could just as easily be dynamically pulled from a text box or drop-down box, as you'll see in the next example. Because the XsltArgumentList class relies on the HashTable class behind the scenes, multiple parameter name/value pairs can be added and stored.
After an XsltArgumentList class has been instantiated and filled with the proper name/value pairs, how do the parameters in the XSLT style sheet get updated with the proper values? The answer is to pass the XsltArgumentList into the XslTransform class's Transform() method, as shown next:
//Create the XPathDocument object XPathDocument doc = new XPathDocument(Server.MapPath("Listing7.1.xml")); //Create the XslTransform object XslTransform xslDoc = new XslTransform(); xslDoc.Load(Server.MapPath("Listing 7.4.xsl")); //Create the XsltArgumentList object XsltArgumentList args = new XsltArgumentList(); args.AddParam("golferName","","Dan"); //Perform the transformation - pass in the parameters in the XsltArgumentList xslDoc.Transform(doc,args,Response.Output);
In the next section you'll be presented with an ASP.NET application that does this task.
Putting It All Together
You've now seen the main XSLT classes built in to the .NET framework. In this section you'll see how these can be used to build a simple ASP.NET application that allows a user to select a specific golfer's information from an XML document. After the golfer is chosen, XSLT will be used along with the XPathDocument, XslTransform, and XsltArgumentList classes to display the golfer's information. Figures 7.4 and 7.5 show screen shots of the two pages involved in the sample XSLT application.
Figure 7.4 The golfer selection form.
Figure 7.5 The XSLT-generated results of the golfer selection.
To build this application, code-behind techniques were used in the ASP.NET page. If you're not familiar with this mechanism in ASP.NET coding, it allows the actual program code to be stored separately from the visual portion (the HTML) found in the ASP.NET page. The technique of placing all the code (programming code and HTML) into one ASP.NET page shown in many places throughout the book was used simply to make listings easier to read and follow. In practice, however, it's highly recommended that you leverage code-behind techniques to keep your ASP.NET code more maintainable.
For this example, a file named xsltGolfer.aspx.cs contains all the programming code for the listings that follow, and xsltGolfer.aspx contains the HTML. The XSLT style sheet used for the application is named xsltGolfer.xsl. Let's start by examining what code is executed when the ASP.NET page first loads (the Page_Load event). As you'll see in Listing 7.10, this code takes care of loading all the firstName element values found in the XML document into a drop-down box.
Listing 7.10 The Page_Load Event and FillDropDown() Method (xsltGolfer.aspx.cs)
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.