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 deserve.
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.
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 provides a
<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 the module,
http://www.exslt.org/date/index.html, rather 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 period
-P1D12H, you get noon, January 14 instead. Such calculation can be performed using the
date:add function, 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.
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. Then
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
date:date-time 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.
- If you are not familiar with XSLT, read "Investigating XSLT: The XML transformation language," by LindaMay Patterson, for a good introduction (developerWorks, August 2001).
- 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. Also, find information on contributing your own additions to EXSLT, if you'd like. For discussion of EXSLT, join the EXSLT mailing list.
- "Extend XSLT's functionality with EXSLT," by Kevin Williams, offers an overview of EXSLT (developerWorks, December 2002). "Extensions to XSLT," by Leigh Dodds introduces EXSLT.
- I also cover EXSLT in other articles in the developerWorks XML zone. In "Multi-pass XSLT," I introduce
exsl:node-set(September 2002). 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.
- IBM WebSphere Studio provides a suite of tools that automate XML development, both in Java and in other languages. It is closely integrated with the WebSphere Application Server, but can also be used with other J2EE servers.
Uche Ogbuji is a consultant and co-founder of Fourthought Inc., a software vendor and consultancy specializing in XML solutions for enterprise knowledge management. Fourthought develops 4Suite, an open source platform for XML, RDF, and knowledge-management applications. Mr. Ogbuji is a computer engineer and writer born in Nigeria, living and working in Boulder, Colorado, USA. You can contact Mr. Ogbuji at firstname.lastname@example.org.