Using the Xalan XSLT engine within a java servlet

Introduction

Separation of style from content allows for the same data to be presented in different ways and is the clear answer to the multiplication of connected devices (Palm, Pocket PC, Interactive TV) that can access networked ressources using their own language (WML, WebClipping, HTML, XHTML, cHTML, etc..).
In this article, we show how to produce HTML and WML content from an XML data source, and the appropriate XSLT stylesheets. The XSLT transformation is done using the Xalan transformation engine, an open-source java project hosted by the Apache foundation. The Xalan engine is called from a java servlet, running in the Tomcat servlet engine.
 

Requirements

In this article, I will assume that you have installed the Tomcat servlet engine, and the Xalan XSLT engine. Both are available from java.apache.org and xml.apache.org. I also assume that xalan.jar, xerces.jar (the XML parser bundled with Xalan) and the servlet classes are present in your classpath. If not, try something like:

XALAN_HOME=/path/to/xalan.jar
XERCES_HOME=/path/to/xerces.jar
export CLASSPATH=$CLASSPATH:$XALAN_HOME:$XERCES_HOME
 
 

Creating a new web application within Tomcat

Although you could install your java classes in an existing web application (the one called "examples", for instance), it is probably better to create a new web application.  This is nicely explained in "Deploying web applications to Tomcat" by James Goodwill.
Assuming $TOMCAT_HOME is the path to the Tomcat directory, first change directory to webapps (the root of all web applications) and create the following directories:

/xslt
/xslt/WEB-INF
/xslt/WEB-INF/classes

To do so, type the following commands:
> cd $TOMCAT_HOME/webapps
> mkdir xslt
> cd xslt
> mkdir WEB-INF
> cd WEB-INF
> mkdir classes
> cd classes

The /xslt/WEB-INF/classes directory is the place where you will store all the servlets classes described below.

Once the directories are created, you should install a servlet context, by editing the file $TOMCAT_HOME/conf/server.xml, and adding the following lines:

<Context path="/xslt" docBase="webapps/xslt" debug="0" reloadable="true" >
</Context>

path="/xslt" tells Tomcat that all requests starting with /onjava belong to the onjava web application.
docBase="webapps/xslt" tells the servlet container that the web application is located on webapps/xslt"
 

Once you have done these operations, you need to restart Tomcat.
 

Xalan-J : a Java XSLT engine

We use Xalan as our XSLT engine. While we embed Xalan within our servlet, Xalan-J can also be used in a command line way to perform XSLT transformation and produce static output files. For example, imagine you want to produce a static HTML file called slashdot.html from a XML file called slashdot.xml.
You just need to type the following line:
> java org.apache.xalan.xslt.Process -in slashdot.xml -xsl slashdot.xsl -out slashdot.html

If you want the output to be displayed on the screen, simply omit the -out flag and argument.

The XSLT processor class

Xalan-J implements the TrAX (Transformation API for XML) interface. From the Xalan documentation: "A TRaX TransformerFactory is an object that processes transformation instructions, and produces Templates (in the technical terminology). A Templates object provides a Transformer, which transforms one or more Sources into one or more Results. To use the TRaX interface, you create a TransformerFactory, which may directly provide a Transformers, or which can provide Templates from a variety of Sources. The Templates object is a processed or compiled representation of the transformation instructions, and provides a Transformer. The Transformer processes a Source according to the instructions found in the Templates, and produces a Result".
In the code below, the XSLT processor class contains a method that takes as input a XML source, a XSL source, a servlet request (it is not used here) and a servlet response, and it returns the output of the transformation to the servlet output stream.


import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

import org.xml.sax.SAXException;

import javax.xml.transform.*;
import javax.xml.transform.stream.*;

public class XalanXslProcessorBean {

    TransformerFactory tFactory;

    //the constructor simply gets a new TransformerFactory instance
    public XalanXslProcessorBean() {
        tFactory = TransformerFactory.newInstance();
    }

    //this method takes as input a XML source, a XSL source, and returns the output of the transformation to the servlet output stream
    public void process(StreamSource xmlSource,
                                  StreamSource xslSource,
                                  HttpServletRequest request,
                                  HttpServletResponse response)
             throws ServletException, IOException, SAXException {
        try {
            Templates templates = tFactory.newTemplates(xslSource);
            Transformer transformer = templates.newTransformer();
             transformer.transform(xmlSource, new StreamResult(response.getOutputStream()));
        }
        catch (Exception e) {
           //should log some message here
        }
    }
}



 
 

The XML data

The XML file we will use was grabbed from the slashdot.org site, and contains the slashdot news in XML form. It needs to be saved to your disk (and called slashdot.xml for example).


<?xml version="1.0"?><backslash xmlns:backslash="http://slashdot.org/backslash.dtd">

 <story>
  <title>NASA Proposes Launch Solar Sail Vehicle For 2010</title>
  <url>http://slashdot.org/article.pl?sid=00/05/15/058238</url>
  <time>2000-05-15 07:54:15</time>
  <author>timothy</author>
  <department>ralph-nader-will-have-to-hire-a-chase-car</department>
  <topic>space</topic>
  <comments>99</comments>
  <section>articles</section>
  <image>topicspace.gif</image>
 </story>

 <story>
  <title>Linuxcare Responds To Tim O'Reilly's Article</title>
  <url>http://slashdot.org/article.pl?sid=00/05/15/0254252</url>
  <time>2000-05-15 02:57:07</time>
  <author>timothy</author>
  <department>consider-source-horses-mouth-grain-of-salt</department>
  <topic>linuxbiz</topic>
  <comments>142</comments>
  <section>articles</section>
  <image>topiclinuxbiz.gif</image>
 </story>

 <story>
  <title>New Internet VCR Service</title>
  <url>http://slashdot.org/article.pl?sid=00/05/14/2048217</url>
  <time>2000-05-14 20:51:57</time>
  <author>timothy</author>
  <department>this-is-cool-but-can-they-do-that?</department>
  <topic>news</topic>
  <comments>189</comments>
  <section>articles</section>
  <image>topicnews.gif</image>
 </story>

 <story>
  <title>Google Releases WAP Search Tool</title>
  <url>http://slashdot.org/article.pl?sid=00/05/14/1240252</url>
  <time>2000-05-14 17:49:11</time>
  <author>emmett</author>
  <department>wireless</department>
  <topic>internet</topic>
  <comments>141</comments>
  <section>articles</section>
  <image>topicinternet.jpg</image>
 </story>

 <story>
  <title>No More Unreal Ports For Linux?</title>
  <url>http://slashdot.org/article.pl?sid=00/05/14/1439224</url>
  <time>2000-05-14 16:32:11</time>
  <author>timothy</author>
  <department>one-web-one-program-happy-mothers-day</department>
  <topic>games</topic>
  <comments>250</comments>
  <section>articles</section>
  <image>topicgames.jpg</image>
 </story>

 <story>
  <title>Pioneer Introduces 1st DVD Recorder (In Japan)</title>
  <url>http://slashdot.org/article.pl?sid=00/05/14/152210</url>
  <time>2000-05-14 15:50:48</time>
  <author>CmdrTaco</author>
  <department>steam-rising-from-the-riaas-forehead</department>
  <topic>tv</topic>
  <comments>98</comments>
  <section>articles</section>
  <image>topictv.jpg</image>
 </story>

 <story>
  <title>QuakeForge And QuakeWorld Forever Merge</title>
  <url>http://slashdot.org/article.pl?sid=00/05/14/1447248</url>
  <time>2000-05-14 15:07:27</time>
  <author>CmdrTaco</author>
  <department>and-then-there-was-one</department>
  <topic>quake</topic>
  <comments>57</comments>
  <section>articles</section>
  <image>topicquake.gif</image>
 </story>

 <story>
  <title>What Happens When Open Source And Work Collide?</title>
  <url>http://slashdot.org/article.pl?sid=00/05/09/016208</url>
  <time>2000-05-14 14:04:07</time>
  <author>Cliff</author>
  <department>sticky-situations</department>
  <topic>programming</topic>
  <comments>170</comments>
  <section>askslashdot</section>
  <image>topicprogramming.gif</image>
 </story>

 <story>
  <title>Black Holes Don't Exist???</title>
  <url>http://slashdot.org/article.pl?sid=00/05/14/1339252</url>
  <time>2000-05-14 13:39:24</time>
  <author>Roblimo</author>
  <department>pop-science-can-be-fun</department>
  <topic>science</topic>
  <comments>162</comments>
  <section>articles</section>
  <image>topicscience.gif</image>
 </story>

 <story>
  <title>Los Alamos Lab: We're OK, You're OK</title>
  <url>http://slashdot.org/article.pl?sid=00/05/14/0143228</url>
  <time>2000-05-14 04:44:44</time>
  <author>timothy</author>
  <department>sir-please-step-*away*-from-the-plutonium-bin</department>
  <topic>news</topic>
  <comments>278</comments>
  <section>articles</section>
  <image>topicnews.gif</image>
 </story>

</backslash>



 

The XSLT stylesheet

An XSL stylesheet basically consists of a set of templates. Each template "matches" some set of elements in the original XML data and then describes the contribution that the matched element makes to the final output.
An XSLT template is defined by a xsl:template tag, whose "match" parameter determines where this template applies. For example <xsl:template match="/"> ... </xsl:template> applies to the root element of the XML document, while <xsl:template match="backslash/story"> matches every story element that has backslash as father. Templates are generally applied recursively, i.e. a template calls another templates using the xsl:apply-templates tag.
<xsl:value-of> inserts the value of an expression to the final output. Note that {element} can also be used to insert the value of element.

Here is the HTML stylesheet (named slashdot.xsl) we will use:



<?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 select="backslash/story"/>
</body>
</html>
</xsl:template>

<xsl:template match="backslash/story">
  <li><a href="{url}"><xsl:value-of select="title"/></a></li>
</xsl:template>

</xsl:stylesheet>


Outputting HTML with a servlet

This java code implements a basic servlet, which uses the XalanXslProcessorBean class defined above. It sets the Content-Type portion of the HTTP header to text/html, creates two StreamSources objects from both the xml and the xsl files, and perform the transformation


import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

import javax.xml.transform.stream.*;

public class XslProcessorServlet extends HttpServlet {

    XalanXslProcessorBean processor;

    public void init(ServletConfig config) {
        processor = new XalanXslProcessorBean();
    }

    public void doGet (HttpServletRequest request,
                                HttpServletResponse response)
             throws ServletException, IOException {

        //sets the Content-Type portion of the HTTP header to text/html
        response.setContentType("text/html");
        try {
            processor.process(new StreamSource("slashdot.xml"), new StreamSource("slashdot.xsl"), request, response);
        }
        catch (Exception e) {

        }
    }
}


Since slashdot.xml is often updated,  you may prefer to fetch it directly from the slashdot.org site:  you then need to change the processor.process(...) line to: processor.process(new StreamSource(new InputStreamReader((new URL("http://slashdot.org/slashdot.xml")).openStream())), new StreamSource("slashdot.xsl"), request, response);
Then restart your servlet container if needed, and reload the page.
 

Outputting HTML and WML with a servlet


Suppose that you want to make your content available to both HTML and WML navigators. Basically, you just need a XSLT stylesheet that can transform XML to HTML, and another one that can transform XML to WML (Wireless Meta Language). You then need to implement a mechanism that can use the appropriate stylesheet, depending on the navigator information contained in the HTTP request header.

Here is the WML stylesheet. Note the xsl:output tag, which is the only way to produce the <!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml"> string in the WML output.



<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml"
                  doctype-public="-//WAPFORUM//DTD WML 1.1//EN"
                  media-type="text/vnd.wap.wml"
                  doctype-system="http://www.wapforum.org/DTD/wml_1.1.xml"
                  encoding="ISO-8859-1"/>

<xsl:template match="/">
<wml>

    <template>
        <do type="prev" name="Previous" label="Back">
            <prev/>
        </do>
    </template>

    <card id="card1" title="Slashdot news">
        <p>
            <xsl:apply-templates select="backslash/story"/>
        </p>
    </card>

</wml>
</xsl:template>
 

<xsl:template match="backslash/story">
  <a href="{url}"><xsl:value-of select="title"/></a><br/>
</xsl:template>

</xsl:stylesheet>


Note that the following servlet code now contains some code to fetch the user agent from the HTTP header, and uses the WML stylesheet when the user agent string contains the word (Nokia). Obviously, this only works with a Nokia phone or with some Nokia emulator.



import java.io.*;
import java.net.*;
import javax.servlet.*;
import javax.servlet.http.*;

import javax.xml.transform.stream.*;

public class XslProcessorServlet extends HttpServlet {

    XalanXslProcessorBean processor;

    public void init(ServletConfig config) {
        processor = new XalanXslProcessorBean();
    }

    public void doGet (HttpServletRequest request,
                                HttpServletResponse response)
        throws ServletException, IOException {

        //fetch the user agent part of the HTTP header
        String useragent = request.getHeader("user-agent");

        //if the user agent contains the string "Nokia", then use the WML stylesheet, otherwise use the HTML one
        StreamSource xslsource;
        if (useragent.indexOf("Nokia") >= 0) {
            //send the correct Content-Type
            response.setContentType("text/vnd.wap.wml");
            xslsource = new StreamSource("slashdot_wml.xsl");
        } else {
            response.setContentType("text/html");
            xslsource = new StreamSource("slashdot_html.xsl");
        }

        try {
            processor.process(new StreamSource("slashdot.xml"), xslsource, request, response);
        }
        catch (Exception e) {

        }
    }
}



 

Conclusion

In this article, I have presented a basic Java servlet designed to transform XML to HTML or WML, and to output the results to a web or WAP browser. To go further in this direction, you should perhaps have a look at the XSLT compiler from SUN, which is supposed to improve the speed of the XSL transformations. The XSLT compiler takes as input a XSL file and returns a Java class, called a translet, that can perform the transformations contained in the XSL file. Translets are therefore much faster than traditional transformation engines such as Xalan-J. The XSLT compiler is available for free at http://www.sun.com/xml/developers/xsltc/.
Also have a look at the Cocoon project, an advanced and ready for production publishing framework based on XML and XSL.

The author: Olivier Elemento is a consultant in fixed and mobile Internet technologies. It can be reached at elemento@club-internet.fr. You can also visist its web site at http://genomenews.free.fr/cv.html
 
 

Ressources

-  "Installing and configuring Tomcat" by James Goodwill, onjava.com
"Deploying web applications to Tomcat" by James Goodwill, onjava.com