Inside Xslt

Extension Functions

In XSLT 1.0, W3C defined the way to differentiate extension functions from built-in functions by requiring that namespace-qualified names be used to reference extension functions, as in starpowder:calculate() . XSLT 1.0 also provided the function-available() function to test for the availability of a function by name .

The XSLT 1.1 working draft put some additional restrictions on extension functions:

Until recently, XSLT processors were free to define the way they implemented extension functions. For example, in Saxon and Xalan, you can run Java code directly if you define a namespace that specifies a Java class as the final part of its URI, as I do here, where I define a Date namespace that corresponds to the Java Date class:

<?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:Date="http://www.saxon.com/java/java.util.Date"> . . .

Now I can use Java Date functions such as toString and new to embed the current date in an <H1> HTML header in the output as follows :

Listing 5.14 Using the Java Date Function

<?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:Date="http://www.saxon.com/java/java.util.Date"> <xsl:template match="/PLANETS"> <HTML> <HEAD> <TITLE> The Planets Table </TITLE> </HEAD> <BODY> <H1> The Planets Table </H1> <BR/> <H1> <xsl:value-of select="Date:toString(Date:new())"/> </H1> <TABLE BORDER="2"> <TD>Name</TD> <TD>Mass</TD> <TD>Radius</TD> <TD>Day</TD> <xsl:apply-templates/> </TABLE> </BODY> </HTML> </xsl:template> <xsl:template match="PLANET"> <TR> <TD><xsl:value-of select="NAME"/></TD> <TD><xsl:apply-templates select="MASS"/></TD> <TD><xsl:apply-templates select="RADIUS"/></TD> <TD><xsl:apply-templates select="DAY"/></TD> </TR> </xsl:template> <xsl:template match="MASS"> <xsl:value-of select="."/> </xsl:template> <xsl:template match="RADIUS"> <xsl:value-of select="."/> </xsl:template> <xsl:template match="DAY"> <xsl:value-of select="."/> </xsl:template> </xsl:stylesheet>

You can see the results of this stylesheet in Figure 5.3.

Figure 5.3. Using an extension function.

This certainly works, and its a great way to interface Java to XSLT. However, the XSLT 1.1 working draft introduced the <xsl:script> element, and its likely it will be a part of XSLT 2.0 as well.

The <xsl:script> Element

The <xsl:script> element was defined in the XSLT 1.1 working draft, and it gives you a well-defined way to connect extension functions to XSLT stylesheets. This element is a top-level element, and has the following attributes:

This element contains character data (Microsoft uses a CDATA section) that implements the extension function or functions.

So how do you connect a function defined in an <xsl:script> element to your XSLT stylesheet? You first create the <xsl:script> element as a top-level element in your stylesheet, then place the functions you want to define in that element. Heres an example where Im defining two JavaScript functions, makeMoney and makeMoreMoney , in an <xsl:script> element that implements the extension namespace starpowder:

<xsl:script implements-prefix="starpowder" language="javascript"> function makeMoney(e) { . . . } function makeMoreMoney(e) { . . . } </xsl:script>

It can also be a good idea, depending on your XSLT processor, to enclose scripts like this in a CDATA section:

<xsl:script implements-prefix="starpowder" language="javascript"> <![CDATA[ function makeMoney(e) { . . . } function makeMoreMoney(e) { . . . } ]]> </xsl:script>

Now you can use the starpowder namespace to indicate that youre calling an extension function, as follows:

<CASH> <xsl:value-of select="starpowder:makeMoney(1000000)"/> </CASH>

Thats all it takes (if your XSLT processor is compliant). If you want to specify Java class rather than a script, you can use the src attribute to specify the Java class you want to use:

<xsl:script implements-prefix="starpowder" src="java:com.MakeMoney" language="java"> </xsl:script>

Working with External Resources

You can also use the src attribute if you have an archive of JavaScript routines, as in this example: src=archives.js.

No XSLT processor that I know of implements the <xsl:script> element yet except the Microsoft MSXML3 processor. You can find information on how you can use scripts to write extension functions for use in the Internet Explorer on the Microsoft site (currently, that page is at http://msdn.microsoft.com/xml/xslguide/script-overview.asp, but Microsoft seems to reorganize its site every two days or so).

The next example shows how to use <xsl:script> with the Internet Explorer. In this example, I write a JavaScript function to convert the planetary radius measurements in planets.xml, which are in miles, to kilometers, and display those measurements in kilometers.

As discussed in Using Internet Explorer to Transform XML Documents, in Chapter 2, you need to make some modifications to browse an XML document that uses an XSL stylesheet in Internet Explorer version 5.5 or earlier (unless you've installed the newest MSXML parser or use version 6.0, just out, except that you must still use text/xsl). To start, use the MIME type text/xsl, not text/xml, for an XSL stylesheet. I'll also give the URI for the stylesheet, kilometers.xsl, as follows:

Listing 5.15 planets.xml Set to Use kilometers.xsl in Internet Explorer

<?xml version="1.0"?> <?xml-stylesheet type="text/xsl" href="kilometers.xsl"?> <PLANETS> <PLANET> <NAME>Mercury</NAME> <MASS UNITS="(Earth = 1)">.0553</MASS> <DAY UNITS="days">58.65</DAY> <RADIUS UNITS="miles">1516</RADIUS> <DENSITY UNITS="(Earth = 1)">.983</DENSITY> <DISTANCE UNITS="million miles">43.4</DISTANCE><!--At perihelion--> </PLANET> <PLANET> <NAME>Venus</NAME> <MASS UNITS="(Earth = 1)">.815</MASS> <DAY UNITS="days">116.75</DAY> <RADIUS UNITS="miles">3716</RADIUS> <DENSITY UNITS="(Earth = 1)">.943</DENSITY> <DISTANCE UNITS="million miles">66.8</DISTANCE><!--At perihelion--> </PLANET> . . .

To convert for use in IE 5.5 or earlier in the stylesheet, kilometers.xsl, I use the XSL namespace that the IE uses, and add the <xsl:script> element, indicating that the scripts Ill write will be in JavaScript. Note, however, that the Internet Explorers <xsl:script> element does not support the implements-prefix attribute, so I cant connect the functions I define to a namespace:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl"> <xsl:script language="javascript"> . . . </xsl:script> . . .

The Internet Explorer requires that you enclose your code in a CDATA section. Here, I define the function milesToKilometers . This function is passed a node, and reads the text in the node with the text property, then uses the JavaScript parseInt function to convert the text to a number of miles. I multiply the number of miles by 1.6 to convert miles to kilometers, and return that result:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl"> <xsl:script language="javascript"> <![CDATA[ function milesToKilometers(e) { miles = parseInt(e.text); return miles * 1.6; } ]]> </xsl:script> . . .

Because you cant associate a namespace with an extension function in the Internet Explorer yet, you use the Microsoft-only element <xsl:eval> to call extension functions. Heres how that looks in the stylesheet kilometers.xsl, where Im passing the milesToKilometers function the current <RADIUS> node so it will convert miles to kilometers. Because IE 5.5 or earlier does not support default rules (although version 6.0just out as this book goes to pressdoes, so you won't have to make this change), I can provide a rule for the root node as well for IE 5.5 or earlier:

Listing 5.16 kilometers.xsl

<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl"> <xsl:script language="javascript"> <![CDATA[ function milesToKilometers(e) { miles = parseInt(e.text); return miles * 1.6; } ]]> </xsl:script> <xsl:template match="/"> <HTML> <HEAD> <TITLE> The Planets Table </TITLE> </HEAD> <BODY> <H1> The Planets Table </H1> <TABLE BORDER="2"> <TR> <TD>Name</TD> <TD>Mass</TD> <TD>Radius</TD> <TD>Day</TD> </TR> <xsl:apply-templates/> </TABLE> </BODY> </HTML> </xsl:template> <xsl:template match="PLANETS"> <xsl:apply-templates/> </xsl:template> <xsl:template match="PLANET"> <TR> <TD><xsl:value-of select="NAME"/></TD> <TD><xsl:value-of select="MASS"/></TD> <TD><xsl:apply-templates match="RADIUS"/></TD> <TD><xsl:value-of select="DAY"/></TD> </TR> </xsl:template> <xsl:template match="RADIUS"> <xsl:eval>milesToKilometers(this)</xsl:eval> </xsl:template> </xsl:stylesheet>

And thats it; you can see the results of this transformation in Figure 5.4. In time, vendors will provide more and more built-in extension functions. How can you determine whether a given extension function is available? You can use the function-available function.

Figure 5.4. Using an extension function in the Internet Explorer.

Using the function-available Function

You use the XSLT 1.0 function named function-available to test whether a function is available. In the following example I want to use the extension function starpowder:calculate to do some math, and if its not available, I send the text Sorry, cant do math today. to the result document (although you could, of course, also quit processing and display an error message with the <xsl:message> element):

<xsl:choose xmlns:starpowder="http://www.starpowder.com"> <xsl:when test="function-available("starpowder:calculate")"> <xsl:value-of select="starpowder:calculate("2+2")"/> </xsl:when> <xsl:otherwise> <xsl:text>Sorry, can't do math today.</xsl:text> </xsl:otherwise> </xsl:choose>

External Objects

The support for extension functions in the XSLT 1.1 working draft introduced a new data type. This new data type is called an external object . An XSLT variable, which is covered in Chapter 9, may be assigned an external object rather than one of the four XPath data-types supported in XSLT (string, number, Boolean, node set). An external object represents an object that is created by an external programming language and returned by an extension function and that is not convertible to one of the four XPath data types. The data type external object was added to XSLT to give you a safe wrapper for such data. No one implements external objects yet, but thats coming.

Категории