Hack 58 Use EXSLT Extensions

figs/moderate.gif figs/hack58.gif

Extensibility defines the ways in which a language can be extended. XSLT is extensible, meaning that if you are a programmer, you can add your own functionality to a processor in the form of extension elements, attributes, and functions. The developers of XSLT realized that they couldn't please everyone with their first shot (who can?), so they made it possible for developers to add features to their XSLT processors independently, and to share those features with others.

Most processors offer their own internal extensions, such as Xalan and Saxon. The EXSLT group also provides a number of extensions that can be supported directly by a processor or by pure XSLT 1.0 processors (http://www.exslt.org). EXSLT organizes its extensions into modules, such as the math and string modules. You can even submit extensions to EXSLT.

The EXSLT effort attempts to standardize and unify all XSLT 1.0 extensions. Saxon, for example, now implements many, but not all, EXSLT extension functions. It is good practice to use EXSLT extensions when available, if your processor supports them. It may be easier, however, to simply use a proprietary extension offered by your processor. XSLT 2.0 and XPath 2.0 offer many more functions than their predecessors, and will likely be the most successful at unifying previous extensions to XSLT 1.0 and XPath 1.0.

EXSLT currently offers 74 extensions, most of them functions. Many of the functions offer a pure XSLT 1.0 solution using the call-template element using with-param children. Table 3-2 lists a single sample from each of EXSLT's eight modules.

Table 3-2. Sampling of EXSLT extensions

Extension

Module

Type

Description

date:date()

date and time

function

Returns the current date.

dyn:evaluate(string)

dynamic

function

Evaluates an XPath expression.

exsl:node-set(object)

common

function

Returns a node-set from a result tree fragment defined in a variable.

func:function

functions

element

Declares an extension function.

math:lowest(node-set)

math

function

Returns the lowest value from a node-set.

regexp:test(string, string, string?)

regular expressions

function

Returns true if the string in the first argument matches the regular expression in the second argument (the third argument is an optional flag).

set:difference(node-set, node-set)

sets

function

Returns a node-set from nodes that exist in one node-set but not another.

str:tokenize(string, string?)

strings

function

Breaks a string into tokens (the second argument is an optional delimiter).


You'll get a chance to use several EXSLT extensions in the following examples.

3.29.1 EXSLT's date:date( ), date:time( ), and math:lowest( ) Functions

Here is a simple order for oats from a feed store represented in XML (order.xml in Example 3-60).

Example 3-60. order.xml
<?xml version="1.0"?>

   

<order id="TDI-983857">

 <store>Prineville</store>

 <product>feed-grade whole oats</product>

 <package>sack</package>

 <weight std="lbs.">50</weight>

 <quantity>23</quantity>

 <price cur="USD">

  <high>5.99</high>

  <regular>4.99</regular>

  <discount>3.99</discount>

 </price>

 <ship>the back of Tom's pickup</ship>

</order>

EXSLT extensions from the math package and the dates and times package are used in the following stylesheet, order.xsl (Example 3-61). They augment the information in the order.

Example 3-61. order.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"



xmlns:date="http://exslt.org/dates-and-times" xmlns:math="http://exslt.org/math"



extension-element-prefixes="date math">>



<xsl:output method="xml" indent="yes" encoding="ISO-8859-1"/>



<xsl:strip-space elements="*"/>



   



<xsl:template match="order">



 <xsl:copy>



  <xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>



  <date><xsl:value-of select="date:date( )"/></date>



  <time><xsl:value-of select="date:time( )"/></time>



  <xsl:copy-of select="product|package|weight|quantity"/>



  <price cur="{price/@cur}"><xsl:value-of select="math:lowest(price/*)"/></price>



  <total><xsl:value-of select="format-number(math:lowest(price/*)*quantity,

'#,###.00')"/></total>



  <xsl:copy-of select="ship"/>



 </xsl:copy>



</xsl:template>



   



</xsl:stylesheet>

Using the date:date() and date:time() functions from the dates and times module (lines 10 and 11) and the math:lowest() function from the math module (line 14), order.xsl transforms and improves upon the information in the original order.xml. The date:date() and date:time() functions provide the system date and time to the result tree. The math:lowest() function takes a node-set as an argument. The function then selects the node having the lowest value, in this case 3.99.

The extension-element-prefixes attribute (line 3) prevents the processor from outputting prefixed namespace declarations on element nodes in the output. The format-number() function on line 14 cleans up the number produced by the lowest() function (http://www.w3.org/TR/xslt#format-number).

Saxon supports these EXSLT functions, so you can get results by issuing the following commands. You can get both Instant Saxon (saxon.exe) and Saxon JAR (saxon8.jar) were discussed in an earlier hack (see [Hack #32] ).

The first example is for Instant Saxon:

saxon order.xml order.xsl

The second is for using Java directly:

java -jar saxon8.jar order.xml order.xsl

These commands will produce the following results (Example 3-62).

Example 3-62. Output of order.xsl
<?xml version="1.0" encoding="ISO-8859-1"?>

<order id="TDI-983857">

   <date>2004-03-05</date>

   <time>14:43:01-07:00</time>

   <product>feed-grade whole oats</product>

   <package>sack</package>

   <weight std="lbs.">50</weight>

   <quantity>23</quantity>

   <price cur="USD">3.99</price>

   <total>91.77</total>

   <ship>the back of Tom's pickup</ship>

</order>

3.29.2 EXSLT's exsl:node-set Function

EXSLT also has a function for converting result tree fragments into node-sets. In XSLT 1.0, a variable can hold a special XSLT type called a result tree fragment. Such a fragment can hold plain bits of text or XML nodes, but it isn't treated natively as an XML node-set. Nonetheless, with an extension function, you can cast a result tree fragment as a node-set and manipulate it as such.

EXSLT offers an extension function that treats result tree fragments as an XML node-set. It is called exsl:node-set(). The enode-set.xsl stylesheet is shown in Example 3-63.

Example 3-63. encode-set.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"



xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl">

<xsl:output method="xml" indent="yes"/>

<xsl:variable name="frag">

<python>

 <description>Python 2.3 String Escapes</description>

 <escape purpose="ignore EOL">\</escape>

 <escape purpose="backslash">\\</escape>

 <escape purpose="octal value">\ddd</escape>

 <escape purpose="hexadecimal">\xXX</escape>

 <escape purpose="other">\other</escape>

 <escape purpose="single quote">\'</escape>

 <escape purpose="double quote">\"</escape>

</python>

</xsl:variable>



<xsl:template match="python">

 <xsl:copy>

  <xsl:copy-of select="exsl:node-set($frag)/python/*"/>

  <xsl:apply-templates select="escape"/>

 </xsl:copy>

</xsl:template>



<xsl:template match="escape">

 <xsl:copy-of select="."/>

</xsl:template>



</xsl:stylesheet>

The EXSLT namespace is declared on line 2, and the exsl prefix is noted as an extension element prefix on line 2 as well. A variable named frag is defined on lines 4 through 15. This variable contains a result tree fragment that happens to be an XML node-set. With the node-set() function (line 19), the XSLT processor will treat this node-set as a chunk of XML rather than just a fragment of nondescript text.

Here is the document escapes.xml (Example 3-64).

Example 3-64. escapes.xml
<?xml version="1.0"?>

   

<python version="2.3">

 <escape purpose="bell">\a</escape>

 <escape purpose="backspace">\b</escape>

 <escape purpose="formfeed">\f</escape>

 <escape purpose="newline">\n</escape>

 <escape purpose="carriage return">\r</escape>

 <escape purpose="horizontal tab">\t</escape>

 <escape purpose="vertical tab">\v</escape>

</python>

If you apply this against escapes.xml with Saxon, using either:

saxon escapes.xml enode-set.xsl

or:

java -jar saxon8.jar escapes.xml enode-set.xsl

you will get the result in Example 3-65.

Example 3-65. Output of encode-set.xsl
<?xml version="1.0" encoding="utf-8"?>

<python>

   <description>Python 2.3 String Escapes</description>

   <escape purpose="ignore EOL">\</escape>

   <escape purpose="backslash">\\</escape>

   <escape purpose="octal value">\ddd</escape>

   <escape purpose="hexadecimal">\xXX</escape>

   <escape purpose="other">\other</escape>

   <escape purpose="single quote">\'</escape>

   <escape purpose="double quote">\"</escape>

   <escape purpose="bell">\a</escape>

   <escape purpose="backspace">\b</escape>

   <escape purpose="formfeed">\f</escape>

   <escape purpose="newline">\n</escape>

   <escape purpose="carriage return">\r</escape>

   <escape purpose="horizontal tab">\t</escape>

   <escape purpose="vertical tab">\v</escape>

</python>

Saxon supports many but not all EXSLT extensions; however, Saxon's documentation states that it prefers that users work with EXSLT's extensions over Saxon's (see http://saxon.sourceforge.net/saxon6.5.3/extensions.html or http://www.saxonica.com/documentation/documentation.html).

Many EXSLT extensions are also implemented as pure XSLT 1.0, meaning that they use imported templates in tandem with call-template to implement the functionality. (Nevertheless, some extensions like node-set() cannot be implemented in XSLT 1.0 alone.) The call-template element acts as a function call. These pure implementations, however, are several years old, and I could not get a number of them to work as advertised. Therefore, I won't be exploring them here.


3.29.3 See Also

  • Learning XSLT, by Michael Fitzgerald (O'Reilly), on pages 254-256 and 262-265.