Read XSLT 2.0 and XPath 2.0 Programmer's Reference, 4th Edition Online
Authors: Michael Kay
select=“sql:connect(…)”/>
This means that these external objects can be used with complete type safety, because the Java class hierarchy has been mapped to the XSLT/XPath type hierarchy.
Generally, processors map the common types into their obvious equivalent in the external programming language. For example, in Java, an
xs:double
maps to a Java
double
, an
xs:string
to a
String
, an
xs:boolean
to a Java
boolean
, and so on. The
xs:integer
type is a little tricky because XML Schema doesn't define its maximum range; Saxon maps it to a Java
long
, but other products may make a different choice; for example
java.math.BigInteger
. Bindings for the more common types are defined in the Java Architecture for XML Binding (JAXB, see
http://java.sun.com/xml/downloads/jaxb.html
). The same bindings are used in the draft XQuery API for Java (
www.xquery.com/tutorials/xqj_tutorial/
), and it's quite likely that they will also be adopted by XSLT vendors. The more complex date, time, and duration types, which have no direct equivalent in Java, have been mapped to new classes introduced in JDK 1.5 for the purpose:
Duration
and
XMLGregorianCalendar
, both in package
javax.xml.datatypes
.
With XSLT 1.0 most XSLT processors naturally followed the
weak typing
approach of implicitly converting the supplied parameters in the XPath function call to the required type declared in the Java method. However, with XSLT 2.0 it is more logical to switch to a stricter model aligned with the XPath 2.0 function calling rules, where only very limited conversions between the supplied value and the required type are supported. For backward-compatibility reasons, however, Saxon still supports many implicit conversions, for example allowing a boolean to be supplied where an integer is expected.
When the values passed to an extension function are nodes, rather than atomic values, the data mapping issues become more complicated. The accepted standard for manipulating XML trees in most languages is the DOM, and it's likely that many processors will offer extension functions the ability to manipulate nodes using the DOM interface, even though the DOM does not match the XSLT/XPath data model particularly well. This is discussed in the section
XPath Trees and the DOM
on page 963.
Binding Using External Objects in the Microsoft .NET Processor
With the Microsoft
System.Xml.Xsl
processor, extension function calls are treated as calls to methods on external objects, and the objects themselves must be supplied by the calling application. A detailed explanation of the mechanism is given at
http://support.microsoft.com/kb/323370/EN-US/
. For example, suppose you want to call the method
GetPrice()
on an object of class
Product
in the .NET namespace
Inventory
. You need to choose a namespace URI to refer to this extension object, say
urn:Product
. Then in the calling application, before invoking the XSLT processor, you can do:
Inventory.Product product = catalog.GetProduct(‘12345’);
XsltArgumentList args = new XsltArgumentList();
args.AddExtensionObject(‘urn:Product’, product);
The argument list is supplied as a parameter to the
Transform
method that runs the transformation:
xslt.Transform(xmlDoc, args, Response.OutputStream);
In the stylesheet you can then call methods on this object using a call of the form:
One benefit of this design is that it does not depend on dynamic loading of classes, which means it is likely to be fairly efficient.
Binding to Assemblies in Saxon on .NET
Saxon runs both on Java and on .NET. The .NET version of the product uses a similar approach to the Java product, where the namespace URI part of the extension function name is used to identify the class, and the local part is used to identify the method within that class. The namespace URI, however, is rather more elaborate than that used in the Java case, reflecting the complexity (and power) of the mechanisms for controlling dynamic loading of assemblies in .NET.
Extension functions can be written in any .NET language (C# is most common). To call a method
GetPrice()
in class
Product
in namespace
Inventory
, the general format is:
xmlns:product=“clitype:Inventory.Product”/>
If the assembly containing the code is a system assembly, or if it has been preloaded by the application, this is all that is needed. If the assembly needs to be dynamically loaded then additional information can be supplied in the form of query parameters. For example, to load an assembly that is stored with a strong name in the global assembly cache, you can specify the assembly name, version, and public key token as follows:
“clitype:Inventory.Product?asm=InvApp;ver=5.0.0.1;sn=“b03e5f7e11c50a4b”/>
Saxon on .NET allows both static and instance-level methods to be invoked, in the same way as the Java product, and with similar conventions for mapping the argument types. It also allows property values to be accessed as if the property were a zero-argument method (the name is used as is, no “Get” prefix is added.)
Binding to Extension Functions in Gestalt
The Gestalt XSLT 2.0 processor written by Colin Adams illustrates yet another approach to extension function binding.
Extension functions must be written in Eiffel, and each extension function is implemented as a subclass of
XM_XPATH_SYSTEM_FUNCTION
. The class needs to implement as a minimum a method
evaluate_item()
(for functions with singleton results) or
create_iterator()
(if the result is a sequence), and methods that tell the system what the types of the arguments and result are. The class can also override other methods such as
optimize()
that are called at compile time. For example, this would allow a function that accepts a regular expression as an argument to do some preprocessing of the regular expression at compile time if it is supplied as a string literal.
You then need to create an object representing your library of extension functions as a subclass of
XM_XPATH_FUNCTION_LIBRARY
, and you must register this function library with the XSLT processor via an API call
add_extension_function_library()
before running a transformation.
This approach to binding of extension functions puts a lot more burden on the developer, but it also gives the ability for close integration and high performance. Essentially, it means that extension functions written by users have exactly the same status as functions built into the system by the vendor.
XPath Trees and the DOM
We haven't got space in this book for a detailed description of the DOM interface, but most readers will already have come across it in some form, and it is described in detail in most good books on XML. The DOM provides an object model (and therefore an API) for navigating and manipulating XML data in tree form. Many XSLT processors allow extension functions to access nodes by using the methods defined in the DOM API.
If you want extension functions to access the XSLT source tree, or a secondary input tree that was loaded using the
document()
function, or even a temporary tree constructed during the course of the XSLT transformation, then you can generally do this by passing a node as one of the function arguments. The extension function can then manipulate this node, and other related nodes such as its children and parent, as objects in a DOM structure. It may also be possible for an extension function to construct a new tree, and return it (typically as a DOM Document object) to the calling XPath expression, where it can be manipulated as a secondary input tree in the same way as the result of the XSLT
document()
function. Some products may also allow a DOM that's passed to an extension function to be modified in situ—this is definitely a dubious practice, because it creates a dependency on order of execution, but it's not absolutely prohibited.
The only problem with using the DOM in this way is that there are many small but significant differences between the tree model used by XSLT and XPath, and the tree model defined in the DOM specification. For example, the DOM exposes entity references and CDATA sections, the XPath model doesn't.
This is exacerbated by the fact that there are two different implementation approaches adopted by XSLT vendors: both are perfectly valid and both need to be catered for. Some products, such as Microsoft MSXML3, are DOM-oriented. This processor uses a DOM as its internal tree model, and provides the XPath model as a virtual data structure (a view or wrapper) on top of this. This means, for example, that CDATA sections will be physically present on the tree, and XPath operations such as
following-sibling::node()
will dynamically merge the CDATA contents with the surrounding text nodes. When an extension function is called, such a product will present the native underlying DOM to the called function, CDATA nodes and all. Other products (Saxon is an example, as is the XSLT processor in Microsoft .NET) use an internal data structure that is closely aligned to the XPath model described in Chapter 2. This data structure will have discarded any information that is not needed for XPath processing, such as CDATA sections and entity references. When an external function is called, the situation is now reversed; such a product will provide the DOM interface as a wrapper on top of the native XPath model.
It's impossible to hide all the differences between these two approaches. For example, where the XSLT specifications dictate that whitespace nodes must be stripped from the tree, a DOM-oriented product will probably not remove these nodes physically from the tree, but will simply hide them from XPath view. A product that uses a native XPath tree is likely to remove the unwanted whitespace nodes from the tree while building the tree. This means that with one approach, the stripped whitespace nodes will be present in the DOM as seen by extension functions, and with the other, they will be absent.
Another difference is that with a native XPath tree, adjacent text nodes will be merged (or normalized) into a single node, whereas with a native DOM tree, they may be unnormalized. (Actually, MSXML3 doesn't always normalize text nodes correctly even in the XPath tree view.)
What all this means is that if you want your extension functions to be fully portable between different processors, you have to be aware of these possible differences, and work around them. The following table lists the areas of potential differences between the DOM view and the XPath view.
XPath Node | DOM Node | Correspondence |
Document | Document | One to one. |
Element | Element | One to one. |
Attribute | Attr | The XPath tree never represents namespace declarations as attributes named xmlns or xmlns:* . The DOM might or might not have such Attr nodes. If the source document used entity references within the attribute value, these might or might not be preserved in the DOM. The value of the getSpecified property in the DOM is unpredictable. |
Text | Text | The DOM text nodes might or might not be normalized. If CDATA sections were used in the original document, CDATA nodes might or might not be present in the DOM. If the source document used entity references within the text value, these might or might not be preserved in the DOM. Whitespace nodes that have been stripped as far as XSLT processing is concerned might or might not be present as text nodes in the DOM. |
Processing instruction | Processing instruction | One to one. |
Comment | Comment | One to one. |
Namespace | N/A | There is no direct equivalent in the DOM to XPath's namespace nodes. It is possible in a DOM for elements and attributes to use namespace URIs that are not declared anywhere on the tree. |
N/A | CDATA section | CDATA section nodes may be present on the DOM tree if this is the native data structure used by the processor, but they are unlikely to be present if the processor constructs a DOM from the XPath tree. |
N/A | Entity reference | Entity reference nodes may be present on the DOM tree if this is the native data structure used by the processor, but they are unlikely to be present if the processor constructs a DOM from the XPath tree. |