Presentation Templating

XML and XSLT in Red Hat Web Application Framework

This document is under construction.

An Introduction to XSLT Programming

To experiment with XSLT, download the latest version of Xalan-J2 from the Apache XML project at http://xml.apache.org/xalan-j. This includes a command line interface for specifying an XML input file and an XSL input file, and writes transformation results to standard output. You may also need an XML parser; Xerces is also available free from the Apache XML project.

There are numerous existing XSLT tutorials and references on the Web. The W3C's page, http://www.w3.org/Style/XSL, has links to many of them.

The following set of examples will demonstrate how to transform an XML document into HTML using XSLT. In the first example, the following well-formed XML document representing phone book entries will be rendered into HTML:
<phone-book>
 <person name="Joe User">
  <phone type="office" number="202-554-5535"/>
  <phone type="pager" number="202-555-6734"/>
  <email address="joeuser@dot.com"/>
 </person>
 <person name="Sally Sysadmin">
  <phone type="office" number="202-554-5536"/>
  <phone type="pager" number="202-555-5555"/>
  <phone type="home" number="301-555-1234"/>
  <email address="sally@dot.com"/>
 </person>
</phone-book>

XSLT the easy way

There are many different ways to use XSLT to render this XML document into the same HTML output. This example will demonstrate the simplest method, which will be a pattern familiar to anyone who has used JSP and other templating systems. (This is essentially the style of XSLT advocated by the article "Rescuing XSLT from Niche Status".)

<?xml version="1.0"?>

<xsl:stylesheet 
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

 <xsl:output method="html"/>

 <xsl:template match="/">
  <html>
   <body>
    <xsl:apply-templates/>
   </body>
  </html>
 </xsl:template>

 <xsl:template match="phone-book">
  <ul>
   <xsl:for-each select="person">
    <li>
     Name: <xsl:value-of select="@name"/>
     <br />
     Phones:
     <ul>
      <xsl:for-each select="phone">
       <li><xsl:value-of select="@number"/> 
          (<xsl:value-of select="@type"/>)
      </xsl:for-each>
     </ul>
     <br />
     Email: <xsl:value-of select="email/@address"/>
    </li>
   </xsl:for-each>
  </ul>
 </xsl:template>

</xsl:stylesheet>

Feeding this stylesheet and the input document to an XSLT processor such as Xalan will generate the following output:

<html>
 <body>
  <ul>
   <li>
    Name: Joe User
    <br />
    Phones:
     <ul>
      <li>202-554-5535 (office)
      <li>202-555-6734 (pager)
     </ul>
    <br />
    Email: joeuser@dot.com
   </li>
   <li>
    Name: Sally Sysadmin
    <br />
    Phones:
     <ul>
      <li>202-554-5536 (office)
      <li>202-555-5555 (pager)
      <li>301-555-1234 (home)
     </ul>
    <br />
    Email: sally@dot.com
   </li>
  </ul>
 </body>
</html>

Note that the stylesheet is itself an XML document. All the tags that specify XSLT instructions are readily identifiable by their "xsl:" prefix. These tags exist in an XSL namespace whose definition is linked from the xsl:stylesheet tag's xmlns attribute.

The HTML output must written as legal XML, which poses some problems:

  • Tags in HTML do not have to be closed; they do in XML. This becomes a problem with singleton tags like "br" and "image". The empty "br" tag in the above example must be written written "br /" to make it legal XML. Many XSLT engines will be smart enough to turn this into regular HTML by stripping out the slash when output mode="html" is specified. Just in case, though, the extra space between the "br" and the "/" helps avoid confusing a web browser if the "<br />" goes to the browser literally.

  • HTML allows valueless attributes like in <input type="checkbox" checked> and <select multiple ...>. This isn't legal XML. You have to provide attribute values: <select multiple="multiple"> Again, many XSLT engines will be smart enough to strip values from certain attributes in HTML output mode.

  • The most common HTML entities like &amp;, &lt;, and &gt; are also XML entities and work in HTML output from an XSLT stylesheet. Unfortunately, &nbsp; doesn't work. To use &nbsp; in your HTML output from an XSLT stylesheet you need to define "nbsp" as an entity explicitly:
    <!DOCTYPE xsl:stylesheet [
        <!ENTITY nbsp " ">
    ]>

A few XSLT tags

The above stylesheet introduces the following XSLT tags:

  • xsl:stylesheet declares an XSLT stylesheet. The xmlns:xsl tells the XML parser that a new XML namespace is being created with the "xsl:" prefix, and that the DTD for all xsl: tags can be found at the specified URL.

  • xsl:template specifies a template rule that executes whenever the piece of the document specified in the match attribute is matched. The element/tag that is matched is specified with XPath. For simplicity's sake, in this example the "/" element is set to match the entire document, and the "phone-book" element specifically. (There is an elaborate algorithm for determining precedence when a given element matches more than one template rule, but it is not covered here.)

  • xsl:apply-templates applies all rules recursively to the portion matched by the prior xsl:template or xsl:for-each. In the above example, xsl:template match="/" matches the whole document, outputs the html/body boilerplate, and then xsl:apply-templates attempts to match any available rules to the already-matched document fragment. This in turn causes the xsl:template match="phone-book" rule to execute.

  • xsl:for-each loops over child elements of the currently active element specified in the last xsl:template or xsl:for-each. In this example, all "person" elements in the "phone-book" are looped over, and then all "phone" elements within a "person" are looped over.

  • xsl:value-of substitutes a value from the document, relative to the currently active element.

The select attribute to xsl:value-of and xsl:for-each (also an optional argument to xsl:apply-templates), as well as the match attribute to xsl:template, is specified with XPath. The named attributes and nodes are relative to whatever element was matched in a previous xsl:template or xsl:for-each rule, and attributes are differentiated from child elements with a "@" prefixed to their name (for example, "email/@address" gives the address attribute of the email element relative to the element last matched by an xsl:template or xsl:for-each).

XPath also allows specification of elements in an XML document by an absolute location, similar to naming files in a directory hierarchy. The example could have specified xsl:for-each match="/phone-book/person" and xsl:for-each match="/phone-book/person/phone", for instance, but this is unnecessary. XPath also includes a code API of functions that can be used within xsl:value-of and xsl:if expressions. These functions are too numerous to discuss in detail here.

Template rule composition and decomposition

In the following, more complex example, the goal is to build a portal-page document that contains a phone book, a set of press releases, etc. The following document contains a phone-book element:
 <portal>
  <phone-book>
   ...
  </phone-book>
  <news>
   <news-item>
    ...
   </news-item>
  </news>
 </portal>

This example uses the same template rule declared previously to style the phone-book element, whether it is the main element of the document or it is enclosed in another element:
<xsl:stylesheet 
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

 <xsl:template match="/">
  <html>
   <body>
    <xsl:apply-templates/>
   </body>
  </html>
 </xsl:template>

 <xsl:template match="phone-book">
  ... same phone-book rules go here ...
 </xsl:template>

 <xsl:template match="news">
  ... news template rules go here ...
 </xsl:template>

 ... more templating rules ...

</xsl:stylesheet>

Similarly, the original template rule for rendering a phone-book could have been written as several separate templating rules, using xsl:apply-templates to render portions of the document recursively. Note that although there is no explicit looping (xsl:for-each) in the example below, the loop is still effectively there:

<?xml version="1.0"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
   version="1.0">

 <xsl:template match="/">
  <html>
   <body>
    <xsl:apply-templates/>
   </body>
  </html>
 </xsl:template>

 <xsl:template match="phone-book">
  <ul>
   <xsl:apply-templates/> <!-- try to match person in another rule -->
  </ul>
 </xsl:template>

 <xsl:template match="person">
  <li>
   Name: <xsl:value-of select="@name"/>
   <br />
   Phones:
   <ul>
    <xsl:apply-templates select="phone"/>
   </ul>  <!--defer to other rules for matching ONLY phone -->
   Email:
    <xsl:apply-templates select="email"/>
     <!--defer to other rules for matching ONLY email -->
  </li>
 </xsl:template>

 <xsl:template match="phone">
  <li><xsl:value-of select="@number"/> (<xsl:value-of select="@type"/>)
 </xsl:template>

 <xsl:template match="email">
  Email: <xsl:value-of select="email/@address"/>
 </xsl:template>

</xsl:stylesheet>

This demonstrates the real power of XSLT. By using XSLT templates as indicated to render dynamic data from a web site into HTML, you can quickly change the look-and-feel of a particular datatype across the whole site.

Using XSLT with Red Hat Web Application Framework

This section of the tutorial will demonstrate how XSLT is integrated into Red Hat Web Application Framework/Java.

A note on infrastructure: Red Hat Web Application Framework depends on the Xalan-J XSLT engine from the Apache XML project. In turn, this engine depends on the Xerces XML parser, also from Apache. Although it is theoretically possible to use a different XML parser with Xalan, this process hasn't been thoroughly tested. It is also possible to use a different XSLT engine, such as Saxon. Red Hat Web Application Framework contains no explicit dependency on any vendor's XSLT engine.

Red Hat Web Application Framework integrates stylesheets in the following way:

  • It provides a default stylesheet as an integral part of each Red Hat Web Application Framework package, so that each package ships with some way to style the content it generates.

  • It allows the defaults for any package to be overridden when that package is invoked from a particular URL pattern. This allows for co-branding, etc.

  • It allows for "one-off" page scripts (for example, JSP/XSL pairs) which can import site-wide styling rules.

This flexibility comes from the PresentationManager interface, whose one method servePage obtains an Transformer for the current request, applies it to an input Document, and serves the transformed output to the response output stream. A Red Hat Web Application Framework application's dispatcher can use the provided BasePresentationManager or swap it out with its own to use a different algorithm for choosing a stylesheet for the current request.

XSLT integration into Red Hat Web Application Framework is accomplished by associating stylesheet documents with both Red Hat Web Application Framework packages and with site nodes. A default stylesheet is associated with each Red Hat Web Application Framework package, and the package defaults can be overridden within a particular URL prefix's scope. A "site node" represents a node in the webapp's URL tree; each site node, combined with its ancestors, generates a URL prefix. For the purposes of this discussion, "subsite" is defined to mean any site node that contains child site nodes (that is, a directory that contains subdirectories).

Also, because XSLT templates are also XML documents, they can be manipulated and composed dynamically. A PresentationManager could compute a new XSLT stylesheet on the fly, taking fragments from files on disk or in the database.

Calling XSLT from a Red Hat Web Application Framework application

The gateway to XSLT from Red Hat Web Application Framework is the PresentationManager interface, whose servePage method selects a stylesheet to use, applies the selected stylesheet to an XML document, and sends the output from the transformation to the client. The most commonly used implementation of PresentationManager is the provided BasePresentationManager, which searches for a stylesheet for a request by first looking for a stylesheet associated with the active site node, then the parents of the active site node, and finally the default stylesheet for the current package. It also takes the locale and output type for the request into account when selecting stylesheets.

Any selected stylesheet may use the xsl:import or xsl:include tags to import rules from another stylesheet. There are two common uses for this: a package's default stylesheet will import stylesheets from other packages that it depends on; and a customized stylesheet (for example, for a site node) can import another stylesheet and the rules defined in the customized stylesheet will override conflicting rules in the imported one.

Calling XSLT from Red Hat Web Application Framework applications is transparent if you use Bebop; you can just pass a Bebop Page object directly as an argument to BasePresentationManager.servePage(). That form of servePage will save the step of explicitly generating an XML document as output from the Page object:

Page p = ... Bebop page ...;
BasePresentationManager.getInstance().servePage(p, request,
response);

Any Red Hat Web Application Framework application code that wants to render an XML Document object into HTML output directly, without using Bebop, would call the servePage method of PresentationManager using approximately this pattern:

import com.arsdigita.templating.PresentationManager;
import com.arsdigita.xml.Document;

Document doc = ... generated document ...

// grab a presentation manager instance
PresentationManager pm = SomePMClass().getInstance();
pm.servePage(doc, request, response);