Skip to main content

Simple Xalan extension functions

Mixing Java with XSLT

Elliotte Rusty Harold (elharo@metalab.unc.edu), Adjunct Professor, Polytechnic University
Photo of Elliot Rusty Harold
Elliotte Rusty Harold is originally from New Orleans, to which he returns periodically in search of a decent bowl of gumbo. However, he resides in the Prospect Heights neighborhood of Brooklyn with his wife, Beth, and cats Charm (named after the quark) and Marjorie (named after his mother-in-law). He's an adjunct professor of computer science at Polytechnic University, where he teaches Java and object-oriented programming. His Cafe au Lait Web site has become one of the most popular independent Java sites on the Internet, and his spin-off site, Cafe con Leche, has become one of the most popular XML sites. His most recent book is Java I/O, 2nd edition. He's currently working on the XOM API for processing XML, the Jaxen XPath engine, and the Jester test coverage tool. He'll speak about XML at XML 2006 in Boston in December. You can reach Elliotte at elharo@metalab.unc.edu.

Summary:  The Xalan XSLT processor can invoke almost any method in almost any Java™ class in the classpath. Doing so can improve performance, provide features like trigonometric functions that aren't available in XSLT, perform file I/O, talk to databases and network servers, or implement algorithms that are easy to write in the Java language but hard to write in XSLT. Learn the basics of invoking Java code from Xalan.

Date:  07 Nov 2006
Level:  Intermediate
Activity:  5567 views

Extensible Stylesheet Language Transformations (XSLT) is a Turing complete programming language. That means that given enough memory, it can calculate anything a program written in any other programming language can calculate. However, this theoretical ability is often impractical. There are several cases where you may need to write code in a more traditional language rather than XSLT:

  • External I/O -- For instance, files, databases, or network connections. XSLT has very limited ability to read or write these things.
  • External devices -- For instance, Universal Serial Bus (USB) ports or the system clock.
  • Advanced math -- XSLT can perform basic arithmetic easily enough, but it doesn't support trigonometry, exponential functions, logarithms, or other more advanced mathematical operators and functions. Although you can implement all of these using the basic operations XSLT does support, such a program would be both unwieldy and slow. Using a language that is designed for such operations dramatically improves both performance and legibility.

This article shows you how to link Java™ classes to XSLT to perform these sorts of operations. The means by which XSLT invokes Java classes varies from one XSLT processor to the next. This article focuses on the Apache Foundation's popular Xalan XSLT processor.

Static methods

The easiest Java method to invoke from Xalan is a simple static method. For example, suppose you have a table of angles like that shown in Listing 1.


Listing 1. An XML document containing angles
<angles units="degrees">
  <data>30<data>
  <data>45<data>
  <data>115<data>
<angles>

Now, suppose you want to do something as simple as write out the sines of those angles. XSLT doesn't have a sine function. You could code a Taylor series for the sine function (see Resources for more about the Taylor series), but it's much easier to call the Java method Math.sin(). Calling a preexisting static Java method from XSLT with Xalan takes only three steps:

  1. Map a namespace prefix such as math to a Uniform Resource Identifier (URI) of the form xalan://fully.package.qualified.ClassName -- for example, xalan://java.lang.Math.
  2. List this prefix in the extension-element-prefixes attribute of the root xsl:stylesheet element.
  3. Invoke the function using the form prefix:methodName() -- for example, math:sin(.)

Listing 2 demonstrates this process.


Listing 2. A Xalan XSLT stylesheet that finds the sines of the angles in Listing 1
<xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:math="xalan://java.lang.Math"
  extension-element-prefixes="math"
>
  
  <xsl:template match="data" >
    <The Math.sin() function expects input in radians
         so you have to convert from degrees before calling it. -->
    <xsl:value-of select="math:sin(3.1415292 * number(.) div 180.0)" />
  <xsl:template>

</xsl:stylesheet>

The same prefix can reference several different static methods in the same class. For example, Listing 3 shows a stylesheet that generates sines, cosines, and tangents for the angles.


Listing 3. A Xalan XSLT stylesheet that finds the sines, cosines, and tangents of the angles in Listing 1
<xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:math="xalan://java.lang.Math"
  extension-element-prefixes="math"
>
  
  <xsl:template match="angles" >
    <table>
      <th><td>Angle<td><td>Sine</td><td>Cosine<td><td>Tangent</td></th>
      <xsl:apply-templates select="data"/>
    <table>
  <xsl:template>

  <xsl:template match="data" >
    <tr>
      <xsl:variable name="radians" select="3.1415292 * number(.) div 180.0"/>
      <td><xsl:value-of select="." /><td>
      <td><xsl:value-of select="math:sin($radians)" /><td>
      <td><xsl:value-of select="math:cos($radians)" /><td>
      <td><xsl:value-of select="math:tan($radians)" /></td>
    </tr>
  <xsl:template>

<xsl:stylesheet>

To invoke static methods in different classes, map each fully qualified class name to a different prefix. Then, list each prefix in the extension-element-prefixes attribute, separated by whitespace.

This technique is by no means limited to methods defined in the core Java packages. You can invoke static methods in your own classes exactly the same way; these methods can do anything a Java static method can do. Just put the class somewhere in the classpath you use when running Xalan.


Caveats

Before you begin writing XSLT extension functions, you need to understand one critical difference between the Java programming language and XSLT. XSLT is a functional language. Among other things, this means that it has no global variables, shares no state between function invocations, and causes no side effects. These characteristics enable compilers and interpreters to perform certain optimizations they couldn't otherwise perform. For instance, because the output of a function is completely determined by its input and its code, you don't need to evaluate a function more than once with the same argument. If foo(2) returns 7 the first time you call it, it will return 7 the second time you call it, and the third, and the thirty-third. Therefore intermediate results can be cached and reused, rather than recalculated.

The Java language is imperative rather than functional: It stores state outside of functions, and functions can have side effects. In Java code, foo(2) may return a different value every time you invoke it.

Today's Xalan (2.8) usually works pretty much as you'd expect. Methods are invoked repeatedly, even with the same arguments, and usually in the most obvious order. However, the Xalan developers plan to more aggressively optimize transforms in the future, and the current situation is very likely to change.

Mixing nonfunctional Java code into a functional language like XSLT runs the risk of violating the XSLT processor's assumptions. For instance, the processor might not recalculate a value when it needs to. It might invoke methods in a different order than you expect. It might even invoke them more or fewer times than you expect. Then again, it might do exactly what you expect. However, there's no promise that it will do so, and even if the extension function works today, it might fail in the next point release when the optimization scheme changes a little.

To safely call a Java method from XSLT, make the method as functional as possible. Methods you call shouldn't depend on the mutable state of any object and should return fully reproducible and predictable results. Static methods that merely accept an argument or two, perform a calculation, and return a value based on nothing but those arguments are ideal. For example, you can safely call most of the methods in the java.lang.Math class, such as Math.sin() and Math.exp() from XSLT. The notable exception in this class is Math.random(), which intentionally returns a different value every time you call it.

Of course, one reason you write extension functions in Java code is precisely to add features like random numbers, dates, times, and other content that XSLT doesn't provide. It's possible to invoke these methods from XSLT. However, if you do so, be aware that these methods might not be called when or as many times as you expect.

Be especially careful to avoid depending on side effects. A method that does something that isn't expressed in its return value -- for example, writing a file -- is dangerous. If you do this, design the method to be idempotent: That is, make sure it does exactly the same thing whether it's invoked once or a dozen times. Also be sure the method returns a value that the stylesheet will use. Otherwise, Xalan can optimize away the invocation of the extension function and not call it even once.


Calling instance methods

Instance methods, almost by definition, involve object state. More often than not, they involve mutable object state. Thus they're rarely the first choice when writing a new extension function. Most extension functions are better written as static methods. However, sometimes it's convenient to invoke a preexisting instance function directly from XSLT.

Calling an instance method is similar to calling a static method: You map the fully qualified class name to the namespace prefix and then invoke the method using that prefix to identify the class and the local name to identify the method. However, instance methods need a third datum: the particular instance of the class on which to invoke the method. To get this, you must first create an object of the class and store it in a variable. To do so, you invoke the prefix:new() function. For example, the fragment in Listing 4 creates a java.awt.Color object and stores it in the XSLT variable named blue.


Listing 4. Retrieving a Color object from XSLT
<xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:color="xalan://java.awt.Color"
  extension-element-prefixes="color"
>
  
  <xsl:template match="/" >
    <xsl:variable name="blue" select="color:new(20, 10, 160)"/>
    <... -->
  <xsl:template>

</xsl:stylesheet>

Then, when invoking a method on this Color object, you pass the variable reference $blue as the first argument. For example, Listing 5 first creates a brighter blue by invoking the brighter() method and then prints its red, green, and blue components.


Listing 5. Invoking an instance method on a Color object
  <xsl:template match="/" >
    <xsl:variable name="blue" 
                  select="color:new(20 div 256, 10 div 256, 160 div 256)"/>
    <xsl:variable name="brighterblue" select="color:brighter($blue)"/>
    <red><xsl:value-of select="color:getRed($brighterblue)"/></red>
    <green><xsl:value-of select="color:getGreen($brighterblue)"/></green>
    <blue><xsl:value-of select="color:getBlue($brighterblue)"/><blue>
  </xsl:template>
  

If the class has a no-args constructor, and you want to use that constructor and then immediately invoke a single instance method on it, there's a shortcut. Just call prefix:methodName as you would with a static method. You don't need to explicitly create an object and store it in a variable first.


Type matching

XSLT uses the XPath data model with four basic types: number (a Java double), string, boolean, and node-set. (It also adds a fifth result-tree fragment type, but that rarely comes into play in simple extension functions.) When Xalan passes arguments to Java functions and converts return values from Java functions, it converts numbers to doubles, strings to strings, and booleans to booleans. It also converts these XPath types to related types if necessary to match a method signature.

For instance, when faced with foo(2), Xalan prefers to call a method named foo that takes a double as an argument. However, if no such method exists, it calls foo(java.lang.Double), foo(float), foo(long), or foo(int) instead (in that order of preference). If those all fail, it looks for foo(short), foo(char), and foo(byte). If none of those are found, it even tries converting the number to a string and calling foo(String). As a last resort, it looks for foo(Object).

Normally, you don't have to worry much about this process -- the right thing happens by default. The only common problem is when a class has both foo(double) and foo(int) variants, and you expect the int version to be invoked. Remember, Xalan always prefers doubles, and it uses the int-accepting method only if it can't find a wider type. For example, did you wonder about this instruction from Listing 5?

    <xsl:variable name="blue" 
                  select="color:new(20 div 256, 10 div 256, 160 div 256)"/>

Perhaps it occurred to you that it would be simpler to pass in the int codes like this:

    <xsl:variable name="blue" 
                  select="color:new(20, 10, 160)"/>

The problem is that although 20, 10, and 160 are all int literals in the Java language, they're all doubles in XSLT. This instruction doesn't invoke Color(int, int, int). Rather, it invokes Color(float, float, float), which expects to receive values between 0.0 and 1.0, rather than 0 and 255. Before invoking this method, you have to rescale the color values into that range.

Be cautious about method overloading based on argument type when you write extension functions. It's simpler not to overload methods at all.

Extension functions that operate on node-sets are trickier to write than ones that operate on simple values, but not excessively so. Xalan converts node-sets to Document Object Model (DOM) NodeIterator, NodeList, and Node objects, in that order of preference. If no function matches, it tries String and finally Object.

Other object types can't be handled easily within XSLT. You can return these types from a Java method and store them in a variable. However, the only thing you can do with such a Java object is pass it back to or use it to invoke another extension function. Try to design your extension functions so that they use only doubles, strings, booleans, and node-sets as both arguments and return values.

If you want to call Java methods that operate on other types, try writing at least one additional adapter function in the Java program. This adapter method can take the XPath types, convert them to the Java types, and then invoke the method you really want to call. Then, when that method returns, the adapter method can convert the return value to one of the four XPath types and return that. It's much easier to manage the conversions in Java code than in XSLT.


Exception handling

XSLT has no exception handling of any kind. If your extension function throws an exception, Xalan shuts down. For example, Listing 6 shows what happened with an earlier, buggier version of Listing 5.


Listing 6. When an extension function throws an exception
$ java  org.apache.xalan.xslt.Process 
 -IN http://www.cafeaulait.org/ -XSL colors.xsl
 ERROR:  'Color parameter outside of expected range: Red Green Blue'

(Location of error unknown)XSLT Error (javax.xml.transform.TransformerException): 
 java.lang.IllegalArgumentException: Color parameter outside of expected range: 
 Red Green Blue

Consequently, you need to implement all your exception-handling logic within the Java methods. Here the exception was a result of a bug in my code, which was easy to fix. If you invoke a preexisting function that can throw a checked exception, you should write a wrapper function that catches and handles that exception; then, invoke the wrapper method from XSLT instead.


In conclusion

XSLT is a wonderfully efficient language for transforming documents, but it's not always the best language for more traditional tasks like integrating differential equations or talking to databases. Fortunately, you can code these tasks in the Java language and then invoke them from your XSLT stylesheets using Xalan.


Resources

Learn

Get products and technologies

  • Xalan 2: Download the XSLT engine is discussed in this article.

  • IBM trial software: Build your next development project with trial software available for download directly from developerWorks.

Discuss

About the author

Photo of Elliot Rusty Harold

Elliotte Rusty Harold is originally from New Orleans, to which he returns periodically in search of a decent bowl of gumbo. However, he resides in the Prospect Heights neighborhood of Brooklyn with his wife, Beth, and cats Charm (named after the quark) and Marjorie (named after his mother-in-law). He's an adjunct professor of computer science at Polytechnic University, where he teaches Java and object-oriented programming. His Cafe au Lait Web site has become one of the most popular independent Java sites on the Internet, and his spin-off site, Cafe con Leche, has become one of the most popular XML sites. His most recent book is Java I/O, 2nd edition. He's currently working on the XOM API for processing XML, the Jaxen XPath engine, and the Jester test coverage tool. He'll speak about XML at XML 2006 in Boston in December. You can reach Elliotte at elharo@metalab.unc.edu.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML, Java technology
ArticleID=172064
ArticleTitle=Simple Xalan extension functions
publish-date=11072006
author1-email=elharo@metalab.unc.edu
author1-email-cc=dwxed@us.ibm.com

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers