Extensible Stylesheet Language Transformation

The development of CSS leads to the main thrust of this chapter, XSLT, a technology that can perform tighter manipulation of XML than CSS. XSLT is an outgrowth of the XSL developed in 1999 by a W3C committee. The original draft of an XSL specification sought to define how XML documents should be formatted and transformed. This was a large task and the specification attacked each of the requirements separately. In this section we will go over the development of the XSL and XSLT specifications, benefits of their use, and some examples.

XSL Formatting Objects

To accomplish XSL formatting, the XSL specification introduced a new type of XML document that consisted of Formatting Objects. Formatting Objects represent how an XML document should be formatted without regard to typesetting specifics (for example, Microsoft Rich Text Format (RTF), Adobe PDF, and other print languages).

Like CSS and HTML together, an XSL Formatting Object tree describes blocks of content, which might contain text, tables, graphics, and other visual elements. This document provides generic information concerning layout, font characteristics, and color. In order for a Formatting Object document to be converted into a format readable by, say, Microsoft Word, typesetting converters must be used. Typesetting converters take a Formatting Object stream and build documents suitable for a particular renderer, in this case the RTF. In this way Web applications can create a single Formatting Object document that can be converted into many different forms of output. The equations here show how it's done.

Figure 5-6 shows how Formatting Objects and converters can be used to transform a single source XML document into many different types of output.

Figure 5-6 XSL styling with Formatting Objects

This technology is still young, and few Formatting Object converters exist. One exception is the Apache Group's FOP project, which converts Formatting Object documents into Adobe PDF (). However, preview releases of XSL formatters that can display XSL-FO natively on the Web are available. Information on the Antenna House XSL Formatter is available at and the X-Smiles browser is described at .

XSL Transformations

The second part of the XSL specification governing XML transformations matured more quickly than its formatting counterpart. The XSLT language owes much of its rapid adoption to Microsoft, which incorporated an early draft of the specification into its widely distributed Internet Explorer 5.0 browser. Although criticized at the time for releasing software based on an unfinished specification, by placing this powerful technology into the hands of eager programmers, Microsoft exposed a large portion of the developer community to the possibilities XML transformations allow.

Partly because of this sudden, wide distribution of XSL transformation technology, the W3C broke out the transformation component of the XSLT specification and created a new XSLT proposal. Although XSL still contains Formatting Object support, it defers to the XSLT specification for rules governing transformations.

Formally speaking, an XSLT is an XML document that describes how to convert one XML document into another. This sounds trivial, but the applications of the idea are far-reaching. Let's return to the navigation tree we looked at earlier. We can construct an XSLT that converts the XML document into an HTML document for viewing in a Web browser, a Formatting Object XML document for display or conversion into a Word table of contents, or into another XML document all together.

When to Use XSLT

We've seen that CSS can be a useful tool for styling XML when the source document closely resembles the desired display format. Unfortunately, this is rarely the case. The metamorphosis an XML document must often undergo, from generic markup to browser-specific HTML, is significant. Branches must be conditionally selected while HTML elements and script must be inserted into the result document. Sometimes HTML is not even the desired output. This is often the case in e-commerce applications where XML documents must be massaged so they can be exchanged cleanly. In such instances XSLT is the transformation tool of choice.

XSLT Browser Compliance

The downside to XSLT is that it is relatively new and few browsers support it at the time of this writing, which is not surprising when one realizes that not all browsers support CSS 1. So far, Microsoft has pursued the technology aggressively. Version 2.0 of the MSXML parser, which shipped with Internet Explorer 5.0, offered an introduction to XSLT, but was not ready for commercial use. Version 3.0 of the parser offers full support of the XSLT 1.0 specification and includes a multithreaded parser, making it a more viable commercial product. Netscape Navigator does not support XSLT in its version 6.0. Fortunately, developers need not wait for browser support before beginning XML projects. Server-side XSLT transformations provide all of the benefit of XSL processing and none of the worry associated with supporting specific Web browsers.

Using XSL Transformations

Programmers use XSLT to alter XML documents by associating transformation rules with particular nodes in the source tree. Basically, the programmer tells the XSLT engine, "Whenever you encounter this type of node or element, update the result document with this information."

In this example we will create a transformation that converts the tree into an HTML document suitable for rendering on Internet Explorer 5.0 or later. Nodes are selected by using the XPath expression language. XPath is a rich query language designed specifically for filtering the content in an XML document. XPath expressions are always based on a context node and are somewhat similar to directory commands often used in MS-DOS and UNIX shells. Here are some brief examples using the root node of Listing 5-1 as the context node:

  1. navitem Selects all of the navitem nodes directly beneath the root node.
  2. navitem[children] Selects all the navitem nodes that have children elements directly beneath the root node.
  3. //navitem[children] Selects all of the navitem nodes with children elements as children.
  4. navitem[1] Selects the first navitem node.
  5. navitem[last( )] Selects the last navitem node which is a child of the context node.
  6. navitem[last( )]/description Selects the <description> element children of the last navitem node.

Once we've identified the nodes we want to transform, we need to specify what should be done to them. This task is accomplished by using the XSLT <template> element. For example, the following code will replace all occurrences of the <navitem> element with the content of the <description> element before applying further template rules to its children.

<xsl:template match="navitem">
  <xsl:value-of select="description">
  <xsl:apply-templates/>
</xsl:template>

XSL Transformation Examples

Now we will go over an example of an XSLT and embellish that example by adding interactivity. The first line of our example is the style sheet declaration that must appear at the beginning of all XSLT documents. Notice that the XSLT namespace points to . This address is different from the Internet Explorer 5.0 release of the parser (based on the working draft of the XSL specification) that looked to . The version attribute is mandatory. This line looks like

<xsl:stylesheet xmlns:xsl=/1999/XSL/Transform" version="1.0">

The next line contains the <xsl:output> element that tells the XSLT processor to produce a particular type of document. Possible values for the method attribute are "text", "html", and "xml".

<xsl:output method="html"/>

Now we're in the meat of the transformation. Let's start by selecting the root navigation node of the document and initializing the resulting HTML document.

   <xsl:template match="navigation[@type='tree']">
    <html>
      <head>
        <style>
          a:visited {background-color:white; color:black; 
text-decoration: underline}           a:link {background-color:white; color:black; text-decoration: underline}           a:active {background-color:activecaption; color:captiontext; text-decoration: underline}           a:hover {background-color:white; color:black; text-decoration: underline}           .clsHeading {font-family: verdana; color: black; font-size: 11; font-weight: 800;}           .clsEntryText {padding-top: 2px; padding-left: 20px;
font-family: verdana; color: black; font-size: 11; font-weight: 400; background-color:#FFFFFF;}           .clsWarningText {font-family: verdana; color: #B80A2D; font-size: 11; font-weight: 600; width:550;   background-color:#EFE7EA;}           .clsCopy {font-family: verdana; color: black; font-size: 11; font-weight: 400;  background-color:#FFFFFF;}         </style>       </head>

Now we will create a division to hold the contents of the root element in the tree. The division will be useful later when we extend the transformation to support the dynamic expanding and collapsing of tree branches. Also note the {generate-id( )} directive to the XSLT processor. Similar to "<% ... %>" in ASP, braces ("{.}") found within attributes prompt the XSLT processor to evaluate the expression and include the result in the result document. In this case the generate-id( ) function will return a unique alphanumeric id for the current element in the XML source document.

<body>
  <div class="clsHeading" id="open_{generate-id()}">

Next we need to test for the existence of openImage and href elements and display them if they are present.

<xsl:if test="openImage">
  <img src="{openImage}"/>&#160;
</xsl:if>
<xsl:if test="href">
  <a href="{href}">
<xsl:if test="href/@target">
  <xsl:attribute name="target">
    <xsl:value-of select="href/@target"/>
  </xsl:attribute>
</xsl:if>
  <xsl:value-of select="description"/>
  </a>
</xsl:if>
<xsl:if test="not(href)"><xsl:value-of select="description"/></xsl:if>

Finally we need to select all the child <navitem> elements, apply transformation rules to them, and close out the navigation template. The <xsl:apply-templates> element will search for <navitem> elements below the root node, process them, and insert their transformed values into this location in the result tree.

          <xsl:apply-templates select="navitem"/>
        </div>
      </body>
    </html>
  </xsl:template>

That takes care of the root navigation node. Now we need to provide a rule for transforming a generic <navitem> element.

  <xsl:template match="navitem">

The icon we use to display a <navitem> element will depend on whether or not the <navitem> has any children. If the element has children we will display a folder icon.

<xsl:choose>
  <xsl:when test="children">
  <div class="clsEntryText" id="open_{generate-id()}">
      <xsl:if test="openImage">
        <img src="{openImage}"/>&#160;
      </xsl:if>
      <xsl:if test="href">
        <a href="{href}">
      <xsl:if test="href/@target">
        <xsl:attribute name="target"><xsl:value-of select=
"href/@target"/></xsl:attribute>       </xsl:if>         <xsl:value-of select="description"/></a>       </xsl:if>       <xsl:if test="not(href)">         <xsl:value-of select="description"/>       </xsl:if>

Next we need to select all the children <navitem> elements and apply their templates recursively.

<xsl:apply-templates select="children"/>

Finally we handle the case where a <navitem> element has no children.

        </div>
      </xsl:when>
      <xsl:otherwise>
        <div class="clsEntryText">
          <xsl:if test="image"><img src="{image}"/>&#160;</xsl:if>
          <xsl:if test="href"><a href="{href}">
            <xsl:if test="href/@target"><xsl:attribute name=
"target"><xsl:value-of select="href/@target"/></xsl:attribute></xsl:if>
            <xsl:value-of select="description"/></a>
          </xsl:if>
          <xsl:if test="not(href)">
<xsl:value-of select="description"/></xsl:if>         </div>       </xsl:otherwise>     </xsl:choose>   </xsl:template> </xsl:stylesheet>

Listing 5-3 shows the complete code listing for this transformation.

Listing 5-3 Navigation.xsl: When applied to Navigation.xml, produces a graphical display of the tree.

<xsl:stylesheet xmlns:xsl=/1999/XSL/Transform" version="1.0">
  <xsl:output method="html"/>
  <xsl:template match="navigation[@type='tree']">
    <html>
      <head>
        <style>
          a:visited {background-color:white; color:black; 
text-decoration: underline}           a:link {background-color:white; color:black;
text-decoration: underline}           a:active {background-color:activecaption; color:
captiontext; text-decoration: underline}           a:hover {background-color:white; color:black;
text-decoration: underline}           .clsHeading {font-family: verdana; color: black;
font-size: 11; font-weight: 800;}           .clsEntryText {padding-top: 2px; padding-left: 20px;
font-family: verdana; color: black; font-size: 11; font-weight: 400; background-color:#FFFFFF;}           .clsWarningText {font-family: verdana; color: #B80A2D; font-size: 11; font-weight: 600; width:550;  background-color:#EFE7EA;}           .clsCopy {font-family: verdana; color: black; font-size: 11; font-weight: 400;  background-color:#FFFFFF;}         </style>       </head>       <body>         <div class="clsHeading" id="open_{generate-id()}">           <xsl:if test="openImage"><img src="{openImage}"/>&#160; </xsl:if>           <xsl:if test="href"><a href="{href}">             <xsl:if test="href/@target"><xsl:attribute name= "target"><xsl:value-of select="href/@target"/></xsl:attribute> </xsl:if>             <xsl:value-of select="description"/></a>           </xsl:if>           <xsl:if test="not(href)">
<xsl:value-of select="description"/></xsl:if>           <xsl:apply-templates select="navitem"/>         </div>       </body>     </html>   </xsl:template>   <xsl:template match="navitem">     <xsl:choose>       <xsl:when test="children">         <div class="clsEntryText" id="open_{generate-id()}">           <xsl:if test="openImage"><img src="{openImage}"/>&#160;
</xsl:if>           <xsl:if test="href"><a href="{href}">             <xsl:if test="href/@target">
<xsl:attribute name="target"><xsl:value-of select="href/@target"/>
</xsl:attribute></xsl:if>             <xsl:value-of select="description"/></a>           </xsl:if>           <xsl:if test="not(href)">
<xsl:value-of select="description"/></xsl:if>           <xsl:apply-templates select="children"/>         </div>       </xsl:when>       <xsl:otherwise>         <div class="clsEntryText">           <xsl:if test="image"><img src="{image}"/>&#160;</xsl:if>           <xsl:if test="href"><a href="{href}">             <xsl:if test="href/@target">
<xsl:attribute name="target"><xsl:value-of select="href/@target"/>
</xsl:attribute></xsl:if>             <xsl:value-of select="description"/></a>           </xsl:if>           <xsl:if test="not(href)"><xsl:value-of select= "description"/></xsl:if>         </div>       </xsl:otherwise>     </xsl:choose>   </xsl:template> </xsl:stylesheet>

The transformation mentioned in Listing 5-3 will produce a Microsoft Windows Explorer-like tree with all of its branches expanded, which can be seen in Figure 5-7.

Figure 5-7 The navigation tree transformed into a static HTML document by using XSLT.

This example looks much better than the CSS version. We still might want to add a few more features to the transformation, so it might be useful to allow users to expand and collapse branches of the tree dynamically. To do this requires little extra work on the part of the XSLT and makes the tree navigation tool more interactive for the user.

An expandable and collapsible tree can be implemented by creating two divisions for each parent node in the tree. One division displays the node collapsed and uses an icon to represent the closed state—a closed book, perhaps. The other division displays the node expanded. Only the expanded division will contain divisions for its children (each of which will also have a collapsed and expanded division).

This transformation will be nearly identical to the static version. The first difference will be to write the JavaScript expand( ) and collapse( ) functions. The script itself is simple. Both functions will expect a division ID to uniquely identify a specific division in the page. We will use the XSL function generate-id( ) to create ID values. The expand( ) and collapse( ) functions will simply toggle the display properties of the open and closed versions of that division.

  <script language="JavaScript">
    function expand(divid)
    {
       eval("closed_"+divid).style.display = 'none';
       eval("open_"+divid).style.display = '';
    }
    function collapse(divid)
    {
       eval("open_"+divid).style.display = 'none';
       eval("closed_"+divid).style.display = '';
    }
  </script>

Next we will create two divisions for each parent node, divid_closed and divid_open. Only one of these divisions will ever be visible at a given time. The first will be the closed division. Notice the onClick( ) event handler attached to the "plus" image. The event handler calls the expand( ) function to toggle the display properties of the open and closed divisions.

<xsl:when test="children">
  <div class="clsEntryText" id="closed_{generate-id()}">
    <xsl:for-each select="ancestor::navitem">
      <xsl:choose>
      <xsl:when test="count(following-sibling::navitem) > 0">
          <img src="/xmlbook/images/line.gif"/>
        </xsl:when>
        <xsl:otherwise>
          <img src="/xmlbook/images/nothing.gif"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
    <img src="/xmlbook/images/folder_plus.gif" 
onClick="expand('{generate-id()}')"/>     <xsl:if test="closedImage"> <img src="{closedImage}"/>&#160; </xsl:if>     <xsl:if test="href"> <a href="{href}">         <xsl:if test="href/@target"> <xsl:attribute name="target"><xsl:value-of select="href/@target"/>
</xsl:attribute> </xsl:if>         <xsl:value-of select="description"/> </a>     </xsl:if>     <xsl:if test="not(href)"> <xsl:value-of select="description"/> </xsl:if>   </div>

The code for the open division is similar except that its display style property is originally set to "none" and it calls the <xsl:apply-templates> XSLT element on its children.

<div class="clsEntryText" id="open_{generate-id()}" style="display:none">
  <xsl:for-each select="ancestor::navitem">
    <xsl:choose>
      <xsl:when test="count(following-sibling::navitem) > 0">
        <img src="/xmlbook/images/line.gif"/>
      </xsl:when>
      <xsl:otherwise>
        <img src="/xmlbook/images/nothing.gif"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:for-each>
  <img  src="/xmlbook/images/folder_minus.gif" onClick=
"collapse('{generate-id()}')"/>
  <xsl:if test="openImage"><img src="{openImage}"/>&#160;</xsl:if>
  <xsl:if test="href"><a href="{href}">
    <xsl:if test="href/@target">
<xsl:attribute name="target"><xsl:value-of select="href/@target"/>
</xsl:attribute> </xsl:if>     <xsl:value-of select="description"/></a>   </xsl:if>   <xsl:if test="not(href)"> <xsl:value-of select="description"/> </xsl:if>   <xsl:apply-templates select="children"/> </div>

Listing 5-4 is a complete listing for the dynamic tree transformation. This transformation will produce an interactive HTML representation of the navigation tree with expandable and collapsible branches.

Figure 5-8 shows the results of this transformation on a navigation tree.

Listing 5-4 navigation_dynamic.xsl

<xsl:stylesheet xmlns:xsl=/1999/XSL/Transform" version="1.0"
<xsl:output method="html"/>
  <xsl:template match="navigation[@type='tree']">
    <html>
      <head>
        <script language="JavaScript">
          function expand(divid)
          {
             eval("closed_"+divid).style.display = 'none';
             eval("open_"+divid).style.display = '';
          }
          function collapse(divid)
          {
             eval("open_"+divid).style.display = 'none';
             eval("closed_"+divid).style.display = '';
          }
        </script>
        <style>
          a:visited {background-color:white; color:black;
text-decoration: underline}           a:link {background-color:white; color:black;
text-decoration: underline}           a:active {background-color:activecaption;
color:captiontext; text-decoration: underline}           a:hover {background-color:white; color:black;
text-decoration: underline}           .clsHeading {font-family: verdana; color: black; font-size: 11; font-weight: 800;}           .clsEntryText {padding-top: 2px; font-family: verdana; color: black; font-size: 11; font-weight: 400; background-color:#FFFFFF;}           .clsWarningText {font-family: verdana; color: #B80A2D; font-size: 11; font-weight: 600; width:550;  background-color:#EFE7EA;}           .clsCopy {font-family: verdana; color: black; font-size: 11; font-weight: 400;  background-color:#FFFFFF;}         </style>       </head>       <body>         <div class="clsEntryText" id="open_{generate-id()}">           <xsl:if test="openImage"><img src="{openImage}"/>&#160;
</xsl:if>           <xsl:if test="href"><a href="{href}">             <xsl:if test="href/@target">
<xsl:attribute name="target"><xsl:value-of select="href/@target"/>
</xsl:attribute></xsl:if>             <xsl:value-of select="description"/></a>           </xsl:if>           <xsl:if test="not(href)">
<xsl:value-of select="description"/></xsl:if>           <xsl:apply-templates select="navitem"/>         </div>       </body>     </html>   </xsl:template> <xsl:template match="navitem">     <xsl:choose>       <xsl:when test="children">         <div class="clsEntryText" id="closed_{generate-id()}">           <xsl:for-each select="ancestor::navitem">             <xsl:choose>               <xsl:when test="count(following-sibling::navitem) > 0">                 <img src="/xmlbook/images/line.gif"/>               </xsl:when>               <xsl:otherwise>                 <img src="/xmlbook/images/nothing.gif"/>               </xsl:otherwise>             </xsl:choose>           </xsl:for-each>           <img src="/xmlbook/images/folder_plus.gif" onClick="expand('{generate-id()}')"/>           <xsl:if test="closedImage">
<img src="{closedImage}"/>&#160;</xsl:if>           <xsl:if test="href"><a href="{href}">             <xsl:if test="href/@target">
<xsl:attribute name="target"><xsl:value-of select="href/@target"/>
</xsl:attribute></xsl:if>             <xsl:value-of select="description"/></a>           </xsl:if>           <xsl:if test="not(href)">
<xsl:value-of select="description"/></xsl:if>         </div>         <div class="clsEntryText" id="open_{generate-id()}" style="display:none">           <xsl:for-each select="ancestor::navitem">             <xsl:choose>               <xsl:when test="count(following-sibling::navitem) > 0">                 <img src="/xmlbook/images/line.gif"/>               </xsl:when>               <xsl:otherwise>                 <img src="/xmlbook/images/nothing.gif"/>               </xsl:otherwise>             </xsl:choose>           </xsl:for-each>           <img  src="/xmlbook/images/folder_minus.gif"
onClick="collapse('{generate-id()}')"/>           <xsl:if test="openImage"><img src="{openImage}"/>&#160; </xsl:if>           <xsl:if test="href"><a href="{href}">             <xsl:if test="href/@target">
<xsl:attribute name="target">
<xsl:value-of select="href/@target"/>
</xsl:attribute>
</xsl:if>             <xsl:value-of select="description"/></a>           </xsl:if>           <xsl:if test="not(href)"><xsl:value-of select= "description"/></xsl:if>           <xsl:apply-templates select="children"/>         </div>       </xsl:when>       <xsl:otherwise>         <div class="clsEntryText">           <xsl:for-each select="ancestor::navitem">             <xsl:choose>               <xsl:when test="count(following-sibling::navitem) > 0">                 <img src="/xmlbook/images/line.gif"/>               </xsl:when>               <xsl:otherwise>                 <img src="/xmlbook/images/nothing.gif"/>               </xsl:otherwise>             </xsl:choose>           </xsl:for-each>           <img  src="/xmlbook/images/divider.gif" onClick= "collapse('{generate-id()}')"/>           <xsl:if test="image"><img src="{image}"/>&#160;</xsl:if>           <xsl:if test="href"><a href="{href}">             <xsl:if test="href/@target"><xsl:attribute name= "target"><xsl:value-of select="href/@target"/></xsl:attribute> </xsl:if>             <xsl:value-of select="description"/></a>           </xsl:if>           <xsl:if test="not(href)"><xsl:value-of select= "description"/></xsl:if>         </div>       </xsl:otherwise>     </xsl:choose>   </xsl:template> </xsl:stylesheet>