I'm having trouble converting an old Extensible Stylesheet Language (XSL) style sheet to Extensible Stylesheet Language Transformations (XSLT). The style sheet takes an arbitrary XML-formatted data set that ADO's Save method produces and builds an HTML table (including column headers) with the data. I tried to use the XSL context() operator, but XSLT doesn't support that operator. Without using the context() operator, how do I access data values from previous selections that have occurred within the style sheet? I'm also having difficulty formatting date fields. How can I obtain a user-defined date format in XSLT?
The context() operator is one of the features that XSL supports but that XSLT doesn't support. (For background information about XSL and its trip through the wilds of becoming a standard, see the Web-exclusive sidebar "XSL to XSLT: Terminology and Background." See "More on the Web," page 48, for instructions.) The context() operator provided access to previous context nodes in the source document's query hierarchy. The source document is the XML document that you apply the style sheet to. The query hierarchy is the set of selections that occurs within the style sheet as the style sheet processes the source document. Selections occur when an XSL style sheet executes xsl:for-each, xsl:apply-templates, or other elements that change the node that the style sheet is processing.
For example, if I have a template within a style sheet that matches the root element, then uses an xsl:for-each element to iterate through the root element's children, the root element represents the first context node in the query hierarchy. As the xsl:for-each element processes the root element's children, each child in turn becomes the second context node. When executing an XSL style sheet, the XSL engine maintains the query hierarchy in a stack of context nodes that the context() operator can access.
Listing 1 shows an example of the XSL style sheet, which builds an HTML table by using an XML document output containing data from an ADO Recordset. The XML document is produced by ADO's Recordset.Save() method. The style sheet produces the header row for the HTML table from the XML Data Reduced (XDR) schema that ADO included in XML. Each AttributeType definition, which defines an XML attribute to hold the data from a column of the recordset, in the XDR schema represents a table column. Web Figure 1 shows an example of the XML output from ADO.
The XSL style sheet uses two templates: one to build the table, including the column headers, and the other to build the individual rows in the table. The first template, at callout A in Listing 1, produces the column headers, using the order-by attribute to sort the headers alphabetically. After producing the column headers, the style sheet executes another template, which sorts and processes all row elements by calling
<xsl:apply-templates select="/xml/rs: data/z:row" order-by="@CustomerID" />
The xsl:apply-templates element instructs the XSL engine to find a matching template for each z:row element in the XML document. The order-by attribute sorts the rows by CustomerID. Then, the template at callout B in Listing 1 is executed for each row.
To ensure that the table displays the data columns in the same order as the header columns, the template at callout B iterates through the AttributeType definitions, sorted by name, in the XDR schema by using the xsl:for-each element. As the xsl:for-each element selects each AttributeType, the context node changes to the node corresponding to the AttributeType, instead of the row element that the template at callout B is processing that contains the data we're attempting to access and output. In other words, because the xsl:for-each element introduces a new context, you have to use the context() operator in the xsl:value-of element at callout B to select the attribute that contains the data for the correct column in the HTML table.
The XSL Pattern (which I discuss in "XSL to XSLT: Terminology and Background") in the select attribute of the xsl:value-of element in Listing 1 uses context(-2) to select the row that the template is processing. Using context(-2) seems peculiar because we're trying to access the previous context, not the second previous one. But the syntax is correct because context(-1) selects the current context. The XSL Pattern then selects all attributes in the row element by using @* and a predicate to select the one attribute that matches the desired column name for the table. The result is the HTML table that Figure 1 shows.
Converting the XSL style sheet that Listing 1 shows to XSLT is straightforward, even though XSLT has no context() operator. Instead of the context() operator, XSLT supports variables. An XSLT variable is a named storage location much like the variables that many programming languages use. The only difference is that XSLT variables are immutable; that is, after you declare a variable and set its value, you can't change that value. An XSLT variable can hold one value or a nodeset, which is a collection of nodes. You can use an XSLT variable to store the context node (as a nodeset with one node) before you execute a construct that changes the context, such as an xslt:for-each element. You can then use the variable to refer to the previously stored node within the new context.
Web Listing 2 shows an XSLT style sheet converted from the XSL style sheet in Listing 1. The XSL style sheet requires a number of updates to make it compatible with the XSLT standard. These updates include converting the order-by attributes to the xslt:sort element and adding namespace declarations for the prefixes referenced in the XML Path Language (XPath) select attributes. Callout A in Web Listing 2 shows the revised template for processing row elements.
The revised template for row elements begins by declaring the variable current_row, which the template sets to the row element being processed. The XPath expression '.' selects the current context node (the node being processed by the template). The XSLT style sheet iterates through the AttributeType elements in the XDR schema to output the table columns in the correct order. Directly after the xslt:for-each element, the template declares the attrName variable to hold the name of the AttributeType being processed. Together, the current_row and attrName variables are used with xslt:value-of to obtain the value of the attribute on the row element being processed. This produces the desired HTML table, except for formatting the columns that contain dates.
Formatting the date columns is easy if you use a Microsoft XPath extension. The format-date() function lets you use a pattern string to specify the date's format. The format-date() function takes two parameters: an expression that resolves to date and a format string. To use the extension function, you need to use a namespace to qualify the function. XPath uses namespaces to ensure that names of vendor-supplied extension functions won't collide with the names of future XPath built-in functions. The code at Callout B in Web Listing 2 uses the ms prefix, which the xslt:stylesheet element declares. An xslt:choose element performs a check similar to an if-then-else construct, common to programming languages, to determine whether the value of the attribute being processed is a dateTime type. If so, the style sheet formats the date by using the format-date() function. Otherwise, it outputs the value as is.
To run the example, you have to save the XSLT style sheet that Web Listing 2 shows to a file called RStoTable.xslt in a directory of your choice on your computer. Also, you have to save the Visual Basic (VB) script that Web Listing 3 shows to a file named FormatTable.vbs in the same directory. The script contains comments explaining the steps you need to take to execute the XSLT style sheet from Web Listing 2. You might need to update the script's connection string to access your database. To execute the script, enter
at a command prompt. The script creates a file called debug.html in the directory you choose that contains the table output from the XSLT style sheet. You also need Microsoft XML Core Services (MSXML) 4.0 to run the example. (To download MSXML 4.0, go to http://msdn.microsoft.com/xml.)