[ Team LiB ] Previous Section Next Section

Extensible Stylesheet Language

In the HTML world, a CSS file is a style sheet that describes how browsers should interpret the HTML file in a standard manner. It is in this file that you describe the display properties of different HTML tags; for example, a table. This provides a level of separation between the formats of the HTML document and its contents. However, by definition, HTML does not provide the kind of separation that XML does. So, how do we specify the display formats for an XML file? We use Extensible Stylesheet Language, or XSL. XSL is a W3C standard and you can learn more about it by visiting the Web site at http://www.w3.org/Style/XSL.

XSL consists of the following components:

  • XSLT A language to transform the XML to its desired output. The desired output may be another XML document or any other format such as HTML.

  • XPath A language for defining the contents of the XML document.

XSL Style Sheet

To better understand XSL, we'll work with an example. Consider the XML document shown in Listing 29.8, which describes a list of books, each book's publisher, its year of publication, and its price. This document lists four books. You can find the schema that describes this document on the CD, under the name booklist.xsd. The corresponding XML file is named booklist.xml.

Listing 29.8 Booklist.xml
<?xml version="1.0" encoding="UTF-8"?>
<booklist>
 <book name="Adventures of Tintin Vol. 1">
  <publisher>Little Brown &amp; Co </publisher>
  <isbn>0316359408</isbn>
  <price>14.00</price>
 </book>
 <book name="The Ultimate Simpsons in a Big Ol&apos; Box">
  <publisher>Harperperennial Library</publisher>
  <isbn>0060516305</isbn>
  <price>35.00</price>
 </book>
 <book name="Bart Simpson&apos;s Guide to Life">
  <publisher>Perennial Press</publisher>
  <isbn>006096975X</isbn>
  <price>15.00</price>
 </book>
 <book name="Harry Potter and the Order of the Phoenix">
  <publisher>Scholastic</publisher>
  <isbn>043935806X</isbn>
  <price>18.00</price>
 </book>
</booklist>

To assign a style sheet for an XML file, you can use the following special XML header, after the XML definition header:


<?xml-stylesheet type="text/xsl"
  href="./GenericRootNodeTemplate.xsl"?>

Let's first write a style sheet that displays this XML file in the browser as a table with the name and the price as columns in a table. To do this, we write the style sheet document. The style sheet document is another XML document (and hence begins with the XML declaration) that conforms to the schema described at the URL http://www.w3.org/1999/XSL/Transform. The root node of the style sheet file is either a stylesheet tag or a transform tag. Thus, the following identifies the beginning of a style sheet document:


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
  xmlns:xsl=" http://www.w3.org/1999/XSL/Transform">

XSL works based on templates. You specify a template and provide the rule under which that template is matched. When matched, the set of tasks indicated under the template is executed. For example, to match the root node of your XML document, you would match for the / tag. So, the following style sheet prints out the phrase Found a Root Node in an HTML document whenever any XML root node is matched:


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:template match="/">
  <html>
   <head>
    <title>Matched</title>
   </head>
   <body>
    Found a Root Node.
   </body>
  </html>
 </xsl:template>
</xsl:stylesheet>

In this example, we look for a root node match and print out the HTML code directly. One thing to keep in mind while writing XSL documents is that, unlike HTML documents, XSL documents do not tolerate HTML code that is not well formed. This is because the XSL document itself is an XML and the HTML code that you write forms part of this XML document. Because any XML document should be well formed, the HTML code inside it is also expected to be well formed.

Now that we've written our first XSL style sheet, let's add a little meat into it. We access the values of the source XML document by using the xsl:value-of tag. This tag is used to access the value from the source XML and write it into the destination stream (which could be an XML or HTML file). We specify the select criterion for the value-of element. The select criterion can be any valid XPath string (we'll look at XPath in a minute). So, in the booklist.xml document, we can access the ISBN number of the book by using the following value-of tag:


<xsl:value-of select="/booklist/book/isbn"/>

If we were to include this tag instead of displaying Found a Root Node in the style sheet, we could actually see the ISBN number of the first book in the XML document.


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:template match="/">
  <html>
   <head>
    <title>Matched</title>
   </head>
   <body>
    <xsl:value-of select="/booklist/book/isbn"/>
   </body>
  </html>
 </xsl:template>
</xsl:stylesheet>

Because we have more than one book in our list of books, we want to display information about all the books, not just the first one in the list. Hence, there is an obvious need for some kind of a looping mechanism. This is achieved by using the xsl:for-each tag. This tag also takes a select criterion (XPath string) and loops through all the entries that are returned by the XPath query. Thus, in our case, we loop through a list of books that are found in the booklist element and display the ISBN number and the price of each book, in a table on the screen.


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:template match="/">
  <html>
   <head>
    <title>Book List</title>
   </head>
   <body>
    <table border="1">
     <tr>
      <th>ISBN</th>
      <th>Price</th>
     </tr>
     <xsl:for-each select="/booklist/book">
       <tr>
        <td><xsl:value-of select="isbn"/></td>
        <td><xsl:value-of select="price"/></td>
       </tr>
     </xsl:for-each>
     </table>
    </body>
  </html>
 </xsl:template>
</xsl:stylesheet>

Within the root template, we first output the header code for the HTML document. We follow that with the code that defines the table and its header. Then we loop for each node that matches the pattern /booklist/book. Each match corresponds to one book in the booklist XML document. For each matched node, we write its ISBN number and the price to one row in the table. Finally, we close the table, body, and html tags. Remember that the document must be well formed, even though HTML is usually very lenient.

Another interesting aspect of the preceding code is the following code snippet:


<td><xsl:value-of select="isbn"/></td>

Note that we specify only isbn in the select criterion, and not the whole path of the tag. This is because the XPath query is smart enough to look for this specified criteria within the select criterion mentioned in the for-each loop. You can still provide the entire path, which would also be correct. You can access other nodes that lie outside the boundaries of the /booklist/book node by providing an absolute path. Think of it as a file system, and you're under a particular directory.

There is a second way to perform the looping action, this time without using the for-each tag. This approach uses the template concept more extensively.

In the previous code snippets, we declared the templates with the match specified as /. As mentioned earlier, this template is always matched when the root node of the XML document is encountered. We can further define templates that match other sub-nodes of your XML document. For example, we could create a template that matches the book node, which can then be applied for every book tag encountered. Note that the transformer does not apply the template for you. You have to request the templates to be applied on the source document.


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:template match="/">
  <html>
   <head>
    <title>Book List</title>
   </head>
   <body>
    <table border="1">
     <tr>
      <th>ISBN</th>
      <th>Price</th>
     </tr>
     <xsl:apply-templates select="/booklist/book"/>
     </table>
    </body>
  </html>
 </xsl:template>
 <xsl:template match="book">
  <tr>
   <td><xsl:value-of select="isbn"/></td>
   <td><xsl:value-of select="price"/></td>
  </tr>
 </xsl:template>
</xsl:stylesheet>

Here we define a template that matches a book tag. When matched, it simply writes out one row of the table. This template is applied within the template for the root node by the following line:


<xsl:apply-templates select="/booklist/book"/>

This is the line that indicates to the transformer that templates should be applied on all tags that matches the given select tag. The transformer then takes care of invoking the appropriate template for each book element before executing following statements within the root template. This is useful not only for looping, but also for controlling the order in which the child nodes are processed within the XML document. For instance, you could write a style sheet that prints all the ISBN numbers and titles, and then prints all prices in a second table.

XSL also enables you to test for conditionals within your style sheet. For instance, if you wanted to print out only those books that are less than $17.00, you could use the xsl:if condition:


<xsl:template match="book">
 <xsl:if test="price &lt; 17">
  <tr>
   <td><xsl:value-of select="isbn"/></td>
   <td><xsl:value-of select="price"/></td>
  </tr>
 </xsl:if>
</xsl:template>

If you want implement an if-then-else block instead, you would use the xsl:choose tag. That tag has following format:


<xsl:choose>
 <xsl:when test="price &lt; 17">
  . Do something in this case
 </xsl:when>
 <xsl:otherwise>
  . Do something else in this case
 </xsl:otherwise>
</xsl:choose>

Thus, the complete XSL style sheet document that displays the booklist as a HTML document is given in Listing 29.9.

Listing 29.9 Booklist.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
 <html>
  <head>
    <title>Book List</title>
  </head>
    <body>
      <table border="1">
       <tr>
        <th>ISBN</th>
        <th>Name</th>
        <th>Price</th>
       </tr>
       <xsl:apply-templates select="/booklist/book"/>
      </table>
     </body>
  </html>
</xsl:template>

<xsl:template match="book">
  <xsl:if test="price &lt; 17">
   <tr>
     <td><xsl:value-of select="isbn"/></td>
     <td><xsl:value-of select="@name"/></td>
     <td><xsl:value-of select="price"/></td>
   </tr>
  </xsl:if>
</xsl:template>
</xsl:stylesheet>

After you've created and linked the XML file and corresponding XSL file, many browsers can transform the XML into the format defined in your XSL. You can also use JAXP transformers to transform the XML document on the server side.

Transforming the XML File

The XML file can be transformed by using the style sheet on either the client side or on the server side. On the client side, a recent popular browser should be compliant with the standards laid out by XSL. By including the stylesheet tag in the XML document and pointing it out to the XSL document, you can simply open the XML document on your browser and the document will be transformed automatically. Open the booklist.xml document provided in the CD and see how the document is displayed. Remember, you need Internet Explorer with a version greater than or equal to 6 to do this because this is the first version of IE that is fully compliant with the standard. This mechanism is useful if you do not want to burden your back-end server with transformation overhead. The JSP can simply return an XML document with the appropriate style sheet linked, and the browser can take care of the transformation.

XML documents can also be transformed on the server side. To do this, you can use JAXP. We've already learned how to parse an XML document using JAXP. In this section, we look at how to transform using JAXP. The javax.xml.transform package provides all the requisite classes for transforming your XML document.

To use JAXP, you should parse the XML document first by using either SAX, DOM, or stream. The parser output should then used by the transformer to perform the transformation. First, you use the javax.xml.transform.TransformerFactory class to create a new transformer instance. That class provides an instance of the transformer object, which then performs the transformation.


TransformerFactory tf =
  TransformerFactory.newInstance();
// Create the transformer for the given stylesheet
Transformer transformer =
  tf.newTransformer(new StreamSource(stylesheet));

Finally, you transform the XML document by providing the source and destination streams into the transform method of the transformer. This method uses the already-set style sheet to perform the transformation and writes the output into the destination stream. Remember that the destination stream could very well be the output buffer of a servlet or JSP, thus sending the transformed output back to the client.


// Transform the XML document giving the source and
// destination streams
transformer.transform(new StreamSource(source),
  new StreamResult(System.out));

Execute the com.wlsunleashed.xml.transform.XMLTransform class (packaged on the CD) and pass to it an XML document and its corresponding XSL style sheet.

    [ Team LiB ] Previous Section Next Section