Book HomeJava and XSLTSearch this book

Chapter 8. Additional Techniques

Contents:

XSLT Page Layout Templates
Session Tracking Without Cookies
Identifying the Browser
Servlet Filters
XSLT as a Code Generator
Internationalization with XSLT

This chapter presents solutions to a few commonly encountered problems that were not covered in previous chapters, such as implementing session tracking without browser cookies, detecting the browser type, and using XSLT as a rudimentary code generator. None of these techniques are remarkably difficult to implement or use. However, they all build upon the technologies presented throughout this book and are important for programmers to understand. The chapter concludes with advice for internationalization using XSLT and Java.

8.1. XSLT Page Layout Templates

In many cases, dynamically generated, highly interactive web applications are overkill. A small company may need only a static web site that displays job openings, new product announcements, and other basic information. Corporate intranets present another common scenario. In a typical intranet, a large number of departments and individual project teams may be responsible for various web sites within the corporation. Many of these groups are composed of nonprogrammers who can create basic XHTML pages but are not technical enough to write XML, XSLT, and servlets. In either scenario, consistent look and feel are essential.

XSLT is very effective for defining consistent page layout. In the approach outlined here, web page authors create XHTML pages using whatever tools they are familiar with. These pages should not use frames or include navigation areas. As Figure 8-1 shows, an XSLT stylesheet is used to insert navigation areas on the top and left sides of input XHTML pages. This is why individual pages should not attempt to insert their own navigation areas.

Figure 8-1

Figure 8-1. XSLT template layout

Since the top navigation area is dynamic, page authors must also include a <meta> tag in every XHTML page that is published:

<meta name="navigationCategory" content="home"/>

This tag allows the top navigation area to visually highlight the category that the current page belongs to.[35] The XSLT stylesheet selects this tag and generates the appropriate XHTML for the navigation area. As shown in Figure 8-2, the sample stylesheet uses hyperlinks for each of the navigation categories. This same approach also works for fancy graphical navigation areas.

[35] You can extend this technique by adding a second <meta> tag for subcategories.

Since a single stylesheet controls page layout, changes to this stylesheet are visible across the entire web site. The code for the home page is listed in Example 8-1. The required elements are emphasized.

Example 8-1. home.xml

<?xml version="1.0" encoding="UTF-8"?>
<html>
  <head>
    <title>Home Page</title>
    <meta name="navigationCategory" content="home"/>
  </head>
  <body>
    <h1>Welcome to the Home Page!</h1>
    <div>
      This is a normal XHTML page that authors 
      create. The guidelines are as follows:
      <ul>
        <li>Each page must be valid XHTML</li>
        <li>Each page must have a meta tag that 
            indicates the navigation category.</li>
        <li>The templatePage.xslt stylesheet will add 
            the top and side navigation bars.</li>
      </ul>
      Pages are published to the WEB-INF/xml directory 
      of a web app. This forces clients to access pages
      through a Servlet, because the Servlet container
      prevents direct access to anything under WEB-INF.
    </div>
  </body>
</html> 
Figure 8-2

Figure 8-2. XHTML output with navigation areas

Since XSLT is used to insert the appropriate navigation areas, all pages must be well-formed XML. This is a good practice, and anyone who knows HTML should be able to make the transition to XHTML.[36] Programmers can provide scripts for page authors to run that validate the XML against one of the XHTML DTDs, reporting errors before pages are published to the web site.

[36] HTML TIDY is a free tool that converts HTML to XHTML. It is available at http://www.w3.org/People/Raggett/tidy.

NOTE: Strictly adhering to XHTML DTDs makes it much easier for programmers to write all sorts of programs that manage web site content because page content is consistently structured and can be easily parsed.

The XSLT stylesheet searches for the <meta> tag; therefore, <html>, <head>, and <meta> are required elements. If the <meta> tag is not found, the navigation category defaults to "unknown," and none of the navigation links are highlighted. Any content found inside of <head> and <body> is simply copied to the appropriate location within the result tree document. Example 8-2 lists the XSLT stylesheet that inserts the navigation areas.

Example 8-2. templatePage.xslt

<?xml version="1.0" encoding="UTF-8"?>
<!--
*********************************************************************
** A stylesheet used by every page on a web site. This stylesheet
** defines where the page header and navigation bar are placed.
******************************************************************-->
<xsl:stylesheet version="1.0" 
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <!--
  *******************************************************************
  ** The result tree is XHTML
  ****************************************************************-->
  <xsl:output method="xml"
     doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"
     doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
     encoding="UTF-8"/>

  <!--
  *******************************************************************
  ** The navigation category is determined by the <meta> tag in the 
  ** source XHTML document. The top navigation bar uses this variable.
  ****************************************************************-->
  <xsl:variable name="global.nav.category">
    <xsl:choose>
      <xsl:when test="/html/head/meta[@name='navigationCategory']">
        <xsl:value-of select="/html/head/meta
                [@name='navigationCategory']/@content"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:text>unknown</xsl:text>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:variable>
  
  <!--
  *******************************************************************
  ** This template produces the XHTML document.
  ****************************************************************-->
  <xsl:template match="/">
    <html xmlns="http://www.w3.org/1999/xhtml">
      <!-- copy the <head> from the source document -->
      <xsl:copy-of select="html/head"/>

      <body>
        <!-- this table defines the overall layout of the page -->
        <table width="100%" cellpadding="4" 
               cellspacing="0" border="0">
          <tr bgcolor="#f0f0f0">
            <td colspan="2">
              <xsl:call-template name="createTopNavbar"/>
            </td>
          </tr>
          <tr valign="top">
            <td bgcolor="#cccccc" width="150px">
               <xsl:call-template name="createLeftNavbar"/>
            </td>
            <td bgcolor="white">
              <!--
              *******************************************************
              ** Copy all contents of the <body> from the source
              ** XHTML document to the result tree XHTML document.
              ****************************************************-->
              <xsl:copy-of select="html/body/* | html/body/text( )"/>
            </td>
          </tr>
        </table>
      </body>
    </html>
  </xsl:template>

  <!--
  *******************************************************************
  ** This template produces the top navigation bar.
  ****************************************************************-->
  <xsl:template name="createTopNavbar">
    <xsl:call-template name="navButton">
      <xsl:with-param name="category" select="'home'"/>
      <xsl:with-param name="displayName" select="'Home'"/>
      <xsl:with-param name="url" select="'home.xml'"/>
    </xsl:call-template>
    |
    <xsl:call-template name="navButton">
      <xsl:with-param name="category" select="'company'"/>
      <xsl:with-param name="displayName" select="'Company'"/>
      <xsl:with-param name="url" select="'company.xml'"/>
    </xsl:call-template>
    |
    <xsl:call-template name="navButton">
      <xsl:with-param name="category" select="'products'"/>
      <xsl:with-param name="displayName" select="'Products'"/>
      <xsl:with-param name="url" select="'products.xml'"/>
    </xsl:call-template>
    |
    <xsl:call-template name="navButton">
      <xsl:with-param name="category" select="'jobs'"/>
      <xsl:with-param name="displayName" select="'Jobs'"/>
      <xsl:with-param name="url" select="'jobs.xml'"/>
    </xsl:call-template>
  </xsl:template>

  <!--
  *******************************************************************
  ** This template produces a "button" in the top navigation bar.
  ****************************************************************-->
  <xsl:template name="navButton">
    <xsl:param name="category"/>
    <xsl:param name="displayName"/>
    <xsl:param name="url"/>
    <xsl:choose>
      <!-- The current category is displayed as text -->
      <xsl:when test="$category = $global.nav.category">
        <xsl:value-of select="$displayName"/>
      </xsl:when>

      <!-- All other categories are displayed as hyperlinks -->
      <xsl:otherwise>
        <a href="{$url}">
          <xsl:value-of select="$displayName"/>
        </a>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  
  <!--
  *******************************************************************
  ** This template creates the left navigation area.
  ****************************************************************-->
  <xsl:template name="createLeftNavbar">
    Left Navigation Area
  </xsl:template>
</xsl:stylesheet> 

This stylesheet is quite simple in concept. First, it sets up the global.nav.category variable. The stylesheet uses XPath to check for the existence of a <meta> tag that contains a navigationCategory attribute:

<xsl:variable name="global.nav.category">
  <xsl:choose>
    <xsl:when test="/html/head/meta[@name='navigationCategory']">
      <xsl:value-of select="/html/head/meta
              [@name='navigationCategory']/@content"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:text>unknown</xsl:text>
    </xsl:otherwise>
  </xsl:choose>
</xsl:variable>

The first part of the XPath expression used by <xsl:when> locates any <meta> tags:

/html/head/meta

Next, a predicate is used to narrow down the list to the one <meta> tag that contains a navigationCategory attribute:

[@name='navigationCategory']

If this is found, the value of the content attribute is assigned to the global.nav.category variable. Otherwise, the value is unknown.

The XSLT stylesheet then contains a template that matches the / pattern. This template defines the overall XHTML page layout by creating a <table>. The document <head>, however, is simply copied from the input XHTML document:

<xsl:copy-of select="html/head"/>

Because the original <head> is merely copied to the result tree, any styles or scripts that page authors include in their own documents are preserved. The only drawback occurs when people define CSS styles that change the look and feel of the navigation areas, such as changing the fonts and colors used in a page. If this is a concern, you might want to include logic in the XSLT stylesheet that ignores all <style> tags and style attributes in the original XHTML document.

Once the <head> is copied, the XSLT stylesheet creates the <body> for the result tree. An XHTML <table> controls the overall page layout, and named XSLT templates are used to create the navigation areas:

<xsl:call-template name="createTopNavbar"/>
...
<xsl:call-template name="createLeftNavbar"/>

The createTopNavbar template is somewhat more complicated because it contains logic to display the current category differently. The createLeftNavbar template, on the other hand, simply copies some static content to the result. Finally, the contents of the <body> tag are copied from the original document to the result tree:

<xsl:copy-of select="html/body/* | html/body/text( )"/>

Unlike the <head>, the <body> is not copied directly. Instead, all elements and text within the <body> are copied. This prevents the following invalid XHTML from being produced:

<tr><td><body>...</body></td></tr>

The createTopNavbar named template is used to create the row of links in the top navigation area. For each navigation category, it calls the navButton template:

<xsl:call-template name="navButton">
  <xsl:with-param name="category" select="'home'"/>
  <xsl:with-param name="displayName" select="'Home'"/>
  <xsl:with-param name="url" select="'home.xml'"/>
</xsl:call-template>

The category parameter allows the navButton template to determine if the displayName parameter should be displayed as a hyperlink or text. The code to do this is emphasized in the navButton template (in Example 8-2) and is not repeated here.

None of this works without a servlet driving the process. In this example, all XHTML pages are stored in the web application's WEB-INF directory and saved with .xml filename extensions. Remember that these are the original web pages and do not contain any navigation areas. They are stored in the WEB-INF directory to ensure that clients cannot access them directly. Instead, clients must use a servlet called TemplateServlet to request all pages. This servlet locates the XML file, performs the XSLT transformation using templatePage.xslt, and sends the result tree back to the client browser. The entire process is transparent to clients because they see only the results of the transformation.

Table 8-1 shows the complete structure of the WAR file that supports this example.

Table 8-1. WAR file contents

File

Description

WEB-INF/web.xml

The deployment descriptor (see Example 8-3)

WEB-INF/classes/chap8/TemplateServlet.class

The servlet that drives the XSLT transformation (see Example 8-4)

WEB-INF/lib/javaxslt.jar

Contains the StylesheetCache class

WEB-INF/xml/company.xml

An example web page

WEB-INF/xml/home.xml

An example web page (see Example 8-1)

WEB-INF/xml/jobs.xml

An example web page

WEB-INF/xml/products.xml

An example web page

WEB-INF/xslt/templatePage.xslt

The XSLT stylesheet (see Example 8-2)

The deployment descriptor, web.xml , is shown in Example 8-3.

Example 8-3. Deployment descriptor

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
  "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
<web-app>
  <servlet>
    <servlet-name>template</servlet-name>
    <servlet-class>chap8.TemplateServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>template</servlet-name>
    <url-pattern>/template/*</url-pattern>
  </servlet-mapping>
</web-app> 

Since all files are protected under the WEB-INF directory, the /template/* URL pattern specified in the deployment descriptor is the only way for clients to access this application. The URL users type into their browser is: http://localhost:8080/chap8/template/home.xml.

This displays the page shown earlier in Figure 8-2. In this URL, the word template maps to the servlet, and /home.xml is the path information. This is retrieved by the servlet using the getPathInfo( ) method of HttpServletRequest. The source code for TemplateServlet is shown in Example 8-4.

Example 8-4. TemplateServlet.java

package chap8;

import com.oreilly.javaxslt.util.StylesheetCache;
import java.io.*;
import java.net.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;

/**
 * Applies a standard stylesheet to every XML page on a site.
 */
public class TemplateServlet extends HttpServlet {
    private String xsltFileName;

    /**
     * Locate the template stylesheet during servlet initialization.
     */
    public void init( ) throws UnavailableException {
        ServletContext ctx = getServletContext( );
        this.xsltFileName = ctx.getRealPath(
                    "/WEB-INF/xslt/templatePage.xslt");
        File f = new File(this.xsltFileName);

        if (!f.exists( )) {
            throw new UnavailableException(
                    "Unable to locate XSLT stylesheet: "
                    + this.xsltFileName, 30);
        }
    }

    public void doGet(HttpServletRequest req, HttpServletResponse res)
            throws ServletException, IOException {
        try {
            // use the ServletContext to locate the XML file
            ServletContext ctx = getServletContext( );
            String xmlFileName = ctx.getRealPath("/WEB-INF/xml"
                    + req.getPathInfo( ));

            // verify that the file exists
            if (!new File(xmlFileName).exists( )) {
                res.sendError(HttpServletResponse.SC_NOT_FOUND, xmlFileName);
            } else {
                res.setContentType("text/html");

                // load the XML file
                Source xmlSource = new StreamSource(new BufferedReader(
                        new FileReader(xmlFileName)));

                // use a cached version of the XSLT
                Transformer trans =
                        StylesheetCache.newTransformer(xsltFileName);
                trans.transform(xmlSource, new StreamResult(res.getWriter( )));
            }
        } catch (TransformerConfigurationException tce) {
            throw new ServletException(tce);
        } catch (TransformerException te) {
            throw new ServletException(te);
        }
    }
}

This is a fairly basic servlet whose sole purpose is to locate XML files and perform XSLT transformations. The init( ) method is used to locate templatePage.xslt from the WEB-INF/xslt directory:

ServletContext ctx = getServletContext( );
this.xsltFileName = ctx.getRealPath(
            "/WEB-INF/xslt/templatePage.xslt");

As discussed in earlier chapters, the getRealPath( ) method converts the path into a system-specific pathname. This allows the StylesheetCache class to locate the XSLT stylesheet properly. Later, in the doGet( ) method of the servlet, the same method is used to locate the requested XML file:

ServletContext ctx = getServletContext( );
String xmlFileName = ctx.getRealPath("/WEB-INF/xml"
        + req.getPathInfo( ));

As shown back in the source for TemplateServlet, it then checks for the existence of this file and sends an error if necessary. Otherwise, it uses JAXP to perform the XSLT transformation. This is where the navigation areas get added to the document.

More on Caching

 

In the TemplateServlet class, the XSLT stylesheets are cached using the com.oreilly.javaxslt.util.StylesheetCache class. In this particular example, however, the XML data and XSLT stylesheets are all static files. Because these files are not dynamically generated, it becomes possible to cache the transformation result, yielding the highest possible performance. The next chapter discusses a class called ResultCache that makes this possible.

Using XSLT stylesheets for page layout templates is a useful technique because individual page authors do not have to duplicate headers, footers, and navigation areas into every page they create. By centralizing page layout in one or more standard XSLT stylesheets, fewer changes are required to update the look of an entire web site.



Library Navigation Links

Copyright © 2002 O'Reilly & Associates. All rights reserved.