EXSLT: Enhancing the Power of XSLT

 

Dare Obasanjo
Microsoft Corporation

May 21, 2003

Summary: Dare Obasanjo extends the XSLT support provided through the .NET Framework by implementing an extension library containing over 60 functions that add support for features such as regular expressions, set based operations, and date-time processing to XSLT. (27 printed pages)

Download the XML05192003_sample.exe sample file.

Every Journey Begins With a Single Step

Like many developers who use XSLT, I am often torn between admiration of its capabilities as a powerful tool for processing XML and frustration at some of the limitations of the language. My common complaints about XSLT tend to revolve around the lack of features aimed at more sophisticated processing of XML data besides reshaping one XML tree structure to another. These complaints have been echoed by other developers on various discussion forums devoted to XSLT. The features often cited as missing are the ability to better process the text content of XML documents using technologies such as regular expressions, the lack of support for dates or times, and the lack of set based operations in a language so centrally focused on sets (node sets that is).

One day, after having spent a frustrating amount of time trying to strip out date information from some XML and then sorting elements based on their date, I took my frustrations to Mark and Arpan, the program managers responsible for XSLT in the .NET Framework. Mark responded with a telling statement, "Our XSLT framework is designed to be extensible. If you don't like the base XSLT functions, just add the functionality yourself." This was the last thing I expected to hear, but after some careful consideration I decided to take him up on his challenge and see if I could provide all the functionality I considered missing from XSLT with user-defined extension functions.

The first thing I did was search for any prior attempts to extend XSLT in such an ambitious manner. My search led me to the EXSLT community inititiative and thus began my journey.

Choosing the Right Tool for the Job

The System.Xml.Xsl.XslTransform class provides two primary mechanisms for creating user-defined functions. XSLT stylesheets can contain functions written in C#, Visual Basic .NET or JScript.NET within msxsl:script elements, which can then be invoked from within the stylesheet just as if they were regular XSLT functions. Another approach is to use XSLT extension objects. Extension objects are regular objects whose public methods are accessible from a stylesheet once the objects are added to the XslTransform class through the AddExtensionObject() method.

I decided to go with extension objects instead of using embedded script because this decoupled my extension functions from a particular stylesheet and enabled me to reuse the objects in other programs as needed. To utilize the EXSLT extension objects in a stylesheet, one should add them to the XslTransform object that was loaded with a stylesheet using functions from the EXSLT namespace prior to actual transformation. The following code fragment shows how to use the EXSLT extension objects during transformation:

   //Create the XslTransform and load the stylesheet.
    XslTransform xslt = new XslTransform();
    xslt.Load("test.xsl");

    //Load the XML data file.
    XPathDocument doc = new XPathDocument("test.xml");

    //Create an XsltArgumentList and add the EXSLT object for the math functions
    XsltArgumentList xslArg = new XsltArgumentList();
    xslArg.AddExtensionObject("http://exslt.org/math", new ExsltMath()); 

    //Create an XmlTextWriter to output to the console.             
    XmlTextWriter writer = new XmlTextWriter(Console.Out);

    //Transform the file.
    xslt.Transform(doc, xslArg, writer, null);
    writer.Close();

To make using my extension functions as unobtrusive as possible, I decided to create an ExsltTransform class as a wrapper around the XslTransform class. The ExsltTransform class has the same methods and properties as the XslTransform class and adds a property called SupportedFunctions that controls which extension functions are visible to the stylesheet used for transformation by an instance of the class. The default value of the SupportedFunctions property is ExsltFunctionNamespace.All, which indicates that all the extension functions in my library are visible to the stylesheet.

Below is a sample application showing usage of the ExsltTransform class:

using System.IO; 
using System.Xml.XPath;
using System.Xml.Xsl; 
using System.Xml;
using Exslt; 
using System;

public class Test{

  private const String filename = "test.xml";
  private const String stylesheet = "test.xsl";

  public static void Main(string[] args){

    try{

      //Create the XslTransform and load the stylesheet.
      ExsltTransform exslt = new ExsltTransform();
      exslt.Load(stylesheet);

      //Load the XML data file.
      XPathDocument doc = new XPathDocument(filename);

      //Create an XsltArgumentList.
      XsltArgumentList xslArg = new XsltArgumentList();
         
      //Create an XmlTextWriter to output to the console.    
      XmlTextWriter writer = new XmlTextWriter(Console.Out);

      //Transform the file.
      exslt.Transform(doc, xslArg, writer);
      writer.Close();

     }catch(Exception e){
      Console.WriteLine("UNEXPECTED ERROR: " + e.ToString());
    }
  }
}

So, given the following stylesheet:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0"
     xmlns:set="http://exslt.org/sets">
<xsl:template match="/">
   <out>
      <all-countries>
:
             <xsl:for-each select="//@country">
            <xsl:value-of select="." />
;
    
         </xsl:for-each>
      </all-countries>
:
        <distinct-countries>
:  
             <xsl:for-each select="set:distinct(//@country)">
            <xsl:value-of select="." />
;
    
         </xsl:for-each>
      </distinct-countries>
   </out>
</xsl:template>
</xsl:stylesheet>

And the following XML document as input:

<doc>
   <city name="Paris"
         country="France" />
   <city name="Madrid"
         country="Spain" />
   <city name="Vienna"
         country="Austria" />
   <city name="Barcelona"
         country="Spain" />
   <city name="Salzburg"
         country="Austria" />
   <city name="Bonn"
         country="Germany" />
   <city name="Lyon"
         country="France" />
   <city name="Hannover"
         country="Germany" />
   <city name="Calais"
         country="France" />
   <city name="Berlin"
         country="Germany" />
</doc>

We end up with the output below (this example was taken from the EXSLT Web site):

<out xmlns:set="http://exslt.org/sets">
   <all-countries>:
    France;
    Spain;
    Austria;
    Spain;
    Austria;
    Germany;
    France;
    Germany;
    France;
    Germany;
    </all-countries>
:
     <distinct-countries>:  
    France;
    Spain;
    Austria;
    Germany;
    </distinct-countries>
</out>

Note   To compile the application one needs to include a reference to the Exslt assembly as shown below

csc /r:Exslt.dll Test.cs 

One thing to note about creating extension functions is that XSLT was designed to be a functional programming language, meaning that function calls are not intended to have side effects. This means that to preserve the semantics of XSLT, one should avoid creating extension functions that cause side effects by modifying the state of either the arguments passed to it or other external objects. A good rule of thumb in designing XSLT extension functions is that the same input should always produce the same output.

Imitation Is the Sincerest Form of Flattery

The EXSLT initiative specifies eight modules of extension functions to XSLT. The modules are listed below with a brief description of each.

  • Common: covers common, basic extension elements and functions.
  • Math: covers extension elements and functions that provide facilities having to do with math.
  • Sets: covers those extension elements and functions that provide facilities having to do with set manipulation.
  • Functions: extension elements and functions that allow users to define their own functions for use in expressions and patterns in XSLT.
  • Dates and Times: covers date and time-related extension elements and functions.
  • Strings: covers extension elements and functions that provide facilities to do with string manipulation.
  • Regular Expressions: covers extension elements and functions that provide facilities to do with regular expressions.
  • Dynamic: covers extension elements and functions that deal with the dynamic evaluation of strings containing XPath expressions.

I decided to implement all but two modules; Dynamic and Functions. I didn't implement the Functions module because it only defines extension elements, but no extension functions. The Dynamic module was unfeasible to implement as extension objects because it requires the functions to have access to the XSLT environment's context to work correctly.

Although my function library uses the same function names and namespace names as those from the EXSLT community initiative, there are some significant differences between my functions and those described in the EXSLT modules. One major difference is that functions with hyphenated names, such as add-duration(), are invoked as addduration() due to the fact that function names used in the stylesheet are taken from method names in the extension object, and hyphenated method names are not allowed in C#. Additionally, in some cases I added functions that I considered necessary to processing XML through XSLT that do not exist in the EXSLT documentation. Examples of such functions are the lowercase() and uppercase() functions that I added to the Strings module because I grew tired of using the more verbose and potentially internationalization unfriendly translate() hack.

If At First You Don't Succeed

What I encountered at first seemed like a significant problem when implementing my version of the EXSLT modules. Certain functions that returned node sets, such as highest() and intersection(), threw exceptions when invoked from within the XSLT stylesheet. The specific exception message was:

System.NotSupportedException: ExsltNodeIterator is an unsupported type.
at System.Xml.XPath.XsltFunction.InvokeFunction(XPathNavigator qy)

I realized that this was due toan acknowledged bug in the NET Framework XSLT implementation. It seemed that due to some oversight the documentation stated that any implementation of the abstract XPathNodeIterator class class can be used to represent a node set by external functions, but the XslTransform class only accepts a specific implementation of this class, ResetableIterator, which is internal to the .NET Framework. This is problematic for functions that accept node sets as input and return a subset of the input nodes, such as the highest() and intersection() functions.

I brainstormed for solutions with some of our friendly neighborhood XSLT gurus and eventually a solution presented itself. Oleg Tkachenko (who will be the guest author for the June column) outlined his solution along with the various approaches that we came up with along the way in his weblog post on returning nodesets from XSLT extension functions. It turns out that there is an internal XPathArrayIterator class in the System.Xml namespace that extends the ResetableIterator class and accepts an ArrayList of XPathNavigators in its constructor. Thus, returning a nodeset from an extension function is a matter of using the following code:

   Assembly systemXml = typeof(XPathNodeIterator).Assembly; 
         Type arrayIteratorType = 
         systemXml.GetType("System.Xml.XPath.XPathArrayIterator"); 

         return (XPathNodeIterator) Activator.CreateInstance
            ( arrayIteratorType, 
            BindingFlags.Instance | BindingFlags.Public | 
            BindingFlags.CreateInstance, null, new object[]
            { arrayListOfNavigators}, null ); 

The one caveat is that since this code uses reflection, the extension objects can only be used in fully trusted environments.

Variety Is the Spice of Life

Once I implemented the EXSLT functions, I noticed that certain stylesheets that used to take 50–100 lines to perform a task could be reduced by half. While writing this article I stumbled upon a telling example that shows how using the EXSLT function library can significantly reduce the size and complexity of an XSLT stylesheet. In a recent post on his weblog, Kirk Allen Evans provided a stylesheet that converts a list of comma separated values into an XML document using traditional XSLT. The stylesheet is fairly complex and over 70 lines long. Oleg Tkachenko posted another version of the stylesheet that performed the same task using EXSLT functions and was less than 20 lines long, and also a lot more readable to boot. Having access to the EXSLT function library definitely makes one a more productive XSLT user.

Of course, the real savior here is the extensibility built into the XslTransform class. I can't wait to explore the limits of my newfound power. Perhaps my next project will be writing a library of Web savvy extension functions that can be used to send and retrieve XML documents from XML Web service end points. The ability to join information from local XML data sources with data retrieved from specific Web services is particularly enticing.

Below are sample stylesheets with input and output documents that demonstrate how my implementation of the EXSLT function library is expected to work.

Common Functions

Stylesheet

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0" xmlns:exsl="http://exslt.org/common"
    exclude-result-prefixes="exsl" >

<xsl:variable name="tree">
   <a>
      <b>
         <c>
            <d />
         </c>
      </b>
   </a>
</xsl:variable>

<xsl:variable name="string"
              select="'fred'" />
<xsl:variable name="number"
              select="93.7" />
<xsl:variable name="boolean"
              select="true()" />
<xsl:variable name="node-set"
              select="//*" />


<xsl:template match="/">
   <out>
:
          <xsl:value-of select="exsl:objecttype($string)" />
;
          <xsl:value-of select="exsl:objecttype($number)" />
;
          <xsl:value-of select="exsl:objecttype($boolean)" />
;
          <xsl:value-of select="exsl:objecttype($node-set)" />
;
          <xsl:value-of select="exsl:objecttype($tree)" />
;   
</out>
</xsl:template>
</xsl:stylesheet>

Input

<Root />

Output

<out>
:
          string
;
          number
;
          boolean
;
          node-set
;
          RTF
;   
</out>

Date Functions

Stylesheet

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0"      xmlns:date="http://exslt.org/dates-and-times"
    exclude-result-prefixes="date" >

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

<xsl:template match="/">
   <out>
    <parsedate>
          <xsl:value-of select="date:parsedate('2000-08-17T16:32:32', 
             's')" />
    </parsedate>   
    <difference>
          <xsl:value-of select="date:difference('2000-08-17T16:32:32', 
             '2002-07-17T09:19:32')" /> 
    </difference>
    <add>
          <xsl:value-of select="date:add('2000-08-17T16:32:32', 
             'P1Y2MT2H')" />  
    </add>
    <addduration>
          <xsl:value-of select="date:addduration('P4Y8MT7H', 'P1Y2MT2H')" />  
     </addduration>
     <sum>
          <xsl:value-of select="date:sum(//duration)" />
     </sum>
     <seconds-now>
          <xsl:value-of select="date:seconds()" />
     </seconds-now>
     <seconds-then>
          <xsl:value-of select="date:seconds('2000-08-17T16:32:32')" />
     </seconds-then>
     <seconds-2hours>
          <xsl:value-of select="date:seconds('P0Y0MT2H')" />  
     </seconds-2hours>
     <duration>
          <xsl:value-of select="date:duration('36000')" />  
     </duration>
     <date>
       <xsl:value-of select="date:date('2000-08-17T16:32:32')" />
     </date>
     <date-today>
       <xsl:value-of select="date:date()" />
     </date-today>
     <datetime-now>
       <xsl:value-of select="date:datetime()" />
     </datetime-now>
      <datetime-newyearsday>
       <xsl:value-of select="date:datetime('01/01/2003 16:32:32')" />
     </datetime-newyearsday>
     <dayabbreviation-today>
       <xsl:value-of select="date:dayabbreviation()" />
     </dayabbreviation-today>
      <dayabbreviation-newyearsday>
       <xsl:value-of select="date:dayabbreviation('2003-1-1')" />
     </dayabbreviation-newyearsday>    
      <dayinweek-today>
       <xsl:value-of select="date:dayinweek()" />
     </dayinweek-today>
      <dayinweek-newyearsday>
       <xsl:value-of select="date:dayinweek('2003-1-1')" />
     </dayinweek-newyearsday>
       <dayinmonth-today>
       <xsl:value-of select="date:dayinmonth()" />
     </dayinmonth-today>
      <dayinmonth-newyearsday>
       <xsl:value-of select="date:dayinmonth('2003-1-1')" />
     </dayinmonth-newyearsday>
      <dayinyear-today>
       <xsl:value-of select="date:dayinyear()" />
     </dayinyear-today>
      <dayinyear-newyearsday>
       <xsl:value-of select="date:dayinyear('2003-1-1')" />
     </dayinyear-newyearsday>
      <dayname-today>
       <xsl:value-of select="date:dayname()" />
     </dayname-today>
      <dayname-newyearsday>
       <xsl:value-of select="date:dayname('2003-1-1')" />
     </dayname-newyearsday> 
      <dayofweekinmonth-today>
       <xsl:value-of select="date:dayofweekinmonth()" />
     </dayofweekinmonth-today>
      <dayofweekinmonth-newyearsday>
       <xsl:value-of select="date:dayofweekinmonth('2003-1-1')" />
     </dayofweekinmonth-newyearsday> 
      <hourinday-now>
       <xsl:value-of select="date:hourinday()" />
     </hourinday-now>
      <hourinday-noon>
       <xsl:value-of select="date:hourinday('2003-1-1T12:00:00')" />
     </hourinday-noon>
      <minuteinhour-now>
       <xsl:value-of select="date:minuteinhour()" />
     </minuteinhour-now>
      <minuteinhour-noon>
       <xsl:value-of select="date:minuteinhour('2003-1-1T12:00:00')" />
     </minuteinhour-noon>  
      <leapyear-today>
       <xsl:value-of select="date:leapyear()" />
     </leapyear-today>
      <leapyear-2000>
       <xsl:value-of select="date:leapyear('2000-1-1')" />
     </leapyear-2000> 
      <monthabbreviation-today>
       <xsl:value-of select="date:monthabbreviation()" />
     </monthabbreviation-today>
      <monthabbreviation-newyearsday>
       <xsl:value-of select="date:monthabbreviation('2003-1-1')" />
     </monthabbreviation-newyearsday>
      <monthname-today>
       <xsl:value-of select="date:monthname()" />
     </monthname-today>
      <monthname-newyearsday>
       <xsl:value-of select="date:monthname('2003-1-1')" />
     </monthname-newyearsday>
      <monthinyear-today>
       <xsl:value-of select="date:monthinyear()" />
     </monthinyear-today>
      <monthinyear-newyearsday>
       <xsl:value-of select="date:monthinyear('2003-1-1')" />
     </monthinyear-newyearsday>
      <weekinyear-today>
       <xsl:value-of select="date:weekinyear()" />
     </weekinyear-today>
      <weekinyear-newyearsday>
       <xsl:value-of select="date:weekinyear('2003-1-1')" />
     </weekinyear-newyearsday>      
      <year-today>
       <xsl:value-of select="date:year()" />
     </year-today>
      <year-newyearsday>
       <xsl:value-of select="date:year('2003-1-1')" />
     </year-newyearsday>
       <time-now>
       <xsl:value-of select="date:time()" />
     </time-now>
      <time-noon>
       <xsl:value-of select="date:time('2003-1-1T12:00:00')" />
     </time-noon>
      <xsl:comment>The second parameter to date:formatdate() is one of the 
      format strings understood
      by the ToString(string) method of the System.DateTime 
      class</xsl:comment>
       <formatdate-newyearsday>
       <xsl:value-of select="date:formatdate('2003-1-1', 'D')" />
     </formatdate-newyearsday> 
     <xsl:comment> Functions that return durations normalize the value to 
     days and hours </xsl:comment>
     <avg>
          <xsl:value-of select="date:avg(//duration)" />
     </avg>
     <max>
          <xsl:value-of select="date:max(//duration)" />
     </max>
     <min>
          <xsl:value-of select="date:min(//duration)" />
     </min>
     <xsl:comment>The date functions understand all date formats supported 
     by the System.DateTime class, so we can do clever things like sort 
     dates even when they are in two different formats</xsl:comment>
     <sorted-dates>
      <xsl:for-each select="/dates-and-times/dates/date">
       <xsl:sort select="date:datetime(.)" />
       <xsl:copy-of select="." />
      </xsl:for-each>
     </sorted-dates>
</out>
</xsl:template>
</xsl:stylesheet>

Input

<dates-and-times>
 <durations>
  <duration>P4Y8MT7H </duration>
  <duration>P17Y2MT2H </duration>
  <duration>P1Y2MT2H </duration>
 </durations>
 <dates>
  <date>Mon, 28 Apr 2003 01:45:36 GMT</date>
  <date>Sat, 10 May 2003 11:44:42 GMT</date>
  <date>2003-05-12T18:02:48-05:00</date>
  <date>2003-05-01T13:02:48-05:00</date>
  <date>Sat, 10 May 2003 09:27:23 GMT</date>
  <date>Mon, 21 Apr 2003 05:28:14 GMT</date>
 </dates>
</dates-and-times>

Output

<out>
  <parsedate>2000-08-17T16:32:32.0000000-07:00</parsedate>
  <difference>P698DT16H47M</difference>
  <add>2001-10-16T18:32:32.0000000-07:00</add>
  <addduration>P2125DT9H</addduration>
  <sum>P8390DT11H</sum>
  <seconds-now>1052802449.4564432</seconds-now>
  <seconds-then>966529952</seconds-then>
  <seconds-2hours>7200</seconds-2hours>
  <duration>PT10H</duration>
  <date>2000-08-17</date>
  <date-today>2003-05-13</date-today>
  <datetime-now>2003-05-13T05:07:29</datetime-now>
  <datetime-newyearsday>2003-01-01T16:32:32</datetime-newyearsday>
  <dayabbreviation-today>Tue</dayabbreviation-today>
  <dayabbreviation-newyearsday>Wed</dayabbreviation-newyearsday>
  <dayinweek-today>3</dayinweek-today>
  <dayinweek-newyearsday>4</dayinweek-newyearsday>
  <dayinmonth-today>13</dayinmonth-today>
  <dayinmonth-newyearsday>1</dayinmonth-newyearsday>
  <dayinyear-today>133</dayinyear-today>
  <dayinyear-newyearsday>1</dayinyear-newyearsday>
  <dayname-today>Tuesday</dayname-today>
  <dayname-newyearsday>Wednesday</dayname-newyearsday>
  <dayofweekinmonth-today>2</dayofweekinmonth-today>
  <dayofweekinmonth-newyearsday>1</dayofweekinmonth-newyearsday>
  <hourinday-now>5</hourinday-now>
  <hourinday-noon>12</hourinday-noon>
  <minuteinhour-now>7</minuteinhour-now>
  <minuteinhour-noon>0</minuteinhour-noon>
  <leapyear-today>false</leapyear-today>
  <leapyear-2000>true</leapyear-2000>
  <monthabbreviation-today>May</monthabbreviation-today>
  <monthabbreviation-newyearsday>Jan</monthabbreviation-newyearsday>
  <monthname-today>May</monthname-today>
  <monthname-newyearsday>January</monthname-newyearsday>
  <monthinyear-today>5</monthinyear-today>
  <monthinyear-newyearsday>1</monthinyear-newyearsday>
  <weekinyear-today>20</weekinyear-today>
  <weekinyear-newyearsday>1</weekinyear-newyearsday>
  <year-today>2003</year-today>
  <year-newyearsday>2003</year-newyearsday>
  <time-now>05:07:29</time-now>
  <time-noon>12:00:00</time-noon>
  <!--The second parameter to date:formatdate() is one of the format 
  strings understood  by the ToString(string) method of the 
  System.DateTime class-->
  <formatdate-newyearsday>Wednesday, January 01, 2003</formatdate-newyearsday>
  <!-- Functions that return durations normalize the value to days and hours -->
  <avg>P2796DT19H40M</avg>
  <max>P6265DT2H</max>
  <min>P425DT2H</min>
  <!--The date functions understand all date formats supported by the 
  System.DateTime class, so we can do clever things like sort dates even 
  when they are in two different formats-->
  <sorted-dates>
    <date>Mon, 21 Apr 2003 05:28:14 GMT</date>
    <date>Mon, 28 Apr 2003 01:45:36 GMT</date>
    <date>2003-05-01T13:02:48-05:00</date>
    <date>Sat, 10 May 2003 09:27:23 GMT</date>
    <date>Sat, 10 May 2003 11:44:42 GMT</date>
    <date>2003-05-12T18:02:48-05:00</date>
  </sorted-dates>
</out>

Math Functions

Stylesheet

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0"      xmlns:math="http://exslt.org/math"
    exclude-result-prefixes="math" >

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

<xsl:template match="/">
   <out>
   <avg><xsl:value-of select="math:avg(//number)" /></avg>
   <max><xsl:value-of select="math:max(//number)" /></max>
<min><xsl:value-of select="math:min(//number)" /></min>
   <highest><xsl:copy-of select="math:highest(//number)" /></highest>
   <lowest><xsl:copy-of select="math:lowest(//number)" /></lowest>
   <abs><xsl:value-of select="math:abs(math:min(//number))" /></abs>
   <sqrt><xsl:value-of select="math:sqrt(math:max(//number))" /></sqrt>
   <power><xsl:value-of select="math:power(2,5)" /></power>
   <constant><xsl:value-of select="math:constant('PI',0)" /></constant>
   <log><xsl:value-of select="math:log(10)" /></log>
   <sin><xsl:value-of select="math:sin(10)" /></sin>
   <cos><xsl:value-of select="math:cos(10)" /></cos>
   <tan><xsl:value-of select="math:tan(10)" /></tan>
   <asin><xsl:value-of select="math:asin(1)" /></asin>
   <acos><xsl:value-of select="math:acos(1)" /></acos>
   <atan><xsl:value-of select="math:atan(1)" /></atan>
   <atan2><xsl:value-of select="math:atan2(5,10)" /></atan2>
   <exp><xsl:value-of select="math:exp(2)" /></exp>
</out>
</xsl:template>
</xsl:stylesheet>

Input

<numbers>
  <number id="a" >95</number>
  <number id="b" >1.98</number>
  <number id="c" >100</number>
  <number id="d" >-95</number>
  <number id="e" >100</number>
</numbers>

Output

<out>
  <avg>40.396</avg>
  <max>100</max>
  <min>-95</min>
  <highest>
    <number id="c">100</number>
    <number id="e">100</number>
  </highest>
  <lowest>
    <number id="d">-95</number>
  </lowest>
  <abs>95</abs>
  <sqrt>10</sqrt>
  <power>32</power>
  <constant>3.1415926535897931</constant>
  <log>2.3025850929940459</log>
  <sin>-0.54402111088936977</sin>
  <cos>-0.83907152907645244</cos>
  <tan>0.64836082745908663</tan>
  <asin>1.5707963267948966</asin>
  <acos>0</acos>
  <atan>0.78539816339744828</atan>
  <atan2>0.46364760900080609</atan2>
  <exp>7.38905609893065</exp>
</out>

Regular Expression Functions

Stylesheet

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0"      xmlns:regexp="http://exslt.org/regular-expressions" 
    exclude-result-prefixes="regexp" >

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

<xsl:template match="/">
   <out>
       <test><xsl:value-of select="regexp:test('The quick brown fox', 
          'fox')" /></test>
       <test><xsl:value-of select="regexp:test('The quick brown fox', 
          'Fox')" /></test>
       <test><xsl:value-of select="regexp:test('The quick brown fox', 
          'Fox', 'i')" /></test>
       <tokenize>BEFORE: 1abc234efg56h78i9j0
            AFTER: <xsl:for-each select="regexp:tokenize('1abc234efg56h78i9j0', '[a-j]+')">
     <xsl:value-of select="." />
     <xsl:text>&#160;</xsl:text>
    </xsl:for-each>
   </tokenize>

<match>
    <xsl:for-each select=
    "regexp:match('http://www.bayes.co.uk/xml/index.xml?/xml/utils/rechecker.xml', 
'(\w+):\/\/([^/:]+)(:\d*)?([^# ]*)')">
   Part <xsl:value-of select="position()" /> = <xsl:value-of select="." />
</xsl:for-each> <xsl:text>&#160;</xsl:text>
</match>

<match>
<xsl:for-each select="regexp:match('This is a test string', '(\w+)', 'g')">
   Part <xsl:value-of select="position()" /> = <xsl:value-of select="." />
</xsl:for-each>  <xsl:text>&#160;</xsl:text>
</match>

<match>
<xsl:for-each select="regexp:match('This is a another test string', 
   '([a-z])+ ', 'gi')">
   Part <xsl:value-of select="position()" /> = <xsl:value-of select="." />
</xsl:for-each>  <xsl:text>&#160;</xsl:text>
</match>
</out>
</xsl:template>
</xsl:stylesheet>

Input

<Root />

Output

<out>
  <test>true</test>
  <test>false</test>
  <test>true</test>
  <tokenize>BEFORE: 1abc234efg56h78i9j0
            AFTER: 1 234 56 78 9 0 
  </tokenize>
  <match>
   Part 1 = http://www.bayes.co.uk/xml/index.xml?/xml/utils/rechecker.xml
   Part 2 = http
   Part 3 = www.bayes.co.uk
   Part 4 = 
   Part 5 = /xml/index.xml?/xml/utils/rechecker.xml
</match>
  <match>
   Part 1 = This
   Part 2 = is
   Part 3 = a
   Part 4 = test
   Part 5 = string
</match>
  <match>
   Part 1 = This 
   Part 2 = is 
   Part 3 = a 
   Part 4 = another 
   Part 5 = test 
</match>
</out>

Set Functions

Stylesheet

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0"      xmlns:set="http://exslt.org/sets" 
    exclude-result-prefixes="set" >

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

<xsl:variable name="a" select="/Root/Numbers/Integer/@value"/> 
<xsl:variable name="b" select="/Root/Numbers/Integer/@value[. > 2]"/> 
<xsl:variable name="c" select="/Root/Numbers/Integer/@value[. = 3]"/> 

<xsl:template match="/">
   <out>

 SET A: { <xsl:for-each select="$a"> <xsl:value-of select="." />, 
</xsl:for-each> }
 SET B: { <xsl:for-each select="$b"> <xsl:value-of select="." />, 
</xsl:for-each> }
 SET C: { <xsl:for-each select="$c"> <xsl:value-of select="." />, 
</xsl:for-each> }

  a UNION b:  { <xsl:for-each select="$a | $b"> <xsl:value-of select="." 
/>, </xsl:for-each> }
  b UNION c:  { <xsl:for-each select="$b | $c"> <xsl:value-of select="." 
/>, </xsl:for-each> }
  a INTERSECTION b:  { <xsl:for-each select="set:intersection($a, $b)"> 
<xsl:value-of select="." />, </xsl:for-each> }
  a INTERSECTION c:  { <xsl:for-each select="set:intersection($a, $c)"> 
<xsl:value-of select="." />, </xsl:for-each> }
  a DIFFERENCE b:  { <xsl:for-each select="set:difference($a, $b)"> 
<xsl:value-of select="." />, </xsl:for-each> }
  a DIFFERENCE c:  { <xsl:for-each select="set:difference($a, $c)"> 
<xsl:value-of select="." />, </xsl:for-each> }
  a SUBSET OF b:  { <xsl:value-of select="set:subset($a, $b)"/> }
  b SUBSET OF a:  { <xsl:value-of select="set:subset($b, $a)"/> }

  DISTINCT a:  {  <xsl:for-each select="set:distinct($a)"> <xsl:value-of 
select="." />, </xsl:for-each> }
  DISTINCT b:  {  <xsl:for-each select="set:distinct($b)"> <xsl:value-of 
select="." />, </xsl:for-each> }
  DISTINCT c:  {  <xsl:for-each select="set:distinct($c)"> <xsl:value-of 
select="." />, </xsl:for-each> }

  a HASSAMENODEAS b:  { <xsl:value-of select="set:hassamenode($a, $b)"/> }
  b HASSAMENODEAS c:  { <xsl:value-of select="set:hassamenode($b, $c)"/> }

  a LEADING c  {  <xsl:for-each select="set:leading($a, $c)"> <xsl:value-
of select="." />, </xsl:for-each> }
  a TRAILING c  {  <xsl:for-each select="set:trailing($a, $c)"> 
<xsl:value-of select="." />, </xsl:for-each> }

</out>
</xsl:template>
</xsl:stylesheet>

Input

<Root>
 <Numbers>
  <Integer value="4" />
  <Integer value="2" />
  <Integer value="3" />
 </Numbers>
 <Numbers>
  <Integer value="2" />
  <Integer value="3" />
  <Integer value="6" />
 </Numbers>
</Root>

Output

<out>
 SET A: { 4, 2, 3, 2, 3, 6,  }
 SET B: { 4, 3, 3, 6,  }
 SET C: { 3, 3,  }

  a UNION b:  { 4, 2, 3, 2, 3, 6,  }
  b UNION c:  { 4, 3, 3, 6,  }
  a INTERSECTION b:  { 4, 3, 3, 6,  }
  a INTERSECTION c:  { 3, 3,  }
  a DIFFERENCE b:  { 2, 2,  }
  a DIFFERENCE c:  { 4, 2, 2, 6,  }
  a SUBSET OF b:  { false }
  b SUBSET OF a:  { true }

  DISTINCT a:  {  4, 2, 3, 6,  }
  DISTINCT b:  {  4, 3, 6,  }
  DISTINCT c:  {  3,  }

  a HASSAMENODEAS b:  { true }
  b HASSAMENODEAS c:  { true }

  a LEADING c  {  4, 2,  }
  a TRAILING c  {  2, 3, 6,  }

</out>

String Functions

Stylesheet

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0"      xmlns:str="http://exslt.org/strings" 
    exclude-result-prefixes="str" >

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

<xsl:template match="/">
   <out>
   <tokenize>
      <xsl:for-each select="str:tokenize('2001-06-03T11:40:23', '-T:')">
    <xsl:value-of select="." /><xsl:text>&#160;</xsl:text>
   </xsl:for-each>
   </tokenize>
   <tokenize>
        <xsl:for-each select="str:tokenize('date math str')">
    <xsl:value-of select="." /><xsl:text>&#160;</xsl:text>
   </xsl:for-each>
   </tokenize>
   <replace>
 <xsl:value-of select="str:replace('The quick brown fox likes running 
 around the farm', 'quick brown fox likes','lazy dog hates')" />
</replace>
<padding>a<xsl:value-of select="str:padding(2)" />a</padding>
<padding><xsl:value-of select="str:padding(5,'*')" /></padding>
<lowercase><xsl:value-of select="str:lowercase('XML')" /></lowercase>
<uppercase><xsl:value-of select="str:uppercase('uPPerCaSe')" /></uppercase>
<split>
 <xsl:for-each select="str:split('set    date math  str regexp')"> 
    <xsl:value-of select="." />-</xsl:for-each>
</split>   
<split>
 <xsl:for-each select="str:split('EXSLT', '')"> <xsl:value-of select="." 
    />-</xsl:for-each> 
</split>
<split>
  <xsl:for-each select="str:split('set++date++math++str++regexp', '++')"> 
     <xsl:value-of select="." />    <xsl:text>&#160;</xsl:text> 
</xsl:for-each> 
</split>
<concat>
  <xsl:value-of select="str:concat(/Root/Numbers/Integer/@value)" />
</concat>
</out>
</xsl:template>
</xsl:stylesheet>

Input

<Root>
 <Numbers>
  <Integer value="4" />
  <Integer value="2" />
  <Integer value="3" />
 </Numbers>
 <Numbers>
  <Integer value="2" />
  <Integer value="3" />
  <Integer value="6" />
 </Numbers>
</Root>

Output

<out>
  <tokenize>2001 06 03 11 40 23 </tokenize>
  <tokenize>date math str </tokenize>
  <replace>The lazy dog hates running around the farm</replace>
  <padding>a  a</padding>
  <padding>*****</padding>
  <lowercase>xml</lowercase>
  <uppercase>UPPERCASE</uppercase>
  <split>set-date-math-str-regexp-</split>
  <split>E-X-S-L-T-</split>
  <split>set date math str regexp </split>
  <concat>423236</concat>
</out>

Dare Obasanjo is a member of Microsoft's WebData team, which among other things develops the components within the System.Xml and System.Data namespace of the .NET Framework, Microsoft XML Core Services (MSXML), and Microsoft Data Access Components (MDAC).

Feel free to post any questions or comments about this article on the Extreme XML message board on GotDotNet.