Menu

Extending XSLT with EXSLT

January 5, 2005

Bob DuCharme

In an earlier column titled XSLT Extensions, I described how the Extensions section of the XSLT 1.0 Recommendation provides a way to identify the use of extensions to the XSLT 1.0 language. If your XSLT processor implements specialized new elements and functions, this gives your stylesheet a standard way to declare them and to check whether the processor in use implements them. The good news is that, for example, a Java programmer using a Java-based XSLT implementation can add and use customized XPath functions in his or her stylesheets. The bad news is that this customization can kill the stylesheet's portability across XSLT processors.

Unless... some hardworking XSLT devotees coordinate a collection of extensions to be implemented the same way by different XSLT processors. In early 2001, Leigh Dodds wrote in XML.com about how Jeni Tennison, Uche Ogbuji, David Carlisle, and others laid the ground work for EXSLT, a "a community initiative to provide extensions to XSLT." They've led this community effort to define a wide variety of extension elements and functions, and many useful functions are implemented identically in three of my four favorite XSLT processors: Saxon, Xalan Java, and libxslt. (My other favorite, XSLT C++, is working on it.) In this month's column, we'll look at two stylesheets that demonstrate a collection of EXSLT functions that work identically in Saxon 8.1, Xalan Java 2.4.1, and libxslt 10106.

Date and Time Functions

The first stylesheet has two groups of function calls. The calls under the line "right now" demonstrate what happens when you call a selection of functions from EXSLT's dates and times module without passing any parameter: the function assumes that you want this information about the point in time right now.


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

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

                extension-element-prefixes="date"

                version="1.0">



  <xsl:template match="/">



    right now:

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

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

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

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

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

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

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

    date:day-of-week-in-month: <xsl:value-of select="date:day-of-week-in-month()"/>

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

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

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

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

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

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

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

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

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

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



    with parameter of '2003-07-11' passed:

    date:day-name: <xsl:value-of select="date:day-name('2003-07-11')"/>

    date:day-abbreviation: <xsl:value-of select="date:day-abbreviation

	     ('2003-07-11')"/>

    date:day-in-week: <xsl:value-of select="date:day-in-week('2003-07-11')"/>

    date:day-in-year: <xsl:value-of select="date:day-in-year('2003-07-11')"/>

    date:day-of-week-in-month: <xsl:value-of select="date:day-of-week-in-month

	     ('2003-07-11')"/>

    date:leap-year: <xsl:value-of select="date:leap-year('2003-07-11')"/>

    date:month-abbreviation: <xsl:value-of select="date:month-abbreviation

	     ('2003-07-11')"/>

    date:month-name: <xsl:value-of select="date:month-name('2003-07-11')"/>

    date:week-in-year: <xsl:value-of select="date:week-in-year('2003-07-11')"/>



  </xsl:template>



</xsl:stylesheet>

The xsl:stylesheet element's extension-element-prefixes attribute lists the prefixes associated with any namespaces that were declared to let the stylesheet use extensions from those namespaces. Usually, this means URLs associated with the XSLT processor in use (for example, http://xml.apache.org/xalan and http://saxon.sf.net/) because the extensions are added features provided by the processor's developer. The http://exslt.org/dates-and-times URL shown above is used by all XSLT processors that implement any of the EXSLT date functions, making this a stylesheet that uses extensions while remaining fairly portable—a very nice combination.

Once you've seen the output (use any well-formed XML file as the source document), most functions in the stylesheet above are self-explanatory, although I had to check the documentation to see what date:date-of-week-in-month meant: "the day-of-the-week in a month of a date as a number (e.g. 3 for the 3rd Tuesday in May)."


right now:

date:date: 2004-12-09

date:date-time: 2004-12-09T18:30:07-05:00

date:day-name: Thursday

date:day-abbreviation: Thu

date:day-in-week: 5

date:day-in-month: 9

date:day-in-year: 344

date:day-of-week-in-month: 2

date:hour-in-day: 18

date:leap-year: true

date:minute-in-hour: 30

date:month-abbreviation: Dec

date:month-in-year: 12

date:month-name: December

date:second-in-minute: 7

date:time: 18:30:07-05:00

date:week-in-year: 50

date:year: 2004



with parameter of '2003-07-11' passed:

date:day-name: Friday

date:day-abbreviation: Fri

date:day-in-week: 6

date:day-in-year: 192

date:day-of-week-in-month: 2

date:leap-year: false

date:month-abbreviation: Jul

date:month-name: July

date:week-in-year: 28

The above stylesheet's second group of function calls, beginning with the phrase "with parameter of '2003-07-11' passed," demonstrates many of the functions from the first group with an ISO 8601 representation of a specific date passed. It only demonstrates the functions whose implementation required more than pulling a substring out of the passed date. For example, the date:day-name function shows that 2003-07-11 falls on a Friday.

Other EXSLT Functions

The following stylesheet, which also works on the three XSLT processors mentioned above, demonstrates several more handy EXSLT functions.


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

                xmlns:exsl="http://exslt.org/common"

                xmlns:math="http://exslt.org/math"

                xmlns:set="http://exslt.org/sets"

                extension-element-prefixes="exsl math set"

                version="1.0">



  <xsl:variable name="var1">

    <a>

      <b fn="mick" ln="taylor">5</b>

      <b fn="mick" ln="jones">2</b>

      <b fn="dick" ln="taylor">8</b>

      <b fn="steve" ln="jones">2</b>

      <b fn="roger" ln="taylor">8</b>

      <b fn="roger" ln="waters">3</b>

    </a>

  </xsl:variable>



  <xsl:template match="/">



    exsl:object-type(3): <xsl:value-of select="exsl:object-type(3)"/>

    exsl:object-type('potrzebie'): <xsl:value-of select="exsl:object-type

	     ('potrzebie')"/>

    exsl:object-type('$var1'): <xsl:value-of select="exsl:object-type

	     ($var1)"/>

    exsl:object-type(2 > 1): <xsl:value-of select="exsl:object-type

	     (2 > 1)"/>

    exsl:object-type(..): <xsl:value-of select="exsl:object-type(..)"/>



    math:constant('PI',4): <xsl:value-of select="math:constant('PI',4)"/>

    math:constant('PI',12): <xsl:value-of select="math:constant('PI',12)"/>

    math:constant('E',12): <xsl:value-of select="math:constant('E',12)"/>

    math:power(2,10): <xsl:value-of select="math:power(2,10)"/>

    math:power(16,.25): <xsl:value-of select="math:power(16,.25)"/>

    math:sqrt(256): <xsl:value-of select="math:sqrt(256)"/>

    math:sin(30 div 57.2957795): <xsl:value-of select="math:sin

	     (30 div 57.2957795)"/>

    math:cos(120 div 57.2957795): <xsl:value-of select="math:cos

	     (120 div 57.2957795)"/>

    math:tan(45 div 57.2957795): <xsl:value-of select="math:tan

	     (45 div 57.2957795)"/>



    set:distinct(exsl:node-set($var1)/a/b/text()): 

    <xsl:for-each select="set:distinct(exsl:node-set($var1)/a/b/text())">

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

      <xsl:text> </xsl:text>

    </xsl:for-each>



    set:difference():

    <xsl:for-each select="set:difference(exsl:node-set($var1)/a/b[@fn='mick'],

                          exsl:node-set($var1)/a/b[@ln='jones'])">

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

    </xsl:for-each>



  </xsl:template>



  <xsl:template match="@*|node()">

    <xsl:copy>

      <xsl:apply-templates select="@*|node()"/>

    </xsl:copy>

  </xsl:template>



</xsl:stylesheet>

It has two template rules: the first, which gets triggered when the XSLT processor sees the root of the source tree, demonstrates our second batch of EXSLT functions, and the second template rule copies nodes not covered by the first one. Like the second template rule, the variable declaration that starts the stylesheet makes some of the functions easier to demonstrate. Before looking at the first template rule's individual function calls, let's look at the result of the stylesheet, which you can run with any well-formed XML file as its source:




exsl:object-type(3): number

exsl:object-type('potrzebie'): string

exsl:object-type('$var1'): node-set

exsl:object-type(2 > 1): boolean

exsl:object-type(..): node-set

 

math:constant('PI',4): 3.1415

math:constant('PI',12): 3.141592653589

math:constant('E',12): 2.718281828459

math:power(2,10): 1024

math:power(16,.25): 2

math:sqrt(256): 16

math:sin(30 div 57.2957795): 0.500000000103536

math:cos(120 div 57.2957795): -0.500000000414144

math:tan(45 div 57.2957795): 1.0000000003586593

 

set:distinct(exsl:node-set($var1)/a/b/text()):

5 2 8 3

 

set:difference():

<b fn="mick" ln="taylor">5</b>

The main template rule begins with five calls to the exsl:object-type function, which returns a string showing the type of the object passed. This can be useful when debugging stylesheets.

The next group of function calls demonstrate several of EXSLT's math functions. In the first three, the math:constant function gets the values of the named constants to the precision specified in the second parameter. The first call to math:constant above gets pi with 4 digits of precision, and the second gets it with 12 digits of precision; the third gets the natural logarithmic base e to 12 digits of precision. The math:constant function can also accept SQRRT2, LN2, LN10, LOG2E, and SQRT1_2 as its first parameter.

The next two lines call the math:power function, which calculates the first parameter to the power specified by the second. The first call calculates 210, giving us 1024, and the second calculates 161/4, giving us 2. The line after those calculates the square root of 256, giving us 16.

The last three math function calls perform a little trigonometry. Because they expect their arguments in radians, and I think about trigonometry in degrees, I specified arguments in degrees and divided each by 57.2957795 to pass the function the radians figure it wanted. The results show that the sine of 30 degrees is .5, the cosine of 120 is -.5, and the tangent of 45 degrees is 1.0. (Actually, the figures returned by the function calls are a tiny bit off from these, because I didn't pick a particularly precise value to convert the degrees to radians. And remember, XSLT is not the best tool for doing precise math.)

The last two function calls demonstrate EXSLT set manipulation functions. For the first, the stylesheet passes the set:distinct function the set of all text node children of the $var1 variable's b elements. The function returns a list of the nodes with the duplicates (an extra 2 and an extra 8) removed. To see the returned nodes individually, the stylesheet loops through them with an xsl:for-each element, putting a single space after each one.

The set:difference function takes two node set arguments and returns the nodes from the first set that aren't in the second. For the first argument, the stylesheet passes it the b elements from the $var1 variable that have "mick" as their fn attribute value; for the second, the stylesheet passes the b nodes from the same variable that have "jones" as the ln attribute value. (The combination of the xsl:apply-templates element and the stylesheet's second template rule ensure that any elements grabbed by the function get added to the result tree in their entirety.) The only b element from the "mick" set that's not in the "jones" set is the mick taylor one, which displays in the result.

The calls to the set manipulation functions also demonstrate exsl:node-set, an EXSLT function that converts a Result Tree Fragment into a node set. For functions such as set:distinct and set:difference that require a node-set argument, this function is obviously useful.

In an earlier column titled Trees, Temporarily, I described how the idea of Result Tree Fragments, which the exsl:node-set function is designed to work around, will no longer be in our way in XSLT 2.0. This brings up an important point about these extension functions: if XSLT 2.0 is on the way, why bother with these extensions? For one thing, XSLT 2.0 is only on the way; the specification is still a Working Draft. And remember, all the functions demonstrated in this column work in Saxon, Xalan Java, and libxslt, while Saxon remains the only XSLT processor to support XSLT 2.0. If you're designing a production application, you should still think about how an XSLT 2.0 version will look, so that you can migrate with minimal trouble when the time comes. Meanwhile, check out the handy extras that the EXSLT project's influence has inspired in these three free, popular, robust XSLT processors.