The news from the XSLT front of late has been very good, with the release of the XSLT 2.0 standard, XPath 2 and XQuery 1 - and I just found another hopeful sign in this post from Mozilla’s Jonas Sicking:
We now have code checked in to support some parts of EXSLT
These functions will be supported in the upcoming Firefox 3
release (sorry, no chance of backporting to earlier releases)exsl:node-set
exsl:object-type
regexp:test
regexp:match
regexp:replace
set:difference
set:distinct
set:intersection
set:distinct
set:has-same-node
set:leading
set:trailing
str:tokenize
str:concat
str:split
math:min
math:max
math:highest
math:lowest
Mozilla Firefox has definitely become my favorite browser, but I have to admit that I’ve long been frustrated that the browser was hampered with such a limited XSLT processor. Perhaps the chief complaint I’ve had comes from the lack of the node-set() method in XSLT’s XPath. Node-set() isn’t in XSLT 1 and it isn’t in XSLT 2, but for completely different reasons.
In XSLT1, there was a fundamental notion that XSLT should be completely side-effect free, to the extent that you couldn’t create intermediate XML to be processed by other templates, but instead had to live with “XML Fragments”. In XSLT2, the underlying data model was revamped to the extent, including the introduction of sequences (more about that in an upcoming post) and one immediate consequence of this was that you could get intermediate XML creation largely for free.
node-set() is an evolutionary step between 1 and 2, however, and was perhaps one of the biggest driving factors in the establishment of the EXSLT library. The nodeset method takes a string representation of a well-formed XML fragment and converts it into a nodeset that can then be assigned to a variable or processed in an apply-templates or for-each statement. This lack was realized early on by Microsoft in their browser, and the Saxon 6.x libraries included an equivalent statement, to the extent that by the early years of this decade the node-set() function was considered an “unofficial” but established XPath method.
The XSLT library in Mozilla, unfortunately, was based upon the early Transformiix library, which didn’t in fact support this capability, and there has long been a curious reticence on the part of the Mozilla development team to dig into the processor to upgrade it even to EXSLT recommendations. This has had the effect of keeping all but the simplest transformations off of the Mozilla platform, a pity given the otherwise superb XML support. As a point of note, the Safari and KDE Konqueror XSLT processors are both built upon Daniel Veillard’s superb libXSLT engine, which was a key participant in the EXSLT recommendations in the first place. (I believe that Opera’s XSLT processor is native to that particular product, but I don’t know for sure - ping me if you know, as I’d like to find out).
While not as elegant as XSLT 2, support for node-set() will make a key difference in what can be done in Mozilla, largely because it makes it possible to more efficiently pipeline transformation processes so that they stay within the context of a single transformation by permitting intermediate processing inline. This is especially true when combined with the str:tokenize() method, which, when passed a string and a parameter, generates a node-set of <token> elements that contain the items of the parsed string.
For instance, suppose that you had a calendar with the input being two sets of strings, being the comma delimited lists of the long and short (three letter) names of months. The following code should work, but doesn’t:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:str="http://exslt.org/strings"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="str exsl"
>
<xsl:output method="xml" media-type="text/xhtml" indent="yes" omit-xml-declaration="yes"/>
<xsl:param name="monthLongNames"
select="'January,February,March,April,May,June,July,August,September,October,November,December'"/>
<xsl:param name="monthShortNames"
select="'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'"/>
<xsl:variable name="months">
<xsl:variable name="longNames" select="str:tokenize($monthLongNames,',')"/>
<xsl:variable name="shortNames" select="str:tokenize($monthShortNames,',')"/>
<xsl:for-each select="$longNames">
<xsl:variable name="pos" select="position()"/>
<month number="{position()}" shortName="{$shortNames[ position() = $pos]}"
longName="{.}"/>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<months><xsl:copy-of select="exsl:node-set($months)/*[position() =5]"/></months>
<html>
<head>
<title>node-set() Test</title>
</head>
<body>
<table border="1">
<tr>
<xsl:for-each select="$months/*">
<xsl:variable name="month" select="."/>
<th><xsl:value-of select="$month/@shortName"/></th>
</xsl:for-each>
</tr>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
The reason for this is in the statement:
<xsl:for-each select="$months/*">
In this case, the pure XSLT 1.0 processor sees $months as containing an XML Fragment, not a collection of nodes, and so the statement $months/* is meaningless. However, using the exsl:node-set() function you can easily fix this particular transformation:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:str="http://exslt.org/strings"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="str exsl"
>
<xsl:output method="xml" media-type="text/xhtml" indent="yes" omit-xml-declaration="yes"/>
<xsl:param name="monthLongNames"
select="'January,February,March,April,May,June,July,August,September,October,November,December'"/>
<xsl:param name="monthShortNames"
select="'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'"/>
<xsl:variable name="months">
<xsl:variable name="longNames" select="str:tokenize($monthLongNames,',')"/>
<xsl:variable name="shortNames" select="str:tokenize($monthShortNames,',')"/>
<xsl:for-each select="$longNames">
<xsl:variable name="pos" select="position()"/>
<month number="{position()}" shortName="{$shortNames[ position() = $pos]}"
longName="{.}"/>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<html>
<head>
<title>node-set() Test</title>
</head>
<body>
<table border="1">
<tr>
<xsl:for-each select="exsl:node-set($months)/*">
<xsl:variable name="month" select="."/>
<th><xsl:value-of select="$month/@shortName"/></th>
</xsl:for-each>
</tr>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
which generates the expected output:
<html>
<head>
<title>node-set() Test</title>
</head>
<body>
<table border="1">
<tr>
<th>Jan</th>
<th>Feb</th>
<th>Mar</th>
<th>Apr</th>
<th>May</th>
<th>Jun</th>
<th>Jul</th>
<th>Aug</th>
<th>Sep</th>
<th>Oct</th>
<th>Nov</th>
<th>Dec</th>
</tr>
</table>
</body>
</html>
The other functions, especially the regular expression commands regexp:test,regexp:match and
,regexp:replace, are a nice bonus feature, and perhaps with enough feedback it might be possible to see the rest of the EXSLT library end up in the Mozilla 3.0 trunk by the time it debuts this summer, but I have to admit to being very happy with the announcement on node-set() - it will significantly broaden the transformative aspect of XSLT in Firefox.
Kurt Cagle is a software consultant, analyst, and author specializing in XML, AJAX and web technologies. He currently lives in Victoria, BC, Canada, which is experiencing its first real sunshine since October.


I should be excited but I'm not. Why doesn't Mozilla work on supporting XSLT 2 before they work on extensions to the language? That's almost like IE improving CSS support but still won't support a <q /> element. I'd (by far) prefer to see a standard implemented further, before extensions are worked on.... even if the extensions are a sort of standard.
Devon,
If it's any consolation, I agree with you whole-heartedly here, but given the comparative indifference that I encountered in the past to upgrading the XSLT processor, even this step is not insignificant. The challenge right now is to find someone willing to take on the burden of implementing an XSLT 2.0 processor on the Mozilla stack, as such a processor would definitely need to be reimplemented from scratch (the underlying data model that Transformiix used wouldn't support it).
Devon: out of you curiosity: have you written XSLT transforms? (In both versions of XSLT?) Do you know the relative merits of the standards?
Personally I think the Mozilla decision is eminently sensible. EXSLT mostly consists of extensions that retrofit almost everything that you'd want from XSLT 2.0 on XSLT 1.0, so it's not like the Mozilla folk are spending time on frills while ignoring crucial functionality. XSLT 2.0 suffers from massive Second System complexity. Even Microsoft declined to implement it, not least since noone actually cares about it. And that's because XSLT 1.0 is Good Enough for most needs; and XSLT 1.0 + EXSLT is Good Enough And Then Some for just about everything.
Bigger version numbers don't mean anything by themselves.
Kurt: I have to say I'm disappointed that they elected to skip the date functions. :-( I hope they just postponed implementation for those, rather than choosing not to support them at all.
However, it remains excellent news nonetheless. EXSLT's string processing facilities in particular have made me reluctant to restrict myself to plain XSLT 1.0, and of course exsl:node-set is indispensable.
The date functions would have been nice, but they also open up a whole can of implementation worms.
I would also concur about the decision. Right now the primary goal in development is getting the committed features of 3.0 done on the promised timeline; the EXSLT extensions are a nice-to-have, especially if you're doing heavy AJAX based work and would like a more solid XSLT processor on the client, but I don't see any real benefit at this stage for them to implement XSLT 2.0 (not that I wouldn't like to see it, mind you).
The only point I don't agree on is XSLT2 itself. I find that the change in model makes a radical difference for the better wrt complexity and verbosity in transformations. 60-70% less code for the same effect, a consistent extension mechanism, a more rational data model. However, I also suspect that if history is any guide, XSLT2 will first find its niche on the server and only over time will it migrate to the client.
Besides date:* functions, I will also miss str:encode-uri which I find mandatory :-(
Are Firefox, Opera, or Safari planning any support for the equivalent of the XPath 2.0 document-uri() or base-uri() functions inside XSLT prior to adopting XSLT 2.0?
Does libxslt support using func:script?
The ability to get the browser url including parameters using xsl:script internal to the XSLT 1.0 transformation inside IE is very powerful.
I use the parameters from the url to support persistent style sheets and to indicate the "page" to display for a given XML that represents in some sense several pages.
Also, is Safari 3.0 planning support for XSLT transformation inside javscript? I understand this to be the case but have not seen it confirmed.
Thank you.
Hey Kurt,
Your Blog rocks!! Just wanted to share something with ya... one blogger to another...
There is this amazing site that I came across where u can make money by sharing information...check it out here's the link http://www.myndnet.com/login.jsp?referral=alpa83&channel=al266
The coolest part is...every time ur information gets sold u get paid for it!!
I signed it for it.. very cool stuff... u can also mail me at barot.alpa@gmail.com
Cheers!
Alpa
Thanks for your great site! Visit my sites, please:
Thanks for your great site! Visit my sites, please:
Testing this out
I must admit it is an awesome site! Thanks for such a great site!! Pretty informative. It is marvelous!!! Have a nice time, on1313 mpjnym.