Chapter 16 - Extending XSLT | |
XSLT For Dummies | |
by Richard Wagner | |
Hungry Minds 2002 |
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 Im 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<=date]"/> <xsl:variable name="Post1950" select="//film[1950<=date]"/> <xsl:variable name="Post1960" select="//film[1960<=date]"/> <xsl:variable name="Post1970" select="//film[1970<=date]"/> <xsl:variable name="Post1980" select="//film[1980<=date]"/> <xsl:variable name="Post1990" select="//film[1990<=date]"/> <xsl:variable name="Pre1940" select="//film[date<=1939]"/> <xsl:variable name="Pre1950" select="//film[date<=1949]"/> <xsl:variable name="Pre1960" select="//film[date<=1959]"/> <xsl:variable name="Pre1970" select="//film[date<=1969]"/> <xsl:variable name="Pre1980" select="//film[date<=1979]"/> <xsl:variable name="Pre1990" select="//film[date<=1989]"/> <xsl:variable name="Pre2000" select="//film[date<=1999]"/> Each of these variables represents a node set based on the film elements 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 stylesheets 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 elements 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<=date]"/> <xsl:variable name="Post1950" select="//film[1950<=date]"/> <xsl:variable name="Post1960" select="//film[1960<=date]"/> <xsl:variable name="Post1970" select="//film[1970<=date]"/> <xsl:variable name="Post1980" select="//film[1980<=date]"/> <xsl:variable name="Post1990" select="//film[1990<=date]"/> <xsl:variable name="Pre1940" select="//film[date<=1939]"/> <xsl:variable name="Pre1950" select="//film[date<=1949]"/> <xsl:variable name="Pre1960" select="//film[date<=1959]"/> <xsl:variable name="Pre1970" select="//film[date<=1969]"/> <xsl:variable name="Pre1980" select="//film[date<=1979]"/> <xsl:variable name="Pre1990" select="//film[date<=1989]"/> <xsl:variable name="Pre2000" select="//film[date<=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 documenta 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.
|