<xsl:param name="rootDir" select="'../docroot/'"/>
The select attribute defines a default value for
this parameter if none was specified. During the stylesheet
development process, the XSLT is tested using a static XML file. This
is done outside of a web application, so the parameter is not
specified and the root directory defaults to
../docroot/. This makes it possible to locate the
CSS file during development, when developers are working from a
static directory structure on their file systems. Later, when the
XSLT stylesheet is deployed to a web application and the servlet is
running, the servlet can specify a different value for this parameter
that indicates a directory relative to the web application context.
This is a useful technique whenever a stylesheet has to reference
external resources such as CSS files, JavaScript files, or images.
Next, the <xsl:output> element is used to
set up XHTML output. The XHTML 1.0 Strict DTD is
used, which eliminates many deprecated HTML 4.0 features. Because the
strict DTD does away with many formatting tags, a CSS file is
required to make the pages look presentable. All the XSLT needs to do
is produce HTML code that references the external stylesheet, as
shown here:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Discussion Forum Home</title>
<link href="{$rootDir}forum.css"
rel="stylesheet" type="text/css"/>
</head>
The XSLT processor does not actually deal with the CSS file. From the
perspective of XSLT, the <link> tag is just
text that is copied to the result tree during the transformation
process. Later, when the web browser displays the XHTML page, the
actual CSS file is loaded. This technique is great because styles can
be shared across all web pages without complicating the XSLT
stylesheets.
The remainder of the stylesheet is pretty basic -- just matching
patterns in the XML and producing XHTML content to the result tree.
One important thing to point out here is the way that hyperlinks are
created:
<a href="/forum/main/postMsg?mode=postNewMsg&boardID={@id}">Post Message</a>
The interesting part of this stylesheet involves construction of the
tree of messages. Since messages in the XML are hierarchical, the
XSLT must recursively process the data to properly show threads of
discussion. Here is another look at a portion of the
viewMonth.xml file presented earlier in this
chapter:
<viewMonth month="1" year="2001">
<board id="1">
<name>Java Programming</name>
<description>General programming questions about Java.</description>
</board>
<message id="1" day="1">
<subject>First test message</subject>
<authorEmail>burke_e@yahoo.com</authorEmail>
<message id="2" day="2">
<subject>Re: First test message</subject>
<authorEmail>aidan@nowhere.com</authorEmail>
</message>
</message>
<message id="3" day="4">
<subject>Another test message</subject>
<authorEmail>burke_e@yahoo.com</authorEmail>
</message>
</viewMonth>
In the XSLT stylesheet, the first part of the recursive process
selects all <message> elements occurring
immediately below the <viewMonth> element:
<xsl:apply-templates select="viewMonth/message"/>
This selects messages with ids 1 and
3, causing the following template to be
instantiated:
<xsl:template match="message">
<xsl:param name="indent" select="0"/>
This template takes a parameter for the level of indentation. If the
parameter is not specified, as in this first usage, it defaults to
0. This code is followed by very basic XSLT code
to produce a one-line summary of the current message, and then the
template recursively instantiates itself:
<xsl:apply-templates select="message">
<xsl:with-param name="indent" select="$indent + 1"/>
</xsl:apply-templates>
This efficiently selects all <message>
elements that occur immediately within the current message and
increments the indentation by 1. This allows the
stylesheet to indent replies appropriately. The recursive process
continues until no messages remain.
Example 7-10. XSLT for the Post/Reply message page
<?xml version="1.0" encoding="UTF-8"?>
<!--
***********************************************************
** postMsg.xslt
**
** Creates the "Post New Message" XHTML page and the
** "Reply to Message" XHTML page.
***********************************************************
-->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:import href="utils.xslt"/>
<!-- pass the root directory as a parameter, thus
allowing this stylesheet to refer to the CSS file -->
<xsl:param name="rootDir" select="'../docroot/'"/>
<xsl:output method="xml" version="1.0" encoding="UTF-8"
indent="yes"
doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"/>
<!-- ===== Global Variables ===== -->
<xsl:variable name="global.subject" select="/postMsg/prefill/subject"/>
<xsl:variable name="global.email" select="/postMsg/prefill/authorEmail"/>
<xsl:variable name="global.message" select="/postMsg/prefill/message"/>
<xsl:variable name="global.title">
<xsl:choose>
<xsl:when test="/postMsg/inResponseTo">
<xsl:text>Reply to Message</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>Post New Message</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!--
**********************************************************
** Create the XHTML web page
*******************************************************-->
<xsl:template match="/">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><xsl:value-of select="$global.title"/></title>
<link href="{$rootDir}forum.css"
rel="stylesheet" type="text/css"/>
</head>
<body>
<!-- show the page title and board name -->
<div class="box1">
<h1><xsl:value-of select="$global.title"/></h1>
<div>
<xsl:value-of select="postMsg/board/name"/>
</div>
</div>
<xsl:apply-templates select="postMsg/inResponseTo"/>
<div class="box2">
<!-- optionally display error message -->
<xsl:if test="postMsg/error/@code='ALL_FIELDS_REQUIRED'">
<p class="error">All fields are required...</p>
</xsl:if>
<!-- Create an XHTML form. The user will provide
the subject, and Email address, and
the message text -->
<form method="post" action="postMsg">
<div>
<input type="hidden" name="boardID"
value="{postMsg/board/@id}"/>
<!-- Determine the mode of operation -->
<xsl:choose>
<xsl:when test="/postMsg/inResponseTo">
<input type="hidden" name="origMsgID"
value="{postMsg/inResponseTo/@id}"/>
<input type="hidden" name="mode" value="replyToMsg"/>
</xsl:when>
<xsl:otherwise>
<input type="hidden" name="mode" value="postNewMsg"/>
</xsl:otherwise>
</xsl:choose>
</div>
<!-- Show the input fields in a table to
keep things aligned properly -->
<table>
<tr>
<td>Subject:</td>
<td>
<input type="text" name="msgSubject"
value="{$global.subject}" size="60" maxlength="70"/>
</td>
</tr>
<tr>
<td nowrap="nowrap">Your Email:</td>
<td>
<input type="text" name="authorEmail"
value="{$global.email}" size="60" maxlength="70"/>
</td>
</tr>
<tr valign="top">
<td>Message:</td>
<td>
<!-- xsl:text prevents the XSLT processor from collapsing to
<textarea/>, which caused problems with many browsers. -->
<textarea name="msgText" wrap="hard" rows="12"
cols="60"><xsl:value-of
select="$global.message"/><xsl:text> </xsl:text></textarea>
</td>
</tr>
<!-- The last table row contains a submit
and cancel button -->
<tr>
<td> </td>
<td>
<input type="submit" name="submitBtn" value="Submit"/>
<input type="submit" name="cancelBtn" value="Cancel"/>
</td>
</tr>
</table>
</form>
</div>
</body>
</html>
</xsl:template>
<!--
**********************************************************
** Show the text: 'In Response to: Msg Subject'
*******************************************************-->
<xsl:template match="inResponseTo">
<div>
In Response to:
<span style="font-weight: bold;">
<xsl:value-of select="subject"/>
</span>
</div>
</xsl:template>
</xsl:stylesheet>
Since this stylesheet must work for posting new messages as well as
for replying to messages, it must determine the appropriate mode of
operation. This can be accomplished by checking for the existence of
elements that occur only in one mode or the other. For example, the
<inResponseTo> XML element occurs only when
the user replies to an existing message. Therefore, the XSLT
stylesheet can define a variable for the page title as follows:
<xsl:variable name="global.title">
<xsl:choose>
<xsl:when test="/postMsg/inResponseTo">
<xsl:text>Reply to Message</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>Post New Message</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:when test="/postMsg/inResponseTo">
returns true when the
<inResponseTo> element exists in the
original XML data. In this case, the global.title
variable is set to "Reply to Message." Otherwise, the
title defaults to "Post New Message."
This stylesheet optionally displays an error message when the user
partially fills out the XHTML form and submits the data. The servlet
redisplays the page with an error message, allowing the user to fix
the problem. It does this by inserting the following XML element into
the data:
<error code="ALL_FIELDS_REQUIRED"/>
The XSLT stylesheet tests for the existence of this element as
follows:
<xsl:if test="postMsg/error/@code='ALL_FIELDS_REQUIRED'">
<p class="error">All fields are required...</p>
</xsl:if>
An additional trick used in this stylesheet involves its interaction
with a servlet. When the user submits the XHTML form data, the
servlet must determine which mode of operation the user was in. For
this task, the servlet looks for a request parameter called
mode. Legal values for this parameter are
replyToMsg and postNewMsg.
Since the user is submitting an XHTML form, the easiest way to pass
this data is via a hidden form field named mode.
Here is the code that does the work:
<xsl:choose>
<xsl:when test="/postMsg/inResponseTo">
<input type="hidden" name="origMsgID"
value="{postMsg/inResponseTo/@id}"/>
<input type="hidden" name="mode" value="replyToMsg"/>
</xsl:when>
<xsl:otherwise>
<input type="hidden" name="mode" value="postNewMsg"/>
</xsl:otherwise>
</xsl:choose>
The stylesheet also inserts a hidden form field that contains the
original message ID whenever the mode is
replyToMsg. On the servlet side, the code looks
something like this: