In a recent developerWorks column, "Extend XSLT's functionality with EXSLT," Kevin Williams wrote a high-level introduction to the community standard of extensions to XSLT. As he pointed out, the EXSLT extensions make XSLT far more useful in general-purpose data-manipulation tasks. Just to select a few random examples:
- The EXSLT Math module contains trigonometric mathematical functions, which makes it feasible to generate pie charts in SVG.
- The EXSLT Regular Expressions module makes it easier to parse and process user input and other variable data sources.
- The EXSLT Dates and Times module makes it easier to render Web pages with date-sensitive content, or to process data containing date fields.
In these and many other real-life tasks, EXSLT makes using XSLT feasible in a way
that is portable across the many processors that support the standard. In prior
articles, I have already shown how EXSLT functions such as
exsl:object-type are useful
for even the most basic processing tasks. In this article I try to cover a
cross-section of EXSLT's capabilities by solving a couple of simple and practical
problems using EXSLT. I also try to avoid ground I've already covered to introduce
the many useful EXSLT facilities that have not received the attention they
If you are completely unfamiliar with EXSLT, please read the Kevin Williams article first. The resources in that article include pointers to XSLT processors that support EXSLT, which you should install so you can play with the examples and use EXSLT yourself. Some EXSLT extensions can be used with arbitrary processors, using XSLT scripts you can download from the EXSLT site. But not all EXSLT modules are supported in this way, and this approach typically loses you a lot of performance. You may also want to read my other discussions of EXSLT on IBM developerWorks. You'll find pointers to such material in Resources.
Reach for your calendar
Problem: You generate a Web site upon user request on the server using XSLT. On that Web site you would like to display the current date and time, along with a countdown in days before a certain event.
The EXSLT Dates and Times module provides many functions for manipulating dates,
including one for getting the current date, some for performing calculations between
dates, and others for formatting, displaying, and interpreting dates. It also
<date:date-format/> extension element
for customized date parsing and formatting.
Listing 1 demonstrates these facilities by displaying the current date and time, and the number of days until the start of the next month.
Listing 1. Sample code for computing date-specific details (listing1.xslt)
<?xml version="1.0" encoding="utf-8"?> <!-- A --> <xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:date="http://exslt.org/dates-and-times" version="1.0" > <xsl:output method="html"/> <!-- B --> <xsl:variable name="now" select="date:date-time()"/> <xsl:template match="/"> <!-- The rest of the Web site HTML material would go here --> <xsl:call-template name="date-section"/> </xsl:template> <xsl:template name="date-section"> <p>This page was loaded at <xsl:text/> <!-- C --> <xsl:value-of select="concat(date:hour-in-day($now), ':', date:minute-in-hour($now), ':', date:second-in-minute($now))"/> <xsl:text> on </xsl:text> <xsl:value-of select="concat(date:day-in-month($now), ' ', date:month-name($now), ' ', date:year($now))"/> </p> <p> <!-- D --> <xsl:variable name="days-elapsed" select="concat('-P',date:day-in-month($now),'D')"/> <xsl:variable name="one-month-hence" select="date:add($now, 'P1M')"/> <xsl:variable name="next-month-start" select="date:add($one-month-hence, $days-elapsed)"/> <xsl:variable name="seconds" select="date:seconds( date:difference($now, $next-month-start) )"/> <xsl:text>The next month starts in </xsl:text> <xsl:value-of select="$seconds div (3600*24)"/> <xsl:text> days</xsl:text> </p> </xsl:template> </xsl:transform>
I shall discuss each of the lettered sections of the listing in turn.
A: First I declare the namespace for the EXSLT dates/times module:
http://exslt.org/dates-and-times. Be careful to get this
right. I've seen a few people make the mistake of using the specification page of
than the proper namespace, which results in errors.
B: The function
date:date-time returns the current
date and time as a string in ISO-8601 format, for example
C: Here I construct a readable representation of the date and time, using
EXSLT functions that allow me to extract the various components from the date/time
string. I could have done this several ways, including the
date:date-format element, with which you use a format string to specify
the look of the rendered date.
D: Here I use a few computing gymnastics to determine the number of days until
the start of next month. I could have performed this calculation in numerous
ways, some of them simpler, but I picked a sequence that shows off a good selection
of EXSLT functions. First I compute the number of days elapsed in the current month
as an EXSLT duration string. An EXSLT duration represents a duration of time rather
than a period of time. For example,
-P19D which means "a
negative period of 19 days". Durations have signs for arithmetic purposes. For
example, if you add the period
P1D12H to a date/time
representing midnight January 15, 2003, you get noon, January 15. If you add the
-P1D12H, you get noon, January 14 instead. Such
calculation can be performed using the
which takes a date/time point and a duration.
Next in the listing I compute the date/time point one month from the present using
date:add. In the case where
2003-01-20T03:16:36Z, the result is
2003-02-20T03:16:36Z. Then I count back from that
date the number of days that have elapsed, which gives me the first date of the next
month -- for example,
2003-02-01T03:16:36Z. The function
date:difference takes two date/time points and
returns the duration between them. I use this to compute the duration until the next
month and then the number of seconds in that duration -- for example,
1036800. Finally, I use integer division by (3600*24), to
determine the number of days until the next month.
The output of this script is as follows:
<p xmlns:date="http://exslt.org/dates-and-times">This page was loaded at 21:16:36 on 19 January 2003</p> <p xmlns:date="http://exslt.org/dates-and-times">The next month starts in 12 days</p>
Note that the above output appears on a single line but has been split to multiple lines for display purposes.
Quick and dirty database operations
Problem: You have a bunch of records that you manage in an XML file, and you would like people to be able to use general queries to retrieve particular portions of this data. In other words, you want to be able to access the XML as a quick and dirty database while processing other XML data.
Take the data file in Listing 2, which represents the departments and employees in an organization.
Listing 2. A data file of employees (employees.xml)
<?xml version="1.0" encoding="utf-8"?> <employees> <department title="Research"> <employee title="Coordinator"> <name> <given>Rene</given> <family>Descartes</family> </name> </employee> <employee title="Project Manager"> <name> <given>Abu</given> <family>Al Kwarizmi</family> </name> </employee> </department> <department title="Executive"> <employee title="Chief Executive Officer"> <name> <given>Genghis</given> <family>Khan</family> </name> </employee> </department> <department title="Wellness"> <employee title="Manager of Transcendence"> <name> <given>Shakyamuni</given> <family>Buddha</family> </name> </employee> </department> </employees>
Now let's say that we want people to be able to compose memos addressed to people according to queries on this data file. Listing 3 is an example of such a memo.
Listing 3. Memo addressed according to an employee data file query (memo.xml)
<?xml version='1.0' encoding='utf-8'?> <memo> <title>With Usura Hath no Man a House of Good Stone</title> <date>2003-01-14</date> <to> <employee-query query="/employees/department[@title='Executive']/employee"/> </to> <body> It appears the art world requires a reminder of the fact that the best art is created for the enjoyment of the first buyer, and not as as a mere investment. As I've said before, none of the work of Duccio, Piero Della Francesca, Pietro Lombardo, Fra Angelico, Zuan Bellini or such others would have been of any value if guided by usurious motives. --EP </body> </memo>
to element contains an
employee-query element, which should be replaced by the result of the
query it specifies on the employee data file. Listing 4 is a
small XSLT file that demonstrates how this process would work by displaying the
actual names of the recipients of the memo.
Listing 4. XSLT file that displays memo recipients (listing4.xslt)
<?xml version="1.0" encoding="utf-8"?> <!-- A --> <xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:dyn="http://exslt.org/dynamic" version="1.0" > <xsl:output method="text"/> <xsl:template match="/"> <!-- Jump right to the to element --> <xsl:apply-templates select="/memo/to"/> </xsl:template> <xsl:template match="to"> <xsl:apply-templates/> </xsl:template> <!-- B --> <xsl:template match="employee-query"> <xsl:variable name="db" select="document('employees.xml')"/> <xsl:variable name="query" select="@query"/> <xsl:for-each select="$db"> <xsl:variable name="recipients" select="dyn:evaluate($query)"/> <xsl:apply-templates select="$recipients"/> </xsl:for-each> </xsl:template> <xsl:template match="employee"> <xsl:value-of select="name/given"/> <xsl:text> </xsl:text> <xsl:value-of select="name/family"/> <xsl:if test="not(position()=last())"> <xsl:text>, </xsl:text> </xsl:if> </xsl:template> </xsl:transform>
A: Notice the different namespace that I use to set up the EXSLT module:
http://exslt.org/dynamic. This module contains several
functions that process strings as XPaths in real-time, and in some cases perform
aggregate calculations based on such strings.
B: This template is where the real action takes place. First I read in the
data file as secondary input. Then I store the query string in a variable. I have to
do this because soon I'll have to shift the context so that the attribute in which
this string appears is no longer readily available. The
xsl:for-each element actually makes the context shift to the data file.
dyn:evaluate function dynamically evaluates the
query string against this context. The resulting node set is returned as if from a
regular XPath expression evaluation. XSLT 1.0 has no way to evaluate a string
dynamically as an XPath expression, but by using EXSLT I gain this capability. In
the rest of the listing, conventional XSLT operations are used to print the names
that come from the elements in the query result.
EXSLT includes tools for making things easier and others for making things possible
in the first place. For example, the
function is impossible to replace in XSLT but
dyn:evaluate() can usually be replaced by a system, whereas you use XSLT
to generate another XSLT script which is what you actually run to get the desired
result. EXSLT includes much more than I can hope to cover in future articles, but
one nice thing about EXSLT is that it is very well documented. You'll find a world
of riches to improve your XSLT development by checking out the Web site and taking
advantage of this community standard.
- The EXSLT home page is the authoritative source for information on the project. Each of the modules, elements, and functions are detailed on that site.
- I also cover EXSLT in other articles in the developerWorks XML zone. In the "Introspector Gadget" section of "Debug XSLT on the fly" I introduce
- The W3C's XSL page offers many useful links to XSLT-related resources, including the specifications themselves, tutorials, articles, and implementations.
- Be sure to bookmark "XSL Frequently Asked Questions," which features many notes that are handy for debugging.
- The style sheet processor used in these examples is 4XSLT, part of 4Suite, which is co-developed by the author.
- Find other articles in Kevin William's XML for Data column.
- Find more XML resources on the developerWorks XML zone.