3.6. Ant Documentation Stylesheet
Apache's
Ant has taken the
Java development community by storm, supplementing traditional Java
IDEs and outright replacing Makefiles on most
Java development projects. Ant is a build tool, similar to the
make utility, only it uses XML files instead of
Makefiles. In addition to a portable build file
based on XML, Ant itself is written in Java and has few
platform-specific dependencies. Finally, since Ant can reuse the same
running instance of the Java Virtual Machine for nearly every step of
the build process, it is blazingly fast. Ant can be downloaded from
http://jakarta.apache.org and is open source
software.
3.6.1. Ant Basics
Ant is driven by an XML build
file,
which consists of one
project.
This project contains one or more
targets,
and targets can have dependencies on one another. The project and
targets are represented as <project> and
<target>
in the XML build file; <project> must be the
document root element. It is common to have a "prepare"
target that builds the output directories and a "compile"
target that depends on the "prepare" target. If you tell
Ant to execute the "compile" target, it first checks to
see that the "prepare" target has created the necessary
directories. The structure of an
Ant build file looks like this:
<?xml version="1.0"?>
<project name="SampleProject" default="compile" basedir=".">
<!-- global properties -->
<property name="srcdir" value="src"/>
<property name="builddir" value="build"/>
<target name="prepare" description="Creates the output directories">
...tasks
</target>
<target name="compile" depends="prepare">
...tasks
</target>
<target name="distribute" depends="compile">
...tasks
</target>
</project>
For each target, Ant is smart enough to know if files
have been modified and if it needs to do any work. For compilation,
the timestamps of .class files are compared to
timestamps of .java files. Through these
dependencies, Ant can avoid unnecessary compilation and perform quite
well. Although the targets shown here contain only single
dependencies, it is possible for a target to depend on several other
targets:
<target name="X" depends="A,B,C">
Although Ant build files are much simpler than corresponding
Makefiles, complex projects can introduce many
dependencies that are difficult to visualize. It can be helpful to
view the complete list of targets with dependencies displayed
visually, such as in a hierarchical tree view. XSLT can be used to
generate this sort of report.
3.6.2. Stylesheet Functionality
Since the build file is
XML, XSLT makes it easy to generate HTML web pages that summarize the
targets and dependencies. Our stylesheet also shows a list of
global properties and can easily be extended to
display anything else contained in the build file.
Although this stylesheet creates several useful HTML tables in its
report, its most interesting feature is the ability to display a
complete dependency graph of all Ant build targets. The output for
this graph is shown in Example 3-13.
Example 3-13. Target dependencies
clean
all (depends on clean, dist)
prepare
tomcat (depends on prepare)
j2ee (depends on tomcat)
j2ee-dist (depends on j2ee)
main (depends on tomcat, webapps)
dist (depends on main, webapps)
dist-zip (depends on dist)
all (depends on clean, dist)
webapps (depends on prepare)
dist (depends on main, webapps)
dist-zip (depends on dist)
all (depends on clean, dist)
main (depends on tomcat, webapps)
dist (depends on main, webapps)
dist-zip (depends on dist)
all (depends on clean, dist)
targets
This is actually the output from the Ant build file included with
Apache's Tomcat. The list of top-level targets is shown at the
root level, and dependent targets are indented and listed next. The
targets shown in parentheses list what each target depends on. This
tree view is created by recursively analyzing the dependencies, which
appear in the Ant build file as follows:
<target name="all" depends="clean,dist">
Figure 3-1 shows a portion of the output in a web
browser. A table listing all targets follows the dependency graph.
The output concludes with a table of all global properties defined in
the Ant build file.
Figure 3-1. Antdoc sample output
The comma-separated list of dependencies presents a challenge that is
best handled through recursion. For each target in the build file, it
is necessary to print a list of targets that depend on that target.
It is possible to have many dependencies, so an Ant build file may
contain a <target> that looks like this:
<target name="docs" depends="clean, prepare.docs, compile">
In the first prototype of the Antdoc stylesheet, the algorithm to
print the dependency graph uses simple substring operations to
determine if another target depends on the current target. This turns
out to be a problem because two unrelated targets might have similar
names, so some Ant build files cause infinite recursion in the
stylesheet. In the preceding example, the original prototype of
Antdoc says that "docs" depends on itself because
its list of dependencies contains the text
prepare.docs.
In the finished version of Antdoc, the list of target dependencies is
cleaned up to remove spaces and commas. For example, "clean,
prepare.docs, compile" is converted into
"|clean|prepare.docs|compile|". By placing the
pipe (|) character before and after every
dependency, it becomes much easier to locate dependencies by
searching for strings.
3.6.3. The Complete Example
The complete XSLT stylesheet is listed in Example 3-14. Comments within the code explain what happens
in each step. To use this stylesheet, simply invoke your favorite
XSLT processor at the command line, passing
antdoc.xslt and your Ant build
file as parameters.
Example 3-14. antdoc.xslt
<?xml version="1.0" encoding="UTF-8"?>
<!--
**************************************************************
** Antdoc v1.0
**
** Written by Eric Burke (burke_e@ociweb.com)
**
** Uses XSLT to generate HTML summary reports of Ant build
** files.
***********************************************************-->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"
doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
indent="yes" encoding="UTF-8"/>
<!-- global variable: the project name -->
<xsl:variable name="projectName" select="/project/@name"/>
<xsl:template match="/">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Ant Project Summary -
<xsl:value-of select="$projectName"/></title>
</head>
<body>
<h1>Ant Project Summary</h1>
<xsl:apply-templates select="project"/>
</body>
</html>
</xsl:template>
<!--
***************************************************************
** "project" template
************************************************************-->
<xsl:template match="project">
<!-- show the project summary table, listing basic info
such as name, default target, and base directory -->
<table border="1" cellpadding="4" cellspacing="0">
<tr><th colspan="2">Project Summary</th></tr>
<tr>
<td>Project Name:</td>
<td><xsl:value-of select="$projectName"/></td>
</tr>
<tr>
<td>Default Target:</td>
<td><xsl:value-of select="@default"/></td>
</tr>
<tr>
<td>Base Directory:</td>
<td><xsl:value-of select="@basedir"/></td>
</tr>
</table>
<!-- show all target dependencies as a tree -->
<h3>Target Dependency Tree</h3>
<xsl:apply-templates select="target[not(@depends)]" mode="tree">
<xsl:sort select="@name"/>
</xsl:apply-templates>
<p/>
<!-- Show a table of all targets -->
<table border="1" cellpadding="4" cellspacing="0">
<tr><th colspan="3">List of Targets</th></tr>
<tr>
<th>Name</th>
<th>Dependencies</th>
<th>Description</th>
</tr>
<xsl:apply-templates select="target" mode="tableRow">
<xsl:sort select="count(@description)" order="descending"/>
<xsl:sort select="@name"/>
</xsl:apply-templates>
</table>
<p/>
<xsl:call-template name="globalProperties"/>
</xsl:template>
<!--
***************************************************************
** Create a table of all global properties.
************************************************************-->
<xsl:template name="globalProperties">
<xsl:if test="property">
<table border="1" cellpadding="4" cellspacing="0">
<tr><th colspan="2">Global Properties</th></tr>
<tr>
<th>Name</th>
<th>Value</th>
</tr>
<xsl:apply-templates select="property" mode="tableRow">
<xsl:sort select="@name"/>
</xsl:apply-templates>
</table>
</xsl:if>
</xsl:template>
<!--
***************************************************************
** Show an individual property in a table row.
************************************************************-->
<xsl:template match="property[@name]" mode="tableRow">
<tr>
<td><xsl:value-of select="@name"/></td>
<td>
<xsl:choose>
<xsl:when test="not(@value)">
<xsl:text disable-output-escaping="yes">&nbsp;</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@value"/>
</xsl:otherwise>
</xsl:choose>
</td>
</tr>
</xsl:template>
<!--
***************************************************************
** "target" template, mode=tableRow
** Print a target name and its list of dependencies in a
** table row.
************************************************************-->
<xsl:template match="target" mode="tableRow">
<tr valign="top">
<td><xsl:value-of select="@name"/></td>
<td>
<xsl:choose>
<xsl:when test="@depends">
<xsl:call-template name="parseDepends">
<xsl:with-param name="depends" select="@depends"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>-</xsl:otherwise>
</xsl:choose>
</td>
<td>
<xsl:if test="@description">
<xsl:value-of select="@description"/>
</xsl:if>
<xsl:if test="not(@description)">
<xsl:text>-</xsl:text>
</xsl:if>
</td>
</tr>
</xsl:template>
<!--
***************************************************************
** "parseDepends" template
** Tokenizes and prints a comma separated list of dependencies.
** The first token is printed, and the remaining tokens are
** recursively passed to this template.
************************************************************-->
<xsl:template name="parseDepends">
<!-- this parameter contains the list of dependencies -->
<xsl:param name="depends"/>
<!-- grab everything before the first comma,
or the entire string if there are no commas -->
<xsl:variable name="firstToken">
<xsl:choose>
<xsl:when test="contains($depends, ',')">
<xsl:value-of
select="normalize-space(substring-before($depends, ','))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="normalize-space($depends)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="remainingTokens"
select="normalize-space(substring-after($depends, ','))"/>
<!-- output the first dependency -->
<xsl:value-of select="$firstToken"/>
<!-- recursively invoke this template with the remainder
of the comma separated list -->
<xsl:if test="$remainingTokens">
<xsl:text>, </xsl:text>
<xsl:call-template name="parseDepends">
<xsl:with-param name="depends" select="$remainingTokens"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<!--
***************************************************************
** This template will begin a recursive process that forms a
** dependency graph of all targets.
************************************************************-->
<xsl:template match="target" mode="tree">
<xsl:param name="indentLevel" select="'0'"/>
<xsl:variable name="curName" select="@name"/>
<div style="text-indent: {$indentLevel}em;">
<xsl:value-of select="$curName"/>
<!-- if the 'depends' attribute is present, show the
list of dependencies -->
<xsl:if test="@depends">
<xsl:text> (depends on </xsl:text>
<xsl:call-template name="parseDepends">
<xsl:with-param name="depends" select="@depends"/>
</xsl:call-template>
<xsl:text>)</xsl:text>
</xsl:if>
</div>
<!-- set up the indentation -->
<xsl:variable name="nextLevel" select="$indentLevel+1"/>
<!-- search all other <target> elements that have "depends"
attributes -->
<xsl:for-each select="../target[@depends]">
<!-- Take the comma-separated list of dependencies and
"clean it up". See the comments for the "fixDependency"
template -->
<xsl:variable name="correctedDependency">
<xsl:call-template name="fixDependency">
<xsl:with-param name="depends" select="@depends"/>
</xsl:call-template>
</xsl:variable>
<!-- Now the dependency list is pipe (|) delimited, making
it easier to reliably search for substrings. Recursively
instantiate this template for all targets that depend
on the current target -->
<xsl:if test="contains($correctedDependency,concat('|',$curName,'|'))">
<xsl:apply-templates select="." mode="tree">
<xsl:with-param name="indentLevel" select="$nextLevel"/>
</xsl:apply-templates>
</xsl:if>
</xsl:for-each>
</xsl:template>
<!--
***************************************************************
** This template takes a comma-separated list of dependencies
** and converts all commas to pipe (|) characters. It also
** removes all spaces. For instance:
**
** Input: depends="a, b,c "
** Ouput: |a|b|c|
**
** The resulting text is much easier to parse with XSLT.
************************************************************-->
<xsl:template name="fixDependency">
<xsl:param name="depends"/>
<!-- grab everything before the first comma,
or the entire string if there are no commas -->
<xsl:variable name="firstToken">
<xsl:choose>
<xsl:when test="contains($depends, ',')">
<xsl:value-of
select="normalize-space(substring-before($depends, ','))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="normalize-space($depends)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- define a variable that contains everything after the
first comma -->
<xsl:variable name="remainingTokens"
select="normalize-space(substring-after($depends, ','))"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="$firstToken"/>
<xsl:choose>
<xsl:when test="$remainingTokens">
<xsl:call-template name="fixDependency">
<xsl:with-param name="depends" select="$remainingTokens"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:text>|</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
3.6.3.1. Specifying XHTML output
One of the first things this
stylesheet does is set the output method to "xml"
because the resulting page will be XHTML instead of HTML. The
doctype-public and
doctype-system are required for valid XHTML and
indicate the strict DTD in this case:
<xsl:output method="xml"
doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
indent="yes" encoding="UTF-8"/>
The remaining XHTML requirement is to declare the namespace of the
<html> element:
<xsl:template match="/">
<html xmlns="http://www.w3.org/1999/xhtml">
...
</html>
</xsl:template>
Because of these XSLT elements, the result tree will contain the
following XHTML:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
...
</html>
3.6.3.2. Creating the dependency graph
The most interesting and difficult aspect of this stylesheet is its
ability to display the complete dependency graph for all Ant build
targets. The first step is to locate all of the targets that do not
have any dependencies. As shown in Example 3-13,
these targets are named clean,
prepare, and targets for the
Tomcat build file. They are selected by looking for
<target> elements that do not have an
attribute named depends:
<!-- show all target dependencies as a tree -->
<h3>Target Dependency Tree</h3>
<xsl:apply-templates select="target[not(@depends)]" mode="tree">
<xsl:sort select="@name"/>
</xsl:apply-templates>
The [not(@depends)] predicate will refine the list
of <target> elements to include only those
that do not have an attribute named depends. The
<xsl:apply-templates> will instantiate the
following template without any parameters:
<xsl:template match="target" mode="tree">
<xsl:param name="indentLevel" select="'0'"/>
<xsl:variable name="curName" select="@name"/>
If you refer to Example 3-14, you will see that this
is the second-to-last template in the stylesheet. Since it is broken
up into many pieces here, you may find it easier to refer to the
original code as this description progresses. Since the
indentLevel parameter is not specified, it
defaults to '0', which makes sense for the
top-level targets. As this template is instantiated recursively, the
level of indentation increases. The
curName variable is local to this template and
contains the current Ant target name. Lines of text are indented
using a style attribute:
<div style="text-indent: {$indentLevel}em;">
CSS is used to indent everything contained within the
<div> tag by the specified number of
em s.[13] The value of
the current target name is then printed using the appropriate
indentation:
<xsl:value-of select="$curName"/>
If the current <target> element in the Ant
build file has a depends attribute, its
dependencies are printed next to the target name as part of the
report. The parseDepends template handles this
task. This template, also part of Example 3-14, is
instantiated using <xsl:call-template>, as
shown here:
<xsl:if test="@depends">
<xsl:text> (depends on </xsl:text>
<xsl:call-template name="parseDepends">
<xsl:with-param name="depends" select="@depends"/>
</xsl:call-template>
<xsl:text>)</xsl:text>
</xsl:if>
To continue with the dependency graph, the target
template must instantiate itself recursively. Before doing this, the
indentation must be increased. Since XSLT does not allow variables to
be modified, a new variable is created:
<xsl:variable name="nextLevel" select="$indentLevel+1"/>
When the template is recursively instantiated,
nextLevel will be passed as the value for the
indentLevel parameter:
<xsl:apply-templates select="." mode="tree">
<xsl:with-param name="indentLevel" select="$nextLevel"/>
</xsl:apply-templates>
The remainder of the template is not duplicated here, but is
emphasized in Example 3-14. The basic algorithm is as
follows:
-
Use <xsl:for-each> to select all targets
that have dependencies.
-
Instantiate the "fixDependency" template to replace
commas with | characters.
-
Recursively instantiate the "target" template for all
targets that depend on the current target.
3.6.3.3. Cleaning up dependency lists
The final template in the Antdoc stylesheet is responsible for
tokenizing a comma-separated list of dependencies, inserting pipe
(|) characters between each dependency:
<xsl:template name="fixDependency">
<xsl:param name="depends"/>
The depends parameter may contain text such as
"a, b, c." The template tokenizes this text, producing
the following output:
|a|b|c|
Since XSLT does not have an equivalent to Java's
StringTokenizer class, recursion is required once
again. The technique is to process the text before the first comma
then recursively process everything after the comma. The following
code assigns everything before the first comma to the
firstToken variable:
<xsl:variable name="firstToken">
<xsl:choose>
<xsl:when test="contains($depends, ',')">
<xsl:value-of
select="normalize-space(substring-before($depends, ','))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="normalize-space($depends)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
If the depends parameter contains a comma, the
substring-before( ) function locates the text
before the comma, and normalize-space( ) trims
whitespace. If no commas are found, there must be only one
dependency.
Next, any text after the first comma is assigned to the
remainingTokens variable. If there are no commas,
the remainingTokens variable will contain an empty
string:
<xsl:variable name="remainingTokens"
select="normalize-space(substring-after($depends, ','))"/>
The template then outputs a pipe character followed by the value of
the first token:
<xsl:text>|</xsl:text>
<xsl:value-of select="$firstToken"/>
Next, if the remainingTokens variable is nonempty,
the fixDependency template is instantiated
recursively. Otherwise, another pipe character is output at the end:
<xsl:choose>
<xsl:when test="$remainingTokens">
<xsl:call-template name="fixDependency">
<xsl:with-param name="depends" select="$remainingTokens"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:text>|</xsl:text>
</xsl:otherwise>
</xsl:choose>
Ideally, these descriptions will help clarify some of the more
complex aspects of this stylesheet. The only way to really learn how
this all works is to experiment, changing parts of the XSLT
stylesheet and then viewing the results in a web browser. You should
also make use of a command-line XSLT processor and view the results
in a text editor. This is important because browsers may skip over
tags they do not understand, so you might not see mistakes until you
view the source.
 |  |  | | 3.5. Schema Evolution |  | 4. Java-Based Web Technologies |
Copyright © 2002 O'Reilly & Associates. All rights reserved.
|