XSL gives your XML some style Wow, you've come so far. A year ago you didn't know what XML was, but now it's everywhere. You're storing XML in databases and using it in middleware, and now you want to present that data to a browser. That's where XSL can help. XSL can help you turn your XML into HTML. Moreover, servlets provide a powerful medium to perform those translations as they reside on the server and have access to all the features of server-side Java. In this article, we'll cover the basics of XSL and XSL processors. If you don't know much about XML, you may want to first read Mark Johnson's excellent XML article, "Programming XML in Java, Part 1." Our example will use a servlet to turn well-formed XML into HTML. If you need to learn more about servlets, please refer to Sun's servlets tutorial (see Resources).
XML and XSL The process of transforming and formatting information into a rendered result is called styling. Two recommendations from the W3C come together to make styling possible: XSL Transformations (XSLT), which allows for a reorganization of information, and XSL, which specifies the formatting of the information for rendering. With those two technologies, when you put your XML and XSL stylesheet into an XSL processor, you don't just get a prettied up version of your XML. You get a result tree that can be expanded, modified, and rearranged. An XSL processor takes a stylesheet consisting of a set of XSL commands and transforms it, using an input XML document. Let's take a look at a simple example. Below we see a small piece of XML, describing an employee. It includes his name and title. Let's assume that we would like to present that in HTML. <employee id="03432">
Joe Shmo Manager
If we wanted our HTML to look like this:
Joe Shmo: Manager
Then we could use a stylesheet, such as the one below, to generate the HTML above. The stylesheet could reside in a file or database entry: <xsl:stylesheet xmlns:xsl=""> <xsl:template match="/">
<xsl:value-of select="employee/name"/> <xsl:text>: <xsl:value-of select="employee/title"/>
Common XSL stylesheet commands Stylesheets are defined by a set of XSL commands. They make up valid XML documents. Stylesheets use pattern matching to locate elements and attributes. There are also expressions that can be used to call extensions -- either Java objects or JavaScript. Let's look at some XSL commands. Stylesheet declaration The stylesheet declaration consists of a version and namespace. The namespace declares the prefix for the tags that will be used in the stylesheet and where the definition of those tags are located: <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> . . .
If there are any extensions referenced, the namespace must be specified. For example, if you were going to use Java, you would specify this namespace:
XSL gives your XML some style Use XSL and servlets to style your XML data xmlns:java="http://xml.apache.org/xslt/java"
Pattern matching When selecting in a stylesheet, a pattern is used to denote which element or attribute we want to access. The syntax is simple: specify the node you want, using / to separate the elements. Notice that in the sample XML code above we matched our template on /, which is the root node. We could have, however, matched on the employee node. Then a select statement could just refer to the name node instead of employee/name. For example, if we had the following XML: <employee id="03432">
Joe Shmo Manager
Attributes can also be selected. The employee id could be accessed by saying employee/@id. Groups of nodes can be accessed by using employee/*. A specific employee could be located using employee/@id='03432'. Pattern matching allows us to select specific values out of our XML document. The <xsl:value-of select... command gives us the ability to select a value for our resulting XML document, as seen in the table below.
Command <xsl:value-of select="employee/name"/> <xsl:value-of select="employee/@id"/> Accessing elements versus attributes
Result Joe Shmo 03432
Templates
Templates provide a way to match nodes in an XML document and perform operations on them. The syntax for a template is: <xsl:template match="nodename"> . . .
The template is matched on a node name, then all the stylesheet commands in that template are applied. We can call templates in our stylesheet by using the apply-templates command: <xsl:apply-templates select="nodename"/>
An example using our employee XML above would be: <xsl:template match="name" <xsl:value-of select="."/>
We can call this template anywhere there is a name node to be referenced, using this: <xsl:apply-templates select="name"/>
Logical commands There are a few structures available for doing ifs and loops. Let's take a look at the syntax.
Choose command The choose command provides a structure to test different situations. <xsl:choose> <xsl:when test="test situation"> stylesheet commands <xsl:otherwise> stylesheet commands
The first successful test will result in that block's stylesheet commands executing. If all the tests fail, the otherwise block is executed. You may have as many when blocks as you want. The otherwise block must always be present; if you don't want to do anything in your otherwise block, just put: <xsl:otherwise/>
If command The if command provides only a single test and doesn't have any kind of else structure available. If you need to have an else, use the choose command. <xls:if test="test situation"> ...
Loops (for-each command) Unlike most languages with for and while structures, XSL offers only the for-each command. As such, you can loop on a set of nodes or you can select the nodes you want to loop on, using a pattern match: <xsl:for-each select="select statement"> ...
For example, if you had more than one employee in your XML document, and you wanted to loop through all the managers, you could use a statement such as this:
XSL gives your XML some style Use XSL and servlets to style your XML data <xsl:for-each select="employee[title='Manager']"> ...
Variables
The variable command provides a way to set a variable and access it later. The extension mechanism uses variables to store the values retrieved from extensions: <xsl:variable name="count">assign value to count here
The variable count can be accessed by using $count later in the stylesheet: <xsl:value-of select="$count"/>
Parameters You can pass parameters to your stylesheet, using the param tag. You can also specify a default value in a select statement. The default is a string, so it must be in single quotes: <xsl:param name="param1" select="'default value'"/>
You can set the parameters you are passing to your stylesheet on your XSLProcessor object: processor.setStylesheetParam("param1", processor.createXString("value"));
Extensions Extensions add functionality to a stylesheet. XSL comes with some basic functions: •
sum()
•
count()
•
position()
•
last()
-- Sum the values in designated nodes -- Count the nodes -- Returns the position of the current node in a loop
-- Test whether this is the last node; this function returns a boolean value
If you want additional functionality, you need to use extensions. Extensions can be called anywhere a value can be selected. Extensions to a stylesheet can be written in Java or JavaScript, among other languages. We'll concentrate on Java extensions in this article. In order to call extensions in Java, the java namespace must be specified in your stylesheet declaration: xmlns:java="http://xml.apache.org/xslt/java"
Any calls to Java extensions would be prefaced with java:. (Note: You don't have to call your namespace java; you can call it whatever you want.) You can do three things with Java extensions: create instances of classes, call methods on those classes, or just call static methods on classes. Table 2 shows syntax that can be used to reference Java objects. Instantiate a class: prefix:class.new (args) Example: variable myVector select"java:java.util.Vector.new()"
Call a static method: prefix:class.methodName (args) Example: variable myString select="java:java.lang.String.valueOf(@quantity))"
Call a method on an object: prefix:methodName (object, args) Example: variable myAdd select="java:addElement($myVector, string(@id))" Table 2. Three ways to use Java objects (For more on XSL, see Elliotte Harold's The XML Bible in Resources.)
The XSL Processor API For our example, we will use Lotus' implementation of Apache's XSL processor Xalan (see Resources). We'll use the following classes in our servlet example: Class com.lotus.xsl.XSLProcessor com.lotus.xsl.XSLProcessor is the processor that implements the functionality defined in org.apache.xalan.xslt.XSLTProcessor. The default constructor can be used and processing can take place using the process() method, as seen below: void process(XSLTInputSource inputSource, XSLTInputSource stylesheetSource, XSLTResultTarget outputTarget)
The process() method transforms the source tree to the output in the given result tree target
XSL gives your XML some style Use XSL and servlets to style your XML data The void reset() method, to be used after process(), resets the processor to its original state. The process() method is overloaded 18 times. Each signature provides a different way to process your XML and XSL. Some return an org.w3c.dom.Document object. I have found that the above process() method is the handiest; the documentation recommends its use because of the XSLTInputSource (used to read in XML or XSL) and XSLTResultTarget (used to write out the results) classes, which we examine next in turn. Class com.lotus.xsl.XSLInputSource The XSLInputSource class can be created by using any of the following constructors: •
XSLTInputSource():
•
XSLTInputSource(org.xml.sax.InputSource isource):
•
XSLTInputSource(java.io.InputStream byteStream):
•
XSLTInputSource(org.w3c.dom.Node node):
•
XSLTInputSource(java.io.Reader characterStream):
•
XSLTInputSource(java.lang.String systemId):
Zero-argument default constructor Creates a new
XSLTInputSource source from a SAX input source Creates a new input source
with a byte stream Creates a new input source with a DOM
node Creates a new input source
with a character stream system identifier
Class com.lotus.xsl.XSLResultTarget
Creates a new input source with a
The XSLResultTarget class can be created by using any of the following constructors: •
XSLTResultTarget():
•
XSLTResultTarget(org.xml.sax.DocumentHandler handler):
•
XSLTResultTarget(org.w3c.dom.Node n):
•
XSLTResultTarget(java.io.OutputStream byteStream):
•
XSLTResultTarget(java.lang.String fileName):
•
XSLTResultTarget(java.io.Writer characterStream):
Zero-argument default constructor
Creates a new output target with a SAX Document handler, which will handle result events Creates a new output target with a
character stream Creates a new output
target with a byte stream Creates a new output target with a
file name Creates a new output target
with a character stream
The API also includes classes that enable you to listen for events -- problems or document events, for example -- during processing.
Put it all together Now that we have a basic overview of the XSL API and XSL stylesheets, let's look at a problem that those technologies could help us solve. In this example, we've got a server-side process that uses XML to store transaction data. There is a business need for the ability to analyze the day's transactions. The solution should be as unobtrusive as possible, leaving the company's current process and data storage procedures unchanged. Here are some of the possible solutions using XSL: 1. Create a periodically running program on the server that will use XSL to transform XML into HTML. The resulting HTML files will be stored on a file server where they can be accessed by a rowser. 2. If you have a controlled set of users, make sure they have browsers that support XSL. In that case, your XML will include a header denoting which XSL document to use, allowing the browser to perform the translation for you. 3. Use XSL in a server-side medium such as servlets. Pull the XML from wherever it may be in a file, a database, or a message queue, then style the XML using XSL.
We'll use approach three, but the first and second approaches are both valid. Approach one would solve any performance problems, but it doesn't provide the dynamic HTML creation that will make our application special. Approach two will be a real possibility soon -- Internet Explorer 5 can do XSL translations, and Netscape has announced that its next release will, too. However, your users won't all have the newest browsers, and we still have to do server-side processing to pull the XML data.
XSL gives your XML some style
Use XSL and servlets to style your XML data Moving on, let's look at the XML schema for our transactions:
Note: XSL doesn't require a DTD to do its processing -- a good feature. Therefore, if minor changes occur in the schema, the same stylesheets can still be used. In our example, four stylesheets will be created, each representing a different view: 1. Transaction view: A list of each day's transactions, including invoice number, customer
name, and invoice total. 2. Products sold view: A list of all the products sold for each day, including the quantity. 3. Detail view: A drill-down from the Transaction and Products sold views. When the user
clicks the invoice number, the details for the transaction display. 4. XML view: A display of the XML we are processing against.
Those examples will lack spinning logos, thus demonstrating the power of XSL without cluttering the output with graphics. The HTML that calls the servlet will use frames. The left frame will have a list of the stylesheets available. When a stylesheet is selected, a request will be sent to the servlet. Then the servlet will open the XML and the stylesheet, and use the XSLProcessor to do the translation. The translation will be in the form of HTML, which will be pushed out to the browser. Let's go over the Transaction view, which shows the list of invoices, then the Detail view, which shows the details of the invoice. The Transaction view Here's the Transaction view -- a list of the day's invoices: <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:java="http://xml.apache.org/xslt/java" exclude-result-prefixes="java"
version="1.0"> <xsl:output method="html" indent="yes"/> <xsl:template match="/orders">
Today's Invoices
Invoice Number | Customer Name | Total Purchase | <xsl:for-each select="invoice"> <xsl:element name="a"> <xsl:attribute name="href"><xsl:text>xsl?stylesheet=detail&invoiceNum=<xsl:valueof select="@number"/> <xsl:value-of select="@number"/> | <xsl:value-of select="name"/> | <xsl:value-of select="java:SumInvoice.summ(item)"/> |
Figure 1 shows the results.
Figure 1. Stylesheet 1: Output from the Transaction view (40 KB)
The Detail view Now we turn our attention to the Detail view: <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:java="http://xml.apache.org/xslt/java" exclude-result-prefixes="java" version="1.0"> <xsl:param name="invoiceNum" select="string('none')"/> <xsl:output method="html" indent="yes"/> <xsl:template match="/">
<xsl:for-each select="orders/invoice[@number=$invoiceNum]"> Invoice Number:<xsl:value-of select="@number"/> | <xsl:value-of select="name"/> | <xsl:apply-templates select="address"/> | Items | Part | Quantity | Cost | Subtotal | <xsl:for-each select="item"> <xsl:value-of select="@part"/> | <xsl:value-of select="@quantity"/> | <xsl:value-of select="@cost"/> | <xsl:value-of select="@quantity * @cost"/> | <xsl:if test="last()"> <xsl:text>Total | <xsl:value-of select="sum(item/@quantity)"/> | <xsl:text> | <xsl:value-of select="java:SumInvoice.summ(item)"/> | |
<xsl:template match="address">
<xsl:choose> <xsl:when test="@type='shipping'"> <xsl:text>Shipping Address <xsl:when test="@type='billing'"> <xsl:text>Billing Address <xsl:otherwise> <xsl:text>Other Address |
<xsl:value-of select="street"/> |
<xsl:value-of select="city"/><xsl:text> <xsl:value-of select="state"/>, <xsl:value-of select="zip"/> |
XSL gives your XML some style Use XSL and servlets to style your XML data
Figure 2. Stylesheet 3: Output from the Detail view (48 KB)
Wrap it up The servlet will extend the HTTPServlet Java class and employ the doPost() and doGet() methods. The XSLInputSource gives us the ability to just hand the XSLProcessor a java.io.Reader object -- perfect since our XML and XSL will reside in flat files. If we had these in any other place, we could just use a StringReader. The XSLInputSource will also accept a org.w3c.dom.Node.
The XSLTResultTarget writes out to an OutputStream -- convenient, since we can get an OutputStream from our HTTPResponse. import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.util.*; import com.lotus.xsl.XSLTInputSource; import com.lotus.xsl.XSLTResultTarget; import com.lotus.xsl.XSLProcessor; import org.xml.sax.SAXException; public class XSLServlet extends HttpServlet { public void init(ServletConfig config) throws ServletException { super.init(config); } //By redirecting our doGets to the doPost method we can have a stylesheet //executed from a link //ex.
00002 public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } //Process the HTTP Post request public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter out = new PrintWriter (response.getOutputStream()); //Get the stylesheet selected by the user String stylesheet = request.getParameter("stylesheet"); //Get the invoice number selected by the user, this is used only for certain //stylesheets. If it is not present, the stylesheet won't use it. String invoiceNum = request.getParameter("invoiceNum"); //Prepare FileReaders for the stylesheet and XML FileReader xslReader = new FileReader(".\\javaworld\\xsl\\"+stylesheet+".xsl"); FileReader xmlReader = new FileReader(".\\javaworld\\xml\\"+"invoice.xml"); response.setContentType("text/html"); try { //Create an instance of an XSLProcessor XSLProcessor proc = new XSLProcessor(); //Set the invoice number parameter proc.setStylesheetParam("invoiceNum", invoiceNum); //Process the stylesheet, all the output will go straight // out to the browser. proc.process(new XSLTInputSource(xmlReader),new XSLTInputSource(xslReader),new XSLTResultTarget(out)); } catch (SAXException saxE) { out.write(saxE.getMessage()); saxE.printStackTrace(out); } out.close(); }
//Get Servlet information public String getServletInfo() { return "XSLServlet Information"; } }
And that's all there is to it!
Conclusion You have seen how to extract XML as HTML, using XSL. But as I mentioned in the beginning of the article, XSL can be used to transform XML from one doc type to another -- XML to HTML, HTML to XML, HTML to HTML, XML to DSML (Directory Service Markup Language), and so on. XSL's most useful feature: it doesn't have to be compiled. The way the servlet example works, we could add stylesheets to our stylesheet folder, then add the stylesheet to our list of stylesheets in our HTML. Another handy XSL use: take a java.sql.Result set and turn it into a simple XML document in which the name of the query is the root node and each row in the result table is an element in our XML document. Then the subelements of that element are the columns and values from that row. Taking that plain, well-formed XML document, we can simply apply XSL to it, creating an XML document that conforms to a desired schema. That would be nice for EDI (Electronic Data Interface) or anything else where you need to extract data from a relational database such as XML. A word on performance: the XSLProcessor runs very fast; it completes transformations in subsecond time. Performance degrades as the stylesheet does more processing or more complex processing. The input XML has to get pretty big -- over 100 KB -- before you will notice a significant decrease in performance. In conclusion, XSL provides an easy way to change XML to HTML. But it can do much more than that. The extension mechanism gives added functionality to the basics of XSL. Using servlets to process your XSL will help you keep your content and presentation separate.