|
| |
For those situations where you would like to augment the functionality of XSLT with calls to a procedural language, Xalan-Java supports the creation and use of extension elements and extension functions. Xalan-Java also provides a growing extensions library available for your use. An extension (a collection of elements and functions) inhabits a namespace, either a namespace you declare and designate as an extensions namespace, or one of the predefined namespaces that Xalan-Java provides. For information about XML namespaces, see Namespaces in XML.
Extension elements Unlike a literal result element, which the stylesheet simply transfers to the result tree, an extension element performs an action. For example, you can use the Redirect extension elements shipped with Xalan-Java to redirect portions of your transformation output to one or more files. Extension elements may contain attributes, text nodes, other elements, basically any valid XML. Extension elements may perform quite sophisticated actions, given that the extension routine (the implementation) has direct access to the XSLT processor context object and to the element. In many cases the implementation returns void or null; if it does return a value, that value is placed in the transformation result tree.
Extension functions You can think of extension functions as extending the core library of functions that XPath provides. An extension function passes arguments to the extension implementation and returns a value. You can use extension functions to return values that XSLT can interact with directly (node-set, result tree fragment, string, boolean, and number) as well as values (of any type) that you pass in turn to other extension functions. Extension functions written in Java can also access certain items in the XSLT execution environment through an ExpressionContext interface.
XSLT extensions are specified in the XSLT Recommendation. This document focuses on the Xalan-Java implementation of those requirements, not on XSLT extensions in general. For additional information on extensions, consult the Recommendation or the other resources listed in Getting up to speed with XSLT.
|
| |
Extensions written in Java are directly supported by Xalan-Java. For extensions written in languages other than Java, Xalan-Java uses the Bean Scripting Framework (BSF), an architecture for incorporating scripting into Java applications and applets. BSF allows an application to take advantage of scripting while being independent of any specific scripting language. To date, we have tested extensions implemented in JavaScript. Other languages with BSF support appear in the table below.
BSF requires bsf.jar on the classpath. This JAR file is shipped with Xalan-Java and is required only if you have extensions written in languages other than Java. The additional JAR files or DLLs required to support extensions written in other languages are listed in the table below. These files are available from the sources indicated and are not shipped with Xalan-Java.
Language
|
Version
|
Requirements
|
Mozilla Rhino (JavaScript)
|
1.5
|
js.jar available from
http://www.mozilla.org/rhino
|
NetRexx
|
1.148 up
|
NetRexxC.zip available from http://www2.hursley.ibm.com/netrexx
|
BML
|
2.4
|
bmlall.jar available from http://oss.software.ibm.com/developerworks/projects/bsf
|
JPython
|
1.1-beta3
|
python.jar available from http://www.jpython.org/
|
Jacl
|
1.1.1
|
jacl.jar and tcljava.jar from http://www.scriptics.com/java
|
Win32 ActiveScript langs
JScript, VBScript
|
|
MSVCP60.DLL from Microsoft, appropriate language DLLs from Microsoft
http://msdn.microsoft.com/scripting
|
PerlScript
|
|
ActivePerl from http://www.activestate.com/
|
|
| |
Let's examine a simple example. The stylesheet below uses an extension element and an extension function to transform an element in the XML source into a statement in the output indicating the date by which a customer can expect a response to a given enquiry.
The source element contains a numdays attribute. The extension element contains a multiplier attribute, which is used to set a variable in the extension. The extension function computes the deadline, that is the current date plus numdays * multiplier. So for <deadline numdays="3"/> (in the XML source) and <my-ext:timelapse multiplier="2"/> (in the stylesheet), the extension computes a deadline 6 days from now, and the stylesheet template transforms the deadline element into a string along the lines of <p>We have logged your enquiry and will respond by April 29, 2000 12:07:16 PM EST.</p>
| The extension function could include both numdays and multiplier as arguments, thus bypassing the need for the extension element, but the purpose here is to illustrate the usage pattern for extension elements. |
As you review this stylesheet, please note the following:
- The declaration of the Xalan lxslt namespace, which provides support for the component and
component/script elements:
xmlns:lxslt="http://xml.apache.org/xslt"
- The declaration of a namespace for this extension:
xmlns:my-ext="ext1"
- The designation of this namespace prefix as an extension prefix. This causes any element in the namespace associated with this prefix to be treated as an extension element rather than a literal result element.
extension-element-prefixes="my-ext"
- The lxslt:component with attributes designating the namespace prefix and the elements and
functions this extension provides.
- The lxslt:script subelement with a JavaScript implementation of the extension. For Java
extensions, the lxslt:script element has a src attribute that you set to identify the Java class.
| | | | <?xml version="1.0"?>
<!--Namespaces are global if you set them in the stylesheet element-->
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0"
xmlns:lxslt="http://xml.apache.org/xslt"
xmlns:my-ext="ext1"
extension-element-prefixes="my-ext">
<!--The component and its script are in the lxslt namespace and define the
implementation of the extension.-->
<lxslt:component prefix="my-ext" elements="timelapse" functions="getdate">
<lxslt:script lang="javascript">
var multiplier=1;
// The methods or functions that implement extension elements always take 2
// arguments. The first argument is the XSL Processor context; the second
// argument is the element node.
function timelapse(xslProcessorContext, elem)
{
multiplier=parseInt(elem.getAttribute("multiplier"));
// The element return value is placed in the result tree.
// If you do not want a return value, return null.
return null;
}
function getdate(numdays)
{
var d = new Date();
var totalDays = parseInt(numdays) * multiplier;
d.setDate(d.getDate() + totalDays);
return d.toLocaleString();
}
</lxslt:script>
</lxslt:component>
<xsl:template match="deadline">
<p><my-ext:timelapse multiplier="2"/>We have logged your enquiry and will
respond by <xsl:value-of select="my-ext:getdate(string(@numdays))"/>.</p>
</xsl:template>
</xsl:stylesheet>
| | | | |
|
| | | | Using an extension element | | | | |
| |
Extension elements pass the extension two objects:
You can use the ElemExtensionCall getAttribute(String name) method, for example, to read element attributes in their raw form. Use the getAttribute(String name, Node sourceNode, XSLTEngineImpl processor) method to evaluate the attribute as an attribute value template. Note that the method names are the same but the method signatures are different. For full details, see the Javadoc for the ElemExtensionCall class.
| | | | Implementing an extension element | | | | |
| |
For each extension element in a namespace, the implementation must be a Java method with the following signature, or the scripting language equivalent:
Type element(org.apache.xalan.extensions.XSLProcessorContext,
org.apache.xalan.templates.ElemExtensionCall extensionElement)
where Type designates the return type and element is the local part of the extension element name (the element name without the namespace prefix). In the method signature, you may also use superclasses of the indicated types.
If the extension element is implemented in a loosely typed scripting language, such as JavaScript, the arguments and return value are untyped.
Caution: The value returned by an extension element is placed in the transformation result. If you are not interested in a return value, use a public void Java method or return null from a scripting language function.
Java example: public void myElement
(org.apache.xalan.xslt.XSLProcessorContext,
org.apache.xalan.templates.ElemExtensionCall extensionElement)
JavaScript example: function myElement(xslProcContext, element)
The Redirect extension in the extensions library contains three extension elements.
|
|
| | | | Using extension functions | | | | |
| |
Extension functions may include arguments of any type and return a value of any type.
XSLT recognizes five data types: node-set, result-tree-fragment, string, boolean, and number. You can use XPath expressions to set variables with values of these types. You can also pass literals for string, boolean, and number arguments. If you want to pass an argument of a type that XSLT does not recognize, use another extension function to return an object of that type. The stylesheet that appears in Formatting a date, for example uses extension functions to return a Date object and a SimpleDateFormat object, and then uses these objects to call another extension function.
| | | | Data type mapping and method selection | | | | |
| |
When calling an extension function written in a language other than Java, objects of the following Java classes will always be passed to the extension function:
XSLT Type
|
Java Type
|
Node-Set
|
org.w3c.dom.traversal.NodeIterator
|
String
|
java.lang.String
|
Boolean
|
java.lang.Boolean
|
Number
|
java.lang.Double
|
Result Tree Fragment
|
org.w3c.dom.DocumentFragment
|
Any non-XSLT type is passed without conversion.
When calling an extension function written in Java, the extension function signature can specify any of the indicated Java types, as explained below:
XSLT Type
|
Java Types
|
Node-Set
|
org.w3c.dom.traversal.NodeIterator, org.w3c.dom.NodeList, org.w3c.dom.Node or its subclasses, java.lang.String, java.lang.Object, char, [double, float, long, int, short, byte,] boolean
|
String
|
java.lang.String, java.lang.Object, char, [double, float, long, int, short, byte,] boolean
|
Boolean
|
boolean, [java.lang.Boolean, java.lang.Object,] java.lang.String
|
Number
|
double, java.lang.Double, float, long, int, short,char, byte, boolean, java.lang.String, java.lang.Object
|
Result Tree Fragment
|
org.w3c.dom.traversal.NodeIterator, org.w3c.dom.DocumentFragment, org.w3c.dom.Node or its other subclasses, java.lang.string, java.lang.Object, char, [double, float, long, int, short, byte,] boolean
|
Non-XSLT Type
|
the native type or any of its superclasses, double, float, long, int, short, char, byte, java.lang.String
|
When calling extension functions written in Java, Xalan-Java selects the method to call as follows:
- Xalan-Java selects all methods whose name matches the extension function name as specified in Extension function Java calls.
- From this list of methods, Xalan-Java determines which methods are qualified.
- Each qualified method is assigned a score based on the table shown above. To assign the score to a given method, Xalan-Java examines each of the XSLT argument types in the function invocation in the stylesheet. For each argument, the appropriate row in the table above is selected. Then, the corresponding Java parameter type in the method signature in the Java program is scored. Types which appear earlier in the list are given a higher score. That is, the list appears in order of scoring preference from highest to lowest. Types shown in square brackets have equal priority.
- The method with the highest score is invoked after the arguments are converted to the appropriate type. If more than one method has the highest score, an exception is thrown.
Any extension function written in Java can have a first parameter of type org.apache.xalan.extensions.ExpressionContext . Any method with an ExpressionContext as the first parameter will score higher than any method which does not have an ExpressionContext as a first parameter.
|
| | | | Extension function Java calls | | | | |
| |
The technique for instantiating Java objects and calling Java methods depends on the format of the extension namespace that was declared. See Declare the namespace for the three different formats of namespace declarations for Java extensions. For each namespace format, the section below describes how to instantiate an object, how to invoke an instance method, and how to invoke a static method. The sections below explain, for each syntax, which methods are qualified for method selection as described in the preceeding section.
| |
To create an instance of an object:
prefix:new (args)
where prefix is the extension namespace prefix. A new instance is to be created with the args constructor arguments (if any). All constructor methods are qualified for method selection.
Example: <xsl:variable name="myType"
select="my-class:new()">
To invoke an instance method on a specified object:
prefix:methodName (object, args)
where prefix is the extension namespace prefix and methodName is the name of the method to invoke on object with the args arguments. object must be an object of the class indicated by the namespace declaration. Otherwise, the case shown immediately below will apply. Only instance methods with the name methodName are qualified methods. If a matching method is found, object will be used to identify the object instance and args will be passed to the invoked method.
Example: <xsl:variable name="new-pop"
select="my-class:valueOf($myType, string(@population))">
To invoke an instance method on a default object:
prefix:methodName (args)
where prefix is the extension namespace prefix and methodName is the name of the method to invoke with the args arguments. The first arg, if any, must not be an object of the class indicated by the namespace declaration. Otherwise, the case shown immediately above will apply. Only instance methods with the name methodName are qualified methods. If a matching method is found, a default instance of the class will be created if it does not already exist.
Example: <xsl:variable name="new-pop"
select="my-class:valueOf(string(@population))">
To invoke a static method:
prefix:methodName (args)
where prefix is the extension namespace prefix and methodName is the name of the method to invoke with the args arguments. Only static methods with the name methodName are qualified methods. If a matching method is found, args will be passed to the invoked static method.
Example: <xsl:variable name="new-pop"
select="my-class:printit(string(@population))">
|
| |
To create an instance of an object:
prefix:subpackage.class.new (args)
where prefix is the extension namespace prefix, subpackage is the rest of the package name (the beginning of the package name was in the namespace declaration), and class is the name of the class. A new instance is to be created with the args constructor arguments (if any). All constructor methods are qualified for method selection.
Example: <xsl:variable name="myType"
select="my-package:extclass.new()">
To invoke an instance method on a specified instance:
prefix:methodName (object, args)
where prefix is the extension namespace prefix and methodName is the name of the method to invoke on object with the args arguments. Only instance methods of the object with the name methodName are qualified methods. If a matching method is found, object will be used to identify the object instance and args will be passed to the invoked method.
Example: <xsl:variable name="new-pop"
select="my-package:valueOf($myType, string(@population))">
To invoke a static method:
prefix:subpackage.class.methodName (args)
where prefix is the extension namespace prefix, subpackage is the rest of the package name (the beginning of the package name was in the namespace declaration), class is the name of the class, and methodName is the name of the method to invoke with the args arguments. Only static methods with the name methodName are qualified methods. If a matching method is found, args will be passed to the invoked static method.
Example: <xsl:variable name="new-pop"
select="my-package:extclass.printit(string(@population))">
| Unlike the class format namespace, there is no concept of a default object since the namespace declaration does not identify a unique class. |
|
| |
To create an instance of an object:
prefix:FQCN.new (args)
where prefix is the extension namespace prefix for the Java namespace and FQCN is the fully qualified class name of the class whose constructor is to be called. A new instance is to be created with the args constructor arguments (if any). All constructor methods are qualified for method selection.
Example: <xsl:variable name="myHash"
select="java:java.util.Hashtable.new()">
To invoke an instance method on a specified instance:
prefix:methodName (object, args)
where prefix is the extension namespace prefix and methodName is the name of the method to invoke on object with the args arguments. Only instance methods of the object with the name methodName are qualified methods. If a matching method is found, object will be used to identify the object instance and args will be passed to the invoked method.
Example: <xsl:variable name="new-pop"
select="java:put($myHash, string(@region), $newpop)">
To invoke a static method:
prefix:FQCN.methodName (args)
where prefix is the extension namespace prefix, FQCN is the fully qualified class name of the class whose static method is to be called, and methodName is the name of the method to invoke with the args arguments. Only static methods with the name methodName are qualified methods. If a matching method is found, args will be passed to the invoked static method.
Example: <xsl:variable name="new-pop"
select="java:java.lang.Integer.valueOf(string(@population))">
| Unlike the class format namespace, there is no concept of a default object since the namespace declaration does not identify a unique class. |
|
|
| |
Please keep in mind that all LocationPath expressions return a node-set, even if the expression only returns a single attribute or a text node (node-sets with one member). You can use the XSLT string() function (as in the syntax examples above) to convert a node-set value to string, and the number() function to convert a node-set value to number (a double).
If you want to pass a node-set to an extension function, set up a Java method to accept an
org.w3c.dom.NodeList (or an org.apache.xpath.NodeSet, which implements NodeList, if you want to modify the nodes).
Suppose, for example, you have a myExtensions.ProcessNodes class with the following doSomething method:
public static boolean doSomething(org.w3c.dom.NodeList nList)
Assuming you set up this extension in the node-ext namespace, any of the following extension calls from a stylesheet are syntactically possible:
<!--Process the current node-->
<xsl:variable name="success" select="node-ext:MyExtensions.ProcessNodes.doSomething(.)"/>
<!--Process all nodes in current context-->
<xsl:variable name="success" select="node-ext:MyExtensions.ProcessNodes.doSomething(*)"/>
<!-- Process all nodes -->
<xsl:variable name="success" select="node-ext:MyExtensions.ProcessNodes.doSomething(/*)"/>
<!--Process the foo/baz nodes in current context -->
<xsl:variable name="success" select="node-ext:MyExtensions.ProcessNodes.doSomething(foo/baz)"/>
<!--Process the/foo/baz and /bar/saz nodes -->
<xsl:variable name="success" select="node-ext:MyExtensions.ProcessNodes.doSomething(/foo/baz | /bar/saz)"/>
The NodeList is in fact a list of references into the XML document, so keep in mind that getNextSibling(), for example, gets you the next sibling in the document, which may not be the next Node in the NodeList.
|
|
| | | | Alternative: using the abbreviated syntax for extensions implemented in Java | | | | |
| |
For extension functions and extension elements implemented in Java, Xalan permits an abbreviated syntax. When you use the abbreviated syntax, you do not use an lxslt:component to designate the functions.
The abbreviated syntax supports the use of extension functions and extension elements implemented in Java. You cannot use this syntax with extensions implemented in JavaScript or another scripting language.
| |
Declare the namespace for your extensions using one of the following three formats. The technique for invoking an extension for each format is explained in Extension function Java calls.
class format: xmlns:my-class="xalan://FQCN"
where FQCN is the fully qualified class name.
Examples: xmlns:my-class="xalan://java.util.Hashtable"
xmlns:my-class="xalan://mypackage.myclass"
package format: xmlns:my-class="xalan://PJPN"
where PJPN is a partial java package name. That is, it is the beginning of or the complete name of a java package.
Examples: xmlns:my-package="xalan://java.util"
xmlns:my-package="xalan://mypackage"
Java format: xmlns:java="http://xml.apache.org/xslt/java"
| Although the namespace declarations for the class and package formats are shown with the xalan:// prefix, the current implementation for those formats will simply use the string to the right of the rightmost forward slash as the Java class name. This format is shown in order to comply with W3C recommendations for namespace declarations. |
| The class: prefix which was sometimes required in earlier versions of Xalan-Java is no longer required. |
|
| | | | Example: Formatting a date | | | | |
| |
This example uses extension functions to call the SimpleDateFormat class and the IntDate class. IntDate uses String arguments to set up a Date object:
| | | | import java.util.Date;
import java.util.Calendar;
public class IntDate
{
public static Date getDate(String year, String month, String day)
{
// Date(int, int, int) has been deprecated, so use Calendar to
// set the year, month, and day.
Calendar c = Calendar.getInstance();
// Convert each argument to int.
c.set ( Integer.parseInt(year),
Integer.parseInt(month),
Integer.parseInt(day) );
return c.getTime();
}
} | | | | |
The template transforms date elements with four attributes. For example, it transforms <date format="EEEE, MMM dd, yyyy" year="2000" month="4" day="27"/> into <p>Date: Thursday, April 27, 2000.</p>.
As you review this stylesheet, please keep the following in mind:
- The exclude-result-prefixes stylesheet attribute prevents the java namespace declaration from
appearing in the output.
- The XSLT type returned by any LocationPath expression is node-set, so the XSLT string
function is used to convert the format, year, month, and day attribute values from node-sets to
strings.
- The format attribute provides a String argument for constructing a java.text.SimpleDateFormat
object.
- The IntDate class uses String values provided by the year, month, and day attributes, to set the
date. XSLT can pass number values, but these are converted into doubles.
- The formatter variable holds a SimpleDateFormat object, and the date variable holds a Date object.
XSLT does not understand either of these types, but they are used to call the SimpleDateFormat format
method. In that call, $formatter is the object, and $date is the argument. The syntax for calling
Java constructors and methods is described above in Extension function Java calls.
| | | | <?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0"
xmlns:java="http://xml.apache.org/xslt/java"
exclude-result-prefixes="java">
<!--Other templates for transforming the rest of
the XML source documents-->
<xsl:template match="date">
<xsl:variable name="year" select="string(./@year)"/>
<xsl:variable name="month" select="string(./@month)"/>
<xsl:variable name="day" select="string(./@day)"/>
<xsl:variable name="format" select="string(./@format)"/>
<xsl:variable name="formatter"
select="java:java.text.SimpleDateFormat.new($format)"/>
<xsl:variable name="date"
select="java:IntDate.getDate($year, $month, $day)"/>
<p>Date: <xsl:value-of select="java:format($formatter, $date)"/></p>
</xsl:template>
</xsl:stylesheet>
| | | | |
|
|
| | | | Examples: using Java and JavaScript to implement the same extension | | | | |
| |
This section contains two examples. The first example uses a Java extension to transform a set of name elements into an alphabetical and numbered list. The second example uses a JavaScript script to do the same. Both examples include equivalent extension elements and an extension function.
| |
MyCounter.java
| | | | Import java.util.*;
public class MyCounter {
Hashtable counters = new Hashtable ();
public MyCounter ()
{}
public void init
( org.apache.xalan.extensions.XSLProcessorContext context,
org.apache.xalan.templates.ElemExtensionCall extElem )
{
String name = extElem.getAttribute("name");
String value = extElem.getAttribute("value");
int val;
try
{
val = Integer.parseInt (value);
}
catch (NumberFormatException e)
{
e.printStackTrace ();
val = 0;
}
counters.put (name, new Integer (val));
}
public int read(String name)
{
Integer cval = (Integer) counters.get (name);
return (cval == null) ? 0 : cval.intValue ();
}
public void incr
( org.apache.xalan.extensions.XSLProcessorContext context,
org.apache.xalan.templates.ElemExtensionCall extElem)
{
String name = extElem.getAttribute("name");
Integer cval = (Integer) counters.get(name);
int nval = (cval == null) ? 0 : (cval.intValue () + 1);
counters.put (name, new Integer (nval));
}
}
| | | | |
An XML source document:
| | | | <?xml version="1.0"?>
<doc>
<name first="David" last="Marston"/>
<name first="David" last="Bertoni"/>
<name first="Donald" last="Leslie"/>
<name first="Emily" last="Farmer"/>
<name first="Jack" last="Donohue"/>
<name first="Myriam" last="Midy"/>
<name first="Paul" last="Dick"/>
<name first="Robert" last="Weir"/>
<name first="Scott" last="Boag"/>
<name first="Shane" last="Curcuru"/>
</doc> | | | | |
The stylesheet:
| | | | <?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lxslt="http://xml.apache.org/xslt"
xmlns:counter="MyCounter"
extension-element-prefixes="counter"
version="1.0">
<lxslt:component prefix="counter"
elements="init incr" functions="read">
<lxslt:script lang="javaclass" src="xalan://MyCounter"/>
</lxslt:component>
<xsl:template match="/">
<HTML>
<H1>Names in alphabetical order</H1>
<counter:init name="index" value="1"/>
<xsl:for-each select="doc/name">
<xsl:sort select="@last"/>
<xsl:sort select="@first"/>
<p>
<xsl:text>[</xsl:text>
<xsl:value-of select="counter:read('index')"/>
<xsl:text>]. </xsl:text>
<xsl:value-of select="@last"/>
<xsl:text>, </xsl:text>
<xsl:value-of select="@first"/>
</p>
<counter:incr name="index"/>
</xsl:for-each>
</HTML>
</xsl:template>
</xsl:stylesheet>
| | | | |
Transformation output:
| | | | <HTML>
<H1>Names in alphabetical order</H1>
<p>[1]. Bertoni, David</p>
<p>[2]. Boag, Scott</p>
<p>[3]. Curcuru, Shane</p>
<p>[4]. Dick, Paul</p>
<p>[5]. Donohue, Jack</p>
<p>[6]. Farmer, Emily</p>
<p>[7]. Leslie, Donald</p>
<p>[8]. Marston, David</p>
<p>[9]. Midy, Myriam</p>
<p>[10]. Weir, Robert</p>
</HTML> | | | | |
|
|
|
|