Chapter 16

Extending XSLT

In This Chapter

bullet Discovering what extensions are

bullet Using extension elements

bullet Using extension functions

bullet Making sure your stylesheets are portable

Y ou know how it is. You get a shiny, new digital gizmo that does 1,001 things for you, but not two weeks go by before you discover the 1,002nd task you really need this gizmo to do, really want it to do, but lo and behold, it cannot do this task. This same Murphy’s Law of Gizmos applies to XSLT: The language does 1,001 kinds of transformations, but sooner or later, you encounter a need that XSLT doesn’t provide a solution for as part of its basic language. Perhaps you need to use a while loop inside your template rule. Or maybe you need to compare nodes from two different node sets and are throwing up your arms trying to do it with XSLT alone. Before you X out XSLT, read on.

As with any new technology, there are gaps in what comes “in the box” with XSLT. Fortunately, you don’t have to wait on future versions of the language to solve the problem. Instead, XSLT supports extensions that allow you to expand the scope of what you can do inside an XSLT stylesheet.

In this chapter, you find out how you can broaden your XSLT horizons through extensions.

Going Beyond the Basics

Extensions are modules that the XSLT processor implements and that support additional functionality. They can surface in XSLT as an element, attribute, or function, such as in the following code snippet:

<xsl:template match=”coffees” grd:guard=”true”>

  <grd:guardian select=”@bean”>

  <xsl:apply-templates select=”grd:guardnodes(coffee)”/>

</xsl:template>

The grd:guard=”true” gives an example of what an extension attribute might look like, the grd:guardian element demonstrates an extension element, and grd:guardnodes() represents an extension function.

Remember

If an XSLT processor doesn’t understand the extension, you cannot use it during your transformations.

By their very nature, extensions give you functionality that is outside standard XSLT, meaning their support is closely tied to the XSLT processor you use. In most cases, extensions are proprietary to a given processor and are useless when you attempt to transform the stylesheet with another vendor’s processing engine. However, a new community-led effort called EXSLT (Extensions for XSLT) seeks to provide a common set of XSLT extensions that can be supported across multiple XSLT processors. The obvious advantage in using EXSLT extensions is that, if EXSLT catches on, you’ll be able to use them on any processing engine that implements EXSLT support. SAXON is perhaps the best example of an XSLT processor that has started adding support for EXSLT functions. For the latest information on EXSLT extensions, go to the community-supported Web site (www.exslt.org).

Tip

When you are evaluating XSLT processors, take a close look at the extensions that are provided. Even if you don’t need extensions now, extension support may be important for you in the future as your needs grow.

Tip

Microsoft’s approach to extensions with msxsl is slightly different than the other XSLT processor vendors. Rather than supporting a rich library of extension elements and functions, msxsl has a single extension element called msxsl:script that serves as their gateway for adding scripting (JavaScript or VBScript) into your stylesheets. In effect, this feature of msxsl enables you to write your own extensions by using a script.

Using an Extension Element

Extension elements expand the xsl: built-in element set by providing new instructions inside your stylesheet. Take, for example, SAXON’s while extension. I can use this element to add true conditional looping inside a stylesheet.

Before using an extension element like the while extension in my stylesheet, I must first declare the extension namespace in the xsl:stylesheet element. All SAXON extensions use the following namespace declaration:

xmlns:saxon=”http://icl.com/saxon”

Tip

Although you can use any prefix, I recommend you stick with saxon to conform to the standards of the processor vendor and to minimize any possible confusion.

Second, add the extension-element-prefixes attribute to the xsl:stylesheet element, using saxon as the attribute value. This attribute tells the processing engine what namespace prefixes are reserved for extensions:

<xsl:stylesheet version=”1.0”

  xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”

  xmlns:saxon=”http://icl.com/saxon”

  extension-element-prefixes=”saxon”>

Inside a template rule, the saxon:while element executes a series of instructions so long as the test pattern returns true. In this simple example, I have test evaluate the value of a variable. However, because the value of an XSLT variable normally doesn’t change, I use a second SAXON extension element, saxon:assign, that allows me to assign a new value to this variable. The template rule is shown here:

  <xsl:template match=”/”>

    <xsl:variable name=”idx” saxon:assignable=”yes” select=”1”/>

    <saxon:while test=”$idx &lt; 11”>

      Value of idx is <xsl:value-of select=”$idx”/>

      <saxon:assign name=”idx” select=”$idx+1”/>

    </saxon:while>

  </xsl:template>

In the xsl:variable instruction, notice the saxon:assignable extension attribute. The SAXON processor requires that this attribute be added to any xsl:variable that you intend to change by using saxon:assign.

The output of my looping is as follows:

   The value of idx is 1

   The value of idx is 2

   The value of idx is 3

   The value of idx is 4

   The value of idx is 5

   The value of idx is 6

   The value of idx is 7

   The value of idx is 8

   The value of idx is 9

   The value of idx is 10

Tip

For more information on saxon:while, saxon:assign, and other SAXON extensions, visit the SAXON Web site at saxon.sourceforge.net.

Using an Extension Function

You can also add extension functions to your stylesheet expressions and use the functions in the same way you use XPath and XSLT built-in functions. To demonstrate, suppose I have two node sets and my objective is to produce a report comparing the like and unlike nodes between them. Rather than going through a series of hoops using XSLT alone to do this, EXSLT supports two functions called set:intersection() and set:difference() that make this task a breeze. For this exercise, I create two reports by using the following XML document, Listing 16-1.

Listing 16-1: afifilms.xml

<?xml version=”1.0”?>

<!-- American Film Institute Top 25 Films -->

<topfilms createdby=”AFI”>

  <film place=”1” date=”1941” genre=”Drama”>Citizen Kane</film>

  <film place=”2” date=”1942” genre=”Romantic Drama”>Casablanca</film>

  <film place=”3” date=”1972” genre=”Crime Drama”>The Godfather</film>

  <film place=”4” date=”1939” genre=”Epic Drama”>Gone With The Wind</film>

  <film place=”5” date=”1962” genre=”War”>Lawrence Of Arabia</film>

  <film place=”6” date=”1939” genre=”Fantasy”>The Wizard Of Oz</film>

  <film place=”7” date=”1967” genre=”Drama”>The Graduate</film>

  <film place=”8” date=”1954” genre=”Crime Drama”>On The Waterfront</film>

  <film place=”9” date=”1993” genre=”Epic”>Schindler’s List</film>

  <film place=”10” date=”1952” genre=”Musical”>Singin’ In The Rain</film>

  <film place=”11” date=”1946” genre=”Drama”>It’s A Wonderful Life</film>

  <film place=”12” date=”1950” genre=”Drama”>Sunset Boulevard</film>

  <film place=”13” date=”1957” genre=”War”>The Bridge On The River Kwai</film>

  <film place=”14” date=”1959” genre=”Drama”>Some Like It Hot</film>

  <film place=”15” date=”1977” genre=”Epic Fantasy”>Star 

Wars</film>

  <film place=”16” date=”1950” genre=”Drama”>All About Eve</film>

  <film place=”17” date=”1951” genre=”Romantic Comedy”>The African Queen</film>

  <film place=”18” date=”1960” genre=”Thriller”>Psycho</film>

  <film place=”19” date=”1974” genre=”Thriller”>Chinatown</film>

  <film place=”20” date=”1975” genre=”Drama”>One Flew Over The Cuckoo’s Nest</film>

  <film place=”21” date=”1940” genre=”Drama”>The Grapes Of Wrath</film>

  <film place=”22” date=”1968” genre=”Space”>2001: A Space Odyssey</film>

  <film place=”23” date=”1941” genre=”Crime Drama”>The Maltese Falcon</film>

  <film place=”24” date=”1980” genre=”Ouch”>Raging Bull</film>

  <film place=”25” date=”1982” genre=”Fantasy”>E.T, The Extra-Terrestrial</film>

</topfilms>

In the first report, my objective is to list these films by decade. To create this listing of films, I rely on EXSLT extensions to do much of the work for me. So, in an XSLT stylesheet, my first step is to add the appropriate extension namespace to the xsl:stylesheet element:

<xsl:stylesheet version=”1.0”

  xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”

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

  extension-element-prefixes=”set”>

Because I’m working with multiple node sets, I create several variables, each of which has a node set assigned to it:

  <xsl:variable name=”Post1940” select=”//film[1940&lt;=date]”/>

  <xsl:variable name=”Post1950” select=”//film[1950&lt;=date]”/>

  <xsl:variable name=”Post1960” select=”//film[1960&lt;=date]”/>

  <xsl:variable name=”Post1970” select=”//film[1970&lt;=date]”/>

  <xsl:variable name=”Post1980” select=”//film[1980&lt;=date]”/>

  <xsl:variable name=”Post1990” select=”//film[1990&lt;=date]”/>

  <xsl:variable name=”Pre1940” select=”//film[date&lt;=1939]”/>

  <xsl:variable name=”Pre1950” 

   select=”//film[date&lt;=1949]”/>

  <xsl:variable name=”Pre1960” select=”//film[date&lt;=1959]”/>

  <xsl:variable name=”Pre1970” select=”//film[date&lt;=1969]”/>

  <xsl:variable name=”Pre1980” select=”//film[date&lt;=1979]”/>

  <xsl:variable name=”Pre1990” select=”//film[date&lt;=1989]”/>

  <xsl:variable name=”Pre2000” select=”//film[date&lt;=1999]”/>

Each of these variables represents a node set based on the film element’s date attribute.

Tip

Assigning a node set to a variable can simplify the way you work with the node set in your stylesheet.

In the stylesheet’s template rule, these variables are used to group the films by decade. One way to create this listing is to use the EXSLT set:intersection(nodeset1, nodeset2) function. This function returns the nodes that are common to both node sets provided as the function parameters.

A xsl:for-each instruction loops through each of the nodes returned by its select attribute and performs the instructions contained between its start and end tags. I can use set:intersection() as the value of the select attribute, so that the for-each loop iterates through each of the common nodes and uses xsl:value-of to print out the film element’s content:

   ------------------------------

   1940’s Films: 

    <xsl:for-each select=”set:intersection($Post1940, $Pre1950)”>

     * <xsl:value-of select=”.”/>

    </xsl:for-each>

I create a similar xsl:for-each loop for each of the remaining decades.

The entire stylesheet is shown here:

<xsl:stylesheet version=”1.0”

  xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”

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

  extension-element-prefixes=”set”>

  

  <xsl:output method=”text”/>

  <xsl:variable name=”Post1940” select=”//film[1940&lt;=date]”/>

  <xsl:variable name=”Post1950” 

   select=”//film[1950&lt;=date]”/>

  <xsl:variable name=”Post1960” select=”//film[1960&lt;=date]”/>

  <xsl:variable name=”Post1970” select=”//film[1970&lt;=date]”/>

  <xsl:variable name=”Post1980” select=”//film[1980&lt;=date]”/>

  <xsl:variable name=”Post1990” select=”//film[1990&lt;=date]”/>

  <xsl:variable name=”Pre1940” select=”//film[date&lt;=1939]”/>

  <xsl:variable name=”Pre1950” select=”//film[date&lt;=1949]”/>

  <xsl:variable name=”Pre1960” select=”//film[date&lt;=1959]”/>

  <xsl:variable name=”Pre1970” select=”//film[date&lt;=1969]”/>

  <xsl:variable name=”Pre1980” select=”//film[date&lt;=1979]”/>

  <xsl:variable name=”Pre1990” select=”//film[date&lt;=1989]”/>

  <xsl:variable name=”Pre2000” select=”//film[date&lt;=1999]”/>

  <xsl:template match=”/”>

   ------------------------------

   1930’s Films:

    <xsl:for-each select=”$Pre1940”>

     * <xsl:value-of select=”.”/>

    </xsl:for-each>

   ------------------------------

   1940’s Films: 

    <xsl:for-each select=”set:intersection($Post1940, $Pre1950)”>

     * <xsl:value-of select=”.”/>

    </xsl:for-each>

   ------------------------------

   1950’s Films:

    <xsl:for-each select=”set:intersection($Post1950, $Pre1960)”>

     * <xsl:value-of select=”.”/>

    </xsl:for-each>

   ------------------------------

   1960’s Films:

    <xsl:for-each select=”set:intersection($Post1960, $Pre1970)”>

     * <xsl:value-of select=”.”/>

    </xsl:for-each>

   ------------------------------

   1970’s Films:

    <xsl:for-each select=”set:intersection($Post1970, $Pre1980)”>

     * <xsl:value-of select=”.”/>

    </xsl:for-each>

   ------------------------------

   1980’s Films:

    <xsl:for-each select=”set:intersection($Post1980, $Pre1990)”>

     * <xsl:value-of select=”.”/>

    </xsl:for-each>

   ------------------------------

   1990’s Films:

    <xsl:for-each select=”set:intersection($Post1990, $Pre2000)”>

     * <xsl:value-of select=”.”/>

    </xsl:for-each>

  </xsl:template>

  

  <xsl:template match=”film”/>

</xsl:stylesheet>

The stylesheet is applied to afifilms.xml, shown in Listing 16-1. When this transformation is done by a processor, such as SAXON, that supports set:intersection(), the result is as follows:

   ------------------------------

   1930’s Films:

    

     * Gone With The Wind

     * The Wizard Of Oz

   ------------------------------

   1940’s Films: 

    

     * Citizen Kane

     * Casablanca

     * It’s A Wonderful Life

     * The Grapes Of Wrath

     * The Maltese Falcon

   ------------------------------

   1950’s Films:

    

     * On The Waterfront

     * Singin’ In The Rain

     * Sunset Boulevard

     * The Bridge On The River Kwai

     * Some Like It Hot

     * All About Eve

     * The African Queen

   ------------------------------

   1960’s Films:

    

     * Lawrence Of Arabia

     * The Graduate

     * Psycho

     * 2001: A Space Odyssey

   ------------------------------

   1970’s Films:

    

     * The Godfather

     * Star Wars

     * Chinatown

     * One Flew Over The Cuckoo’s Nest

   ------------------------------

   1980’s Films:

    

     * Raging Bull

     * E.T, The Extra-Terrestrial

   ------------------------------

   1990’s Films:

    

     * Schindler’s List

Now I want to create a second report based on the XML source document — a list of films organized into categories: epic dramas, dramas that are not epics, epics that are not dramas, and dramas that are not romantic. To do so, I define three variables that return node sets based on the value of the genre attribute:

  <xsl:variable name=”DramaFilms” select=”//film[contains(@genre, ‘Drama’)]”/>

  <xsl:variable name=”EpicFilms” select=”//film[contains(@genre, ‘Epic’)]”/>

  <xsl:variable name=”RomanticFilms” select=”//film[contains(@genre, ‘Romantic’)]”/>

DramaFilms returns all the film elements that contain the word Drama in their genre attributes. The other two variables use similar logic for their node sets.

Now that I have these three subsets of films, I can use set:intersection() and set:difference() to create the desired lists. The stylesheet is as follows:

<xsl:stylesheet version=”1.0”

  xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”

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

  extension-element-prefixes=”set”>

  

  <xsl:output method=”text”/>

  <xsl:variable name=”DramaFilms” select=”//film[contains(@genre, ‘Drama’)]”/>

  <xsl:variable name=”EpicFilms” select=”//film[contains(@genre, ‘Epic’)]”/>

  <xsl:variable name=”RomanticFilms” select=”//film[contains(@genre, ‘Romantic’)]”/>

  <xsl:template match=”/”>

   ------------------------------

   Epic Dramas:

    <xsl:for-each select=”set:intersection($DramaFilms, $EpicFilms)”>

     * <xsl:value-of select=”.”/>

    </xsl:for-each>

   ------------------------------

   Non-Epic Dramas:

    <xsl:for-each select=”set:difference($DramaFilms, $EpicFilms)”>

     * <xsl:value-of select=”.”/>

    </xsl:for-each>

   ------------------------------

   Non-Drama Epics:

    <xsl:for-each select=”set:difference($EpicFilms,$DramaFilms)”>

     * <xsl:value-of select=”.”/>

    </xsl:for-each>

   ------------------------------

   Non-Romantic Dramas:

    <xsl:for-each select=”set:difference($DramaFilms, $RomanticFilms)”>

     * <xsl:value-of select=”.”/>

    </xsl:for-each>

  </xsl:template>

  

  <xsl:template match=”film”/>

</xsl:stylesheet>

Looking closer at the extension functions that are used, the first xsl:for-each loop uses a set:intersection() function to return all the common nodes that have Drama and Epic strings as part of their genre value. However, the next xsl:for-each instruction uses the set:difference() function to return the nodes from the DramaFilms node set that are not part of the EpicFilms node set. The remaining xsl:for-each loops follow the same pattern to return the nodes desired.

When transformed by a processor that supports these extensions, the result is:

   ------------------------------

   Epic Dramas:

    

     * Gone With The Wind

   ------------------------------

   Non-Epic Dramas:

    

     * Citizen Kane

     * Casablanca

     * The Godfather

     * The Graduate

     * On The Waterfront

     * It’s A Wonderful Life

     * Sunset Boulevard

     * Some Like It Hot

     * All About Eve

     * One Flew Over The Cuckoo’s Nest

     * The Grapes Of Wrath

     * The Maltese Falcon

   ------------------------------

   Non-Drama Epics:

    

     * Schindler’s List

     * Star Wars

   ------------------------------

   Non-Romantic Dramas:

    

     * Citizen Kane

     * The Godfather

     * Gone With The Wind

     * The Graduate

     * On The Waterfront

     * It’s A Wonderful Life

     * Sunset Boulevard

     * Some Like It Hot

     * All About Eve

     * One Flew Over The Cuckoo’s Nest

     * The Grapes Of Wrath

     * The Maltese Falcon

Tip

More extension functions are available than elements or attributes. Take a close look at the function set offered by EXSLT and processor vendors; these many extension functions can save you a considerable amount of time and effort when authoring stylesheets.

Ensuring Portability of Your Stylesheets

As I said from the get-go, because extension elements or functions are not part of the XSLT standard, their support depends entirely on the XSLT processor vendor. The obvious problem that can then surface is that, when you try to run your extension-laden stylesheet, the XSLT processor you’re using may not support the extensions you’re using. For example, if you try to run the saxon:while example earlier in the chapter using msxsl, you get a processing error saying that saxon:while is an unrecognized element.

Rather than allowing this error to occur, you need a fallback strategy, in the event that the stylesheet is run by a processor that doesn’t support the extension. As I discuss in the following sections, you can add a fallback plan to your stylesheet and test for the support of extension elements or functions before they are processed as part of the stylesheet.

Testing extension elements

For extension elements, the first way in which you can handle unsupportive processors is to use the xsl:fallback element, which you can embed inside the extension element you’re using. If the processor runs the extension successfully, then the xsl:fallback instruction is simply ignored. But suppose all heck breaks loose and the processing engine doesn’t recognize the extension. When this event occurs, the processor executes the instructions provided inside xsl:fallback. If you add xsl:fallback to the saxon:while stylesheet shown earlier in this chapter, the stylesheet looks like this:

<xsl:stylesheet version=”1.0”

  xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”

  xmlns:saxon=”http://icl.com/saxon”

  extension-element-prefixes=”saxon”>

  <xsl:template match=”/”>

    <xsl:variable name=”idx” saxon:assignable=”yes” select=”1”/>

    <saxon:while test=”$idx &lt; 11”>

      The value of idx is <xsl:value-of select=”$idx”/>

      <saxon:assign name=”idx” select=”$idx+1”/>

      <xsl:fallback>

        <xsl:message terminate=”yes”>XSLT processor does not support saxon:while extension.</xsl:message>

      </xsl:fallback>

    </saxon:while>

  </xsl:template>

</xsl:stylesheet>

In this case, if the processor doesn’t support saxon:while, then an xsl:message instruction is nestled inside xsl:fallback. When encountered, the xsl:message instruction sends its contents as a template to the processor and its terminate attribute specifies whether or not the processor should stop processing. It is up to the processor to determine how it wants to handle the xsl:message template; some may output it in the result document, while others display a message box with the template contents. Therefore, if the xsl:fallback instruction is executed, you know the processor does not support the extension.

A second way to determine whether or not an element is supported is by using the element-available() built-in function. This function allows you to test an extension element so that you can determine whether or not it is supported before you try to call it. The typical scenario is to test it inside an xsl:choose instruction, as shown here:

<xsl:stylesheet version=”1.0”

  xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”

  xmlns:saxon=”http://icl.com/saxon”

  extension-element-prefixes=”saxon”>

  <xsl:template match=”/”>

    <xsl:variable name=”idx” saxon:assignable=”yes” select=”1”/>

    <xsl:choose>

      <xsl:when test=”element-available(‘saxon:while’)”>

        <saxon:while test=”$idx &lt; 11”>

          The value of idx is <xsl:value-of select=”$idx”/>

          <saxon:assign name=”idx” select=”$idx+1”/>

        </saxon:while>

      </xsl:when>

      <xsl:otherwise>

        Sorry, Charlie. No support here for while looping.

      </xsl:otherwise>

    </xsl:choose>

  </xsl:template>

</xsl:stylesheet>

In this example, xsl:when tests to determine whether saxon:while is an element that the processor can use. If so, then the processor executes the contents of saxon:while. If not, the xsl:otherwise element is used instead, adding the “Sorry, Charlie” literal text to the result document.

Tip

For most purposes, the choice of using xsl:fallback versus element-available() is personal preference. However, the one case in which element-available() is the more powerful option is when you want to test for multiple extension elements and, depending on which element is available, add a different result to the output.

For example, I can add an extra xsl:when to the preceding stylesheet to test to see if msxsl:script is available if saxon:while is not. The xsl:choose instruction would be changed to:

    <xsl:choose>

      <xsl:when test=”element-available(‘saxon:while’)”>

        <saxon:while test=”$idx &lt; 11”>

          The value of idx is <xsl:value-of select=”$idx”/>

          <saxon:assign name=”idx” select=”$idx+1”/>

        </saxon:while>

      </xsl:when>

      <xsl:when test=”element-available(‘msxsl:script’)”>

        <!-- run a script -->

      </xsl:when>

      <xsl:otherwise>

        Sorry, Charlie. No support here for while looping.

      </xsl:otherwise>

    </xsl:choose>

When this stylesheet is applied, the XSLT processor first tests to see whether saxon:while is available. If not, then msxsl:script is tested. And if it is not available too, then xsl:otherwise is chosen.

Testing extension functions

Extension functions can be evaluated in a way eerily similar to the element-available() function described in the preceding section. The function-available() built-in function tests whether the processor supports a function. For example:

<xsl:stylesheet version=”1.0”

  xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”

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

  extension-element-prefixes=”date”>

  <xsl:template match=”/”>

    <xsl:choose>

      <xsl:when test=”function-available( ‘date:time’ )”>

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

      </xsl:when>

      <xsl:otherwise>

        What do you think I am? A Timex? 

      </xsl:otherwise>

    </xsl:choose>

  </xsl:template>

</xsl:stylesheet>

In this example, the xsl:when instruction checks to see if the date:time() function is available. If the test returns true, then the function is run. But if not, then the XSLT processor uses the xsl:otherwise element and outputs its contents to the result document.

Tip

Even if you use a single XSLT processor, it is good programming practice to add fallback code if you use extension elements in your stylesheets. Doing so helps ensure that your stylesheets produce exactly the results you intend.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset