1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
|
<?xml version="1.0"?>
<document url="http://jaxen.org/extensions.xml">
<properties>
<author>Elliotte Rusty Harold</author>
<title>Writing Jaxen Extension Functions</title>
</properties>
<body>
<title>Writing Jaxen Extension Functions</title>
<section name="What is an extension function?">
<p>
An extension function is any function used in an XPath expression that
is not included in the standard XPath 1.0 library.
</p>
<p>
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.)
</p>
</section>
<section name="Writing an extension function">
<p>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.
</p>
<p>
This function has the following signature:
</p>
<source>number math:min(node-set)</source>
<p>
In Jaxen terms a number is a java.lang.Double and a node-set is a java.util.List.
</p>
<p>
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:
</p>
<source>package org.jaxen;
public interface Function {
Object call(Context context, List args) throws FunctionCallException;
}</source>
<p>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:
</p>
<source>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);
}
}</source>
<p>
Notice the use of Jaxen's implementation of the XPath
number() function to convert each value in the node-set to a double.
</p>
<p>
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.
</p>
</section>
<section name="Installing an extension function into Jaxen">
<p>
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
</p>
<p>
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:
</p>
<source> SimpleFunctionContext fc = new XPathFunctionContext();
fc.registerFunction("http://exslt.org/math", "min", new MinFunction());</source>
<p>
You'll also need a namespace context that can map the prefix math to the URI
http://exslt.org/math:
</p>
<source> SimpleNamespaceContext nc = new SimpleNamespaceContext();
nc.addNamespace("math", "http://exslt.org/math");</source>
<p>
Finally when evaluating the function you'll need to set your custom
XPath function and namespace contexts for the expression:
</p>
<source> BaseXPath xpath = new DOMXPath("math:min(//x)");
xpath.setFunctionContext(fc);
xpath.setNamespaceContext(nc);</source>
<p>
Otherwise, evaluating the expression will throw a JaxenException.
</p>
<p>
You can add the function to the default function context
by registering it with the constant XPathFunctionContext.INSTANCE instead:
</p>
<source>XPathFunctionContext.INSTANCE.registerFunction("http://exslt.org/math", "min", new MinFunction());</source>
</section>
</body>
</document>
|