Using JavaScript with XSLT Style Sheets
- Extending the Basic Style Sheet
- Define the Functionality
- Summary
Most XML developers are familiar with the use of XSLT style sheets to transform their XML data from one from one structure to another. For example, the following XML
<?xml version="1.0"?> <personnel> <employee> <fullname>Karen Smith</fullname> <phone>352-555-1234</phone> </employee> <employee> <fullname>John Drew</fullname> <phone>329-555-8827</phone> </employee> </personnel>
can be easily translated to
<html> <head> <title>Phone List</title></head> <body> <h1>Employee List</h1> <ul> <li>Karen Smith (352-555-1234)</li> <li>John Drew (329-555-8827)</li> <li>Frank McCartney (983-555-8273)</li> </ul> </body> </html>
with the appropriate style sheet, such as
<?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/"> <html> <head><title>Phone List</title></head> <body> <h1>Employee List</h1> <ul> <xsl:apply-templates/> </ul> </body> </html> </xsl:template> <xsl:template match="employee"> <li> <xsl:value-of select="fullname"/> (<xsl:value-of select="phone"/>) </li> </xsl:template> </xsl:stylesheet>
The templates and XPath functions used by them allow for fairly complex and sophisticated transformations, but even they are sometimes not enough.
For this reason, the XSLT Recommendation provides a way for XSLT implementers to extend, or add functionality to their applications. For example, the Xalan-Java 2 XSLT processor from the Apache project has a redirect extension that allows developers to indicate that subsequent content should be sent to a new file, allowing an application to split an XML file into, say, different pages for each subject.
The recommendation doesn't stop there, however. It also allows implementers to define a way for developers to add their own functionality using external applications in languages such as Java, C++, or (as in the case of this article) JavaScript.
These extensions take the form of extension functions, which are used in a way similar to XPath functions; and extension elements, which provide even more functionality.
The Plan
Suppose you have an XML file that contains commerce information, and you want to transform it into a summary of orders. Unfortunately, the XML file doesn't actually contain any customer information. Instead, it contains a customer identifier that refers back to a database.
This article will chronicle the building of a style sheet that uses extension functions and elements to allow calculations and a call to an external function that would, in a production application, return the customer information.
(You can also download the source file and style sheet here, along with a command line script to execute the transformation.)
Source and Result Files
Let's start with a look at where we're starting out, and where we're going. The original XML file contains just the basics, as shown in Listing 1.
Listing 1The Source Document
<?xml version="1.0"?> <orders> <order orderid="23" custid="969982"> <product itemid="ATL322"> <quantity>1</quantity> <price>280</price> </product> <product itemid="ORE948"> <quantity>3</quantity> <price>49</price> </product> </order> <order orderid="24" custid="39488"> <product itemid="ZPI88"> <quantity>200</quantity> <price>1.72</price> </product> </order> </orders>
The end result is a Web page that shows each order, including the extended price for each item and the customer information, as shown in Listing 2 and Figure 1.
Listing 2The Final Result
<html> <head><title>Order Summaries</title></head> <body> <h1>Order Summaries</h1> <p><b>Customer Information: </b>Darling Clementine, Inc., 228 Range Road, Oklahoma City, OK 73101 <br /><b>Order number: </b>23</p> <table border="1" width="50%"> <tr> <th>Product Number</th> <th>Quantity</th> <th>Unit Price</th> <th>Extended Price</th> </tr> <tr> <td>ATL322</td><td>1</td><td>280</td><td>280</td> </tr> <tr> <td>ORE948</td><td>3</td><td>49</td><td>147</td> </tr> <tr> <td align="right" colspan="3">Order Total: </td> <td>427</td> </tr> </table> <br> <hr> <br> <p><b>Customer Information: </b>Into the Wind, LLC, 849 Research Way, Suite 399, Miami Beach, FL 33109 <br /><b>Order number: </b>24</p> <table border="1" width="50%"> <tr> <th>Product Number</th> <th>Quantity</th> <th>Unit Price</th> <th>Extended Price</th> </tr> <tr> <td>ZPI88</td><td>200</td><td>1.72</td><td>344</td> </tr> <tr> <td align="right" colspan="3">Order Total: </td> <td>344</td> </tr> </table> <br> <hr> <br> </body> </html>
Figure 1 The final result.
The extended prices and order total will come from extension functions, and the customer information will be provided by an extension elements. We'll also use extension elements to "zero out" the totals between orders.
Extending the Basic Style Sheet
We'll start by putting together the basic style sheet, with placeholders for the extensions. The overall format is laid out in Listing 3.
Listing 3The Basic Style Sheet
<?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/"> <html> <head><title>Order Summaries</title></head> <body> <h1>Order Summaries</h1> <xsl:apply-templates/> </body> </html> </xsl:template> <xsl:template match="order"> <p><b>Customer Information: </b>(CUSTOMER INFORMATION) <br /><b>Order number: </b><xsl:value-of select="@orderid"/></p> <table width="50%" border="1"> <tr> <th>Product Number</th> <th>Quantity</th> <th>Unit Price</th> <th>Extended Price</th> </tr> <xsl:apply-templates select="product"/> <tr> <td colspan="3" align="right">Order Total: </td> <td>(ORDER TOTAL)</td> </tr> </table> <br /><hr /><br /> </xsl:template> <xsl:template match="product"> <tr> <td><xsl:value-of select="@itemid"/></td> <td><xsl:value-of select="quantity"/></td> <td><xsl:value-of select="price"/></td> <td>(EXTENDED PRICE)</td> </tr> </xsl:template> </xsl:stylesheet>
All of this is just standard XSLT. The placeholders are shown in bold.
Adding an Extension Function
Now let's add in the extensions to see how they're used. Later, we'll define the functionality that goes behind them.
We'll start with the extension function. XPath has a number of built-in functions, such as round() and concat(). These functions can take XPath expressions as arguments, and because they themselves are XPath expressions, we output them using the value-of element. For example, we could round off prices to the nearest dollar using the round() function:
... <xsl:template match="product"> <tr> <td><xsl:value-of select="@itemid"/></td> <td><xsl:value-of select="quantity"/></td> <td><xsl:value-of select="round(price)"/></td> <td>(EXTENDED PRICE)</td> </tr> </xsl:template> ...
In this same way, we can add a new function, which we'll define later, as shown in Listing 4.
Listing 4Adding an Extension Function
<?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:totalSys="TotalSystem" version="1.0"> ... <xsl:template match="order"> <p><b>Customer Information: </b>(CUSTOMER INFORMATION) <br /><b>Order number: </b><xsl:value-of select="@orderid"/></p> <table width="50%" border="1"> <tr> <th>Product Number</th> <th>Quantity</th> <th>Unit Price</th> <th>Extended Price</th> </tr> <xsl:apply-templates select="product"/> <tr> <td colspan="3" align="right">Order Total: </td> <td><xsl:value-of select="totalSys:totalCost()"/></td> </tr> </table> <br /><hr /><br /> </xsl:template> <xsl:template match="product"> <tr> <td><xsl:value-of select="@itemid"/></td> <td><xsl:value-of select="quantity"/></td> <td><xsl:value-of select="price"/></td> <td><xsl:value-of select="totalSys:getExtPrice(number(quantity), number(price))"/></td> </tr> </xsl:template> ...
Here, we defined two functions: totalCost(), which takes no arguments; and getExtPrice(), which takes as arguments the quantity and price children. (The getExtPrice() function also updates the running total value, representing the total cost of the order.) XPath normally converts elements to text values, but eventually we'll want to perform arithmetic on them, so we'll use XPath's built-in number() function to change their type before passing them to the script.
Both functions have a namespace prefix, totalSys, to tell the processor that we're not dealing with built-in functions. The namespace itself doesn't matter, as long as it's unique.
Ultimately, the processor will output the values returned by these functions just as if it were one of the built-in functions.
Adding an Extension Element
The purpose of an extension element is a bit different from that of a function. Although an extension element can return a value for output, elements are usually used more to cause something to be done.
For example, we have two things that we want done: We want to retrieve the customer information from the database, and we want to reset the order total for each order. The elements are added in Listing 5.
Listing 5Adding Extension Elements
... <xsl:template match="order"> <p><b>Customer Information: </b><totalSys:customerInfo/> <br /><b>Order number: </b><xsl:value-of select="@orderid"/></p> <table width="50%" border="1"> <tr> <th>Product Number</th> <th>Quantity</th> <th>Unit Price</th> <th>Extended Price</th> </tr> <totalSys:resetTotal resetTo="0"/> <xsl:apply-templates select="product"/> <tr> <td colspan="3" align="right">Order Total: </td> <td><xsl:value-of select="totalSys:totalCost()"/></td> </tr> </table> <br /><hr /><br /> </xsl:template> ...
In the first case, we're simply going to return customer information. In the second, we're using a custom attribute to pass information to the script. This element won't actually output any information, but it will reset the running total for each order.
Now, all we need is a way to tell the processor what it's supposed to do when it encounters these extensions.