Writing Jaxen Extension Functions

What is an extension function?

An extension function is any function used in an XPath expression that is not included in the standard XPath 1.0 library.

Whereas standard functions have unqualified names (string(), count(), boolean(), etc.), extension functions generally belong to a namespace and have prefixed names like saxon:evaluate or exslt:tokenize. (the bundled Jaxen extension functions in org.jaxen.function.ext do not yet have a namespace. This is a bug. Please don't emulate it with your own extension functions.)

Writing an extension function

Let's suppose you want to write an extension function that finds the minimum of a set of numeric values. We'll call this extension function min() and put it in the http://exslt.org/math namespace. (This is actually an extension function defined by the EXSLT library at http://www.exslt.org/math/functions/min/math.min.html) We'll use the prefix math in this document but the prefix can change as long as the URI is correct.

This function has the following signature:

number math:min(node-set)

In Jaxen terms a number is a java.lang.Double and a node-set is a java.util.List.

Each extension function is implemented by a single class. This class can belong to any package. It must have a no-args constructor and implement the org.jaxen.Function interface. This interface declares a single method, call:

package org.jaxen;

public interface Function {
    Object call(Context context, List args) throws FunctionCallException;
}

For the math:min function we'll need to iterate through the list, convert each one to a numeric value, and then finds the minimum. Some casting is required; but mostly we just iterate through the list while comparing each member of the list to the current minimum value. If the next value is smaller, then we replace the old minimum value with the new minimum value. Finally we return a new Double object containing the minimum value. Here's the code:

public class MinFunction implements Function {

    public Object call(Context context, List args) 
      throws FunctionCallException {
        
        if (args.isEmpty()) return Double.valueOf(Double.NaN);
        
        Navigator navigator = context.getNavigator();
        double min = Double.MAX_VALUE;
        Iterator iterator = args.iterator();
        while (iterator.hasNext()) {
            double next = NumberFunction.evaluate(iterator.next(), navigator).doubleValue();
            min = Math.min(min, next);
        }
        return new Double(min);
    }
    
}

Notice the use of Jaxen's implementation of the XPath number() function to convert each value in the node-set to a double.

Extension functions should be side effect free. They should not write files, change fields, or modify the state of anything. Extension functions may be called at any time, and not necessarily in the order you expect them to be. Furthermore, extension functions may be called more or less often than you expect. Each invocation of an extension function should be completely self-contained.

Installing an extension function into Jaxen

You may have noticed the name and namespace of the extension function showed up nowhere in the extension function class. To bind it to a name it must be registered with the function context. You can either register it with the default global function context (XPathFunctionContext.INSTANCE) or register it with a custom function context for the XPath expression

Let's assume you want to register it with a custom function context. Simply pass the namespace URI, local name, and a MinFunction object to the XPathFunctionContext constructor:

        SimpleFunctionContext fc = new XPathFunctionContext();
        fc.registerFunction("http://exslt.org/math", "min", new MinFunction());

You'll also need a namespace context that can map the prefix math to the URI http://exslt.org/math:

        SimpleNamespaceContext nc = new SimpleNamespaceContext();
        nc.addNamespace("math", "http://exslt.org/math");

Finally when evaluating the function you'll need to set your custom XPath function and namespace contexts for the expression:

        BaseXPath xpath = new DOMXPath("math:min(//x)");
        xpath.setFunctionContext(fc);
        xpath.setNamespaceContext(nc);

Otherwise, evaluating the expression will throw a JaxenException.

You can add the function to the default function context by registering it with the constant XPathFunctionContext.INSTANCE instead:

XPathFunctionContext.INSTANCE.registerFunction("http://exslt.org/math", "min", new MinFunction());