Contents |
Introduction Building the Source Document The Controller Using XPath Expressions Writing and Registering a Node Handler Patterns The NodeInfo Object |
This document describes how to use Saxon as a Java class library, without making any use of XSLT stylesheets. If you want to know how to control stylesheet processing from a Java application, see using-xsl.html.
Note: The Java API was provided in Saxon long before the XSLT interface. Most of the things that the Java API was designed to do can now be done more conveniently in XSL. Reflecting this, some of the features of the API have been withdrawn as redundant, and the focus in Saxon will increasingly be on doing everything possible through XSLT. The new API for invoking XPath, however, is designed to make Saxon's XPath engine available to non-XSLT applications.
The Java processing model in Saxon is an extension of the XSLT processing model:
When a Java node handler is invoked, it is provided with information about the node via a NodeInfo object (usually you will be processing element nodes, in which case the NodeInfo will be an ElementInfo object). The node handler is also given information about the processing context, and access to a wide range of processing services, via an XSLTContext object.
The NodeInfo object allows navigation around the tree. It also provides facilities to:
The two basic Saxon tree structures, the standard tree and the tiny tree, both implement DOM interfaces as well as Saxon's own NodeInfo interface. However, Saxon will work with other tree structures that implement only the NodeInfo interface: one such structure is the Saxon JDOM Adapter, which provides a Saxon interface to a JDOM tree. (For more information about JDOM, see http://www.jdom.org/.)
The XSLTContext object allows a node handler to:
There are two standard APIs for processing XML documents: the SAX interface, and the DOM. SAX (see http://www.megginson.com/SAX/index.html) is an event-driven interface in which the parser reports things such as start and end tags to the application as they are encountered, while the Document Object Model (DOM) (see http://www.w3.org/dom is a navigational interface in which the application can roam over the document in memory by following relationships between its nodes. Another API, JDOM, is similar in concept to DOM but provides a lighter-weight API that is more closely integrated with the standard Java 2 classes.
Saxon offers a higher-level processing model than either SAX or DOM. It allows applications to be written using a rule-based design pattern, in which your application consists of a set of rules of the form "when this condition is encountered, do this processing". It is an event-condition-action model in which the events are the syntactic constructs of XML, the conditions are XSLT-compatible patterns, and the actions are Java methods. Further, the action taken when these rules are fired may include evaluation of XPath expressions, providing a higher-level access mechanism than raw navigation of the tree.
If you are familiar with SAX, some of the differences in Saxon are:
An earlier release of Saxon allowed a purely serial mode of processing: each node was processed as it was encountered. With experience, this proved too restrictive, and caused the internal architecture to become too complex, so it was withdrawn. It has been replaced with a new facility, preview mode. This is available both with XSL and with the Java API.
Preview mode is useful where the document is too large to fit comfortably in main memory. It allows you to define node handlers that are called as the document tree is being built in memory, rather than waiting until the tree is fully built which is the normal case.
When you define an element as a preview element (using the setPreviewElement() method of the PreviewManager class), its node handler is called as soon as the element end tag is encountered. When the node handler returns control to Saxon, the children of the preview element are discarded from memory.
This means, for example, that if your large XML document consists of a large number of chapters, you can process each chapter as it is read, and the memory available needs to be enough only for (a) the largest individual chapter, and (b) the top-level structure identifying the list of chapters.
When the document tree has been fully built, the node handler for its root element will be called in the normal way.The first thing the application must do is to build the source document, in the form of a tree. This can be done using the JAXP 1.1 interface. A typical sequence is:
System.setProperty("javax.xml.parsers.DocumentBuilderFactory", "net.sf.saxon.om.DocumentBuilderFactoryImpl"); DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance(); dfactory.setNamespaceAware(true); DocumentBuilder docBuilder = dfactory.newDocumentBuilder(); String systemId = new File(sourceFile).toURL().toString(); Node doc = docBuilder.parse(new InputSource(systemId)); |
Alternatively you can use the underlying Saxon classes directly.
The Builder class is used to build a document tree from a SAX InputSource (which must be wrapped inside a javax.xml.transform.sax.SAXSource() object: this object can also define the parser to be used). There are actually two implementations of the builder, which construct different internal data structures: these are the standard builder, net.sf.saxon.tree.TreeBuilder, and the tinytree builder, net.sf.saxon.tinytree.TinyBuilder. The main method of the Builder class is build(). The builder can be serially reused to build further documents, but it should only be used for one document at a time. The builder needs to know about the Stripper if whitespace nodes are to be stripped from the tree, and it needs to know about the PreviewManager if any elements are to be processed in preview mode. The relevant classes can be registered with the builder using the setStripper() and setPreviewManager() methods.
Saxon provides a layer of services on top of a SAX-compliant XML parser. It will work with any Java-based XML parser that implements the SAX1 or SAX2 interface.
You can define the parser to be used by supplying a parser within the SAXSource
object
supplied to the Builder.build()
method. If you don't supply a parser, Saxon will select one
using the JAXP mechanisms, specifically, the system property javax.xml.parsers.DocumentBuilderFactory
.
The mechanism used
at previous releases, namely the configuration file ParserManager.properties
,
is no longer available.
If you want to use different parsers depending on the URI of the document being read,
you can achieve this by writing a URIResolver
that nominates the parser to be used for each
input file.
Processing is controlled by a class called the Controller. Some of the functions of this class are
relevant only to XSLT transformation, but most can also be used when Saxon is used purely from Java.
Each application run must instantiate a new Controller. Saxon's Controller class implements
the JAXP 1.1 java.xml.transform.Transformer
interface.
Using a Controller is not absolutely essential. You need it if you want to register node handlers, if you want to evaluate any but the simplest XPath expressions, or if you want to use the Saxon Outputter to generate your output file.
There are several classes used to define the kind of processing you want to perform. These are the RuleManager for registering template rules, the KeyManager for registering key definitions, the PreviewManager for registering preview elements, the Stripper for registering which elements are to have whitespace nodes stripped, and the DecimalFormatManager for registering named decimal formats. These classes can all be reused freely, and they are thread safe once the definitions have been set up. All of these objects are registered with the Controller using methods such as setRuleManager() and setKeyManager().
The Controller class is used to process a document tree by applying registered node handlers. Its main method is run(). The controller is responsible for navigating through the document and calling user-defined handlers which you associate with each element or other node type to define how it is to be processed. The controller can also be serially reused, but should not be used to process more than one document at a time. The Controller needs to know about the RuleManager to find the relevant node handlers to invoke. If keys are used it will need to know about the KeyManager, and if decimal formats are used it will need to know about the DecimalFormatManager. These classes can be registered with the Controller using setRuleManager(), setKeyManager(), and setDecimalFormatManager() respectively. If preview mode is used, the PreviewManager will need to know about the Controller, so it has a setController() method for this purpose.
A new API has been introduced for executing XPath expressions. This is simpler and safer than the
API provided in previous releases, which was essentially improvised from implementation classes rather
than being designed top-down as an interface suitable for application use. The API is loosely modelled
on the proposed DOM Level 3 API for XPath. For full documentation, see the Javadoc description of package
net.sf.saxon.xpath
The new API uses the class net.sf.saxon.xpath.XPathEvaluator
. This class provides a few
simple configuration interfaces to set the source document, the static context, and the context node,
plus a number of methods for evaluating XPath expressions. The static context can be omitted if the
expression does not use namespaces, external variables, or extension functions. If the expression uses
namespaces, an instance of StandaloneContext can be supplied, allowing the required namespaces to be
declared either explicitly, or by reference to the in-scope namespaces of some Node.
There are two methods for direct evaluation, evaluate() which returns a List containing the result of the expression (which in general is a sequence), and evaluateSingle() which returns the first item in the result (this is appropriate where it is known that the result will be single-valued). The results are returned as NodeInfo objects in the case of nodes, or as objects of the most appropriate Java class in the case of atomic values: for example, Boolean, Double, or String in the case of the traditional XPath 1.0 data types.
It is also possible to prepare an XPath expression for subsequent execution, using the createExpression() method on the XPathEvaluator class. This is worthwhile where the same expression is to be executed repeatedly. The compiled expression is represented by an instance of the class net.sf.saxon.xpath.XPathExpression, and it can be executed repeatedly, with different context nodes. However, the compiled expression is bound to one particular source document (this is to ensure that the same NamePool is used).
The design principle of this API is to minimize the number of Saxon classes that need to be used. Apart from the NodeInfo interface, which is needed when manipulating Saxon trees, only the four classes XPathProcessor, XPathExpression, StandaloneContext, and XPathException are needed. For convenience, XPathException and StandaloneContext have been moved to the net.sf.saxon.xpath package.
If you want to use extension functions or variables you will need to create your own implementation of StaticContext. Although this interface has been greatly simplified, this is still not to be attempted lightly.
The old APIs for executing expressions still exist for the time being, but they are likely to be less stable.
You can register a node handlers that will be called to process each node, in the same way as template rules are used in XSLT. They node handler can choose whether or not subsidiary elements should be processed (by calling applyTemplates()), and can dive off into a completely different part of the document tree before resuming. A user-written node handler must implement the NodeHandler interface.
To register a node handler, create a RuleManager, register the node handler with it using its setHandler() method, and regsiter the RuleManager with the Controller by calling the Controller's setRuleManager() method.
Always remember that if you want child elements to be processed recursively, your node handler must call the applyTemplates() method.
A node handler can write to the current output destination. The controller maintains a current outputter. Your node handler can switch output to a new destination by calling changeOutputDestination(), and can revert to the previous destination by calling resetOutputDestination(). This is useful both for splitting an input XML document into multiple XML documents, and for creating output fragments that can be reassembled in a different order for display. Details of the output format required must be set up in a Properties object, which is supplied as a parameter to changeOutputDestination().
The node handler is supplied with an NodeInfo object which provides information about the current node, and with a Context object that gives access to a range of standard services such an Outputter object which includes a write() method to produce output.
Normally you will write one node handler for each type of element, but it is quite possible to use the same handler for several different elements. You can also write completely general-purpose handlers. You define which elements will be handled by each element handler using a pattern, exactly as in XSLT.
You only need to provide one method for the selected node type. This is:
start() | This is called when the node is encountered in the tree. The NodeInfo object
passed gives you information about the relevant node. You can save information
for later use if required, using one of several techniques:
|
Patterns are used in the setHandler() interface to define which nodes a particular handler applies to. Patterns used in the Saxon Java API have exactly the same form as in XSLT.
The detailed rules for patterns can be found in patterns.html.
Patterns are represented in the API by the class net.sf.saxon.pattern.Pattern respectively. It operates in much the same way as the Expression class introduced earlier. There is a static method to create a Pattern from a String, and a method matches() that tests whether a particular node matches a pattern.
When you create a Pattern using the method Pattern.make() you must supply a StaticContext object. This object provides the information needed to interpret certain patterns: for example, it provides the ability to convert a namespace prefix within the expressions into a URI. In an XSLT stylesheet, the StaticContext provides information the expression can get from the rest of the stylesheet; in a Java application, this is not available, so you must provide the context yourself. If you don't supply a StaticContext object, a default context is used: this will prevent you using context-dependent constructs such as variables and namespace prefixes.
The NodeInfo object represents a node of the XML document. It has a subclass DocumentInfo to represent the root node, but all other nodes are represented by NodeInfo itself. These follow the XPath data model closely.
In earlier releases, NodeInfo extended the DOM interface Node. This is no longer the case; it was changed to make it easier to integrate Saxon with other XML tree representations uch as JDOM. However, the main Saxon implementations of the NodeInfo interface continue to also implement the DOM Node interface, so you can still use DOM methods by casting the concrete node object to a DOM class.
The NodeInfo object provides node handlers with information about the node. The most commonly-used methods include:
getNodeType() | gets a short identifying the node type. The values are consistent with those used in the DOM. |
getDisplayName(), getLocalName(), getPrefix(), getURI() | These methods get the name of the element, or its various parts. The getDisplayName() method returns the QName as used in the original source XML. |
getAttributeValue() | get the value of a specified attribute, as a String. |
getStringValue() | get the string value of a node, as defined in the XPath data model |
getParent() | get the NodeInfo representing the parent element, (which will be a DocumentInfo object if this is the outermost element). |
getEnumeration() | returns an SequenceIterator object that can be used to iterate over the nodes on any of the XPath axes. The first argument is an integer identifying the axis; the second is a NodeTest (a simple form of pattern) which can be used to filter the nodes on the axis. Supply AnyNodeTest.getInstance() if you want all the nodes on the axis. |
Michael H. Kay
20 April 2002