• No results found

will help identify any naming conflicts you did not know about.

3.6 Ant Documentation Stylesheet

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 ([email protected]) **

** 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:text disable-output- escaping="yes">&amp;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: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="$ind entLevel+1"/> <!-- search all other <target> elements that have "depends" attributes -->

<xsl:for-each select="../target[@depends]">

<!-- Take the comma-separated list of dependencies and

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 e verything 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

ems.[5] The value of the current target name is then printed using the appropriate indentation:

[5] An em is approximately equal to the width of a lowercase letter "m" in the current font.

<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.