Development Techniques


Now we’ll discuss how you can use various aspects of Xalan to add XSLT processing to your application. We’ll look at using the JAXP TrAX API, Xalan’s own API, and the XSLTC XSLT compiler built into Xalan. We’ll also cover the use of Xalan-specific XSLT extensions.

TrAX

The TrAX API is specified by the JCP as part of the Java APIs for XML processing, under JSR 63. The main abstraction we’ll be using from TrAX is the transformer, which provides an abstraction of an XSLT processing engine. Transformers are created from a source that provides the stylesheet for the transformation. Once a transformer has been created, you can use the transform method to supply a source containing the XML to be transformed and a result that specifies the destination for the transformed XML.

Transformers are obtained via a TransformerFactory:

  1: /*   2:  *    3:  * SimpleTransformerMain.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch2;   9:   10: import java.io.FileNotFoundException;  11: import java.io.FileOutputStream;  12:   13: import javax.xml.transform.Transformer;  14: import javax.xml.transform.TransformerConfigurationException;  15: import javax.xml.transform.TransformerException;  16: import javax.xml.transform.TransformerFactory;  17: import javax.xml.transform.stream.StreamResult;  18: import javax.xml.transform.stream.StreamSource;  19:   20: public class SimpleTransformerMain {  21:   22:     public static void main(String[] args) {  23:         TransformerFactory xFactory =   24:             TransformerFactory.newInstance();

A number of classes implement the Source interface. StreamSource provide a source that’s derived from Java Readers or InputStreams:

 25:         StreamSource stylesheet = new StreamSource(args[1]);

A transformer embodies a particular transformation (stylesheet), so it’s created from a source that contains the stylesheet:

 26:         Transformer xformer = null;  27:         try {  28:             xformer = xFactory.newTransformer(stylesheet);

You create a second source containing the XML to be transformed (line 32) and then create a result for the transformed XML (lines 33-39). StreamResults output their data to a Java Writer or OutputStream:

 29:         } catch (TransformerConfigurationException tfce) {  30:             tfce.printStackTrace();  31:         }  32:         StreamSource input = new StreamSource(args[0]);  33:         StreamResult output = null;  34:         try {  35:             output =   36:                 new StreamResult(new FileOutputStream(args[2]));  37:         } catch (FileNotFoundException fnfe) {  38:             fnfe.printStackTrace();  39:         } 

Calling the transform method transforms the input:

 40:         try {  41:             xformer.transform(input, output);  42:         } catch (TransformerException xfe) {  43:             xfe.printStackTrace();  44:         }  45:     }  46: }

SAX

Xalan supports both SAX and DOM versions of the TrAX APIs. This means sources and results can be SAX event streams or DOM trees, which makes it easy to add XSLT transformation capability to your existing XML applications. You select the source and result types you need and then hook the transformer into the application. Let’s look at a SAX-based version of the simple transformer program. You want a SAX-based transformation that accepts a SAX event stream for the input XML and outputs a SAX event stream for its result:

  1: /*   2:  *    3:  * SAXTransformerMain.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch2;   9:   10: import java.io.FileNotFoundException;  11: import java.io.FileOutputStream;  12: import java.io.IOException;  13:   14: import javax.xml.parsers.ParserConfigurationException;  15: import javax.xml.parsers.SAXParserFactory;  16: import javax.xml.transform.TransformerConfigurationException;  17: import javax.xml.transform.TransformerFactory;  18: import javax.xml.transform.sax.SAXResult;  19: import javax.xml.transform.sax.SAXSource;  20: import javax.xml.transform.sax.SAXTransformerFactory;  21: import javax.xml.transform.sax.TransformerHandler;  22: import javax.xml.transform.stream.StreamResult;  23: import javax.xml.transform.stream.StreamSource;  24:   25: import org.xml.sax.InputSource;  26: import org.xml.sax.SAXException;  27: import org.xml.sax.XMLFilter;  28: import org.xml.sax.XMLReader;  29:   30: public class SAXTransformerMain {  31:   32:     public static void main(String[] args) {  33:         String input = args[0];  34:         String stylesheet = args[1];  35:         String output = args[2];  36:           37:         SAXParserFactory pFactory =   38:             SAXParserFactory.newInstance();  39:         pFactory.setNamespaceAware(true);  40:         XMLReader r = null;  41:           42:         try {  43:             r = pFactory.newSAXParser().getXMLReader();  44:         } catch (SAXException se) {  45:             se.printStackTrace();  46:         } catch (ParserConfigurationException pce) {  47:             pce.printStackTrace();  48:         }

In lines 37-48, you set up a SAX parser that provides a SAX event stream as the source for the XML you’ll be transforming.

As before, you create an instance of TransformerFactory so that you can create transformers:

 49:           50:         TransformerFactory xFactory =   51:             TransformerFactory.newInstance();

This time, you need to check to make sure the TransformerFactory supports using SAX both as a source and as a result. You do this by calling the getFeature method on the TransformerFactory and testing for SAXSource.FEATURE and SAXResult.FEATURE, respectively. Once you’ve tested for SAX compatibilty, you can downcast the TransformerFactory to a SAXTransformerFactory, which has the SAX related factory methods you need:

 52:           53:         if (xFactory.getFeature(SAXSource.FEATURE) &&   54:             xFactory.getFeature(SAXResult.FEATURE)) {  55:             SAXTransformerFactory sxFactory =   56:                 (SAXTransformerFactory) xFactory;  57:             

The SAX API has an interface that matches the goals for the transformation engine: XMLFilter. XMLFilter takes a SAX event stream as input (via its parent) and produces a SAX event stream as output (by calling methods on a ContentHandler, and so on). The SAXTransformerFactory has additional factory methods for constructing a transformation engine that can be used as an XMLFilter. It also has factory methods for creating a transformation engine that’s only a consumer of a SAX event stream—this engine is a TransformationHandler. You’ll use both of them in this example.

Calling SAXTransformer’s newXMLFilter method creates the XMLFilter tFilter:

 58:             XMLFilter tFilter = null;  59:             try {  60:                 StreamSource stylesheetSource =   61:                     new StreamSource(stylesheet);  62:                 tFilter =   63:                     sxFactory.newXMLFilter(stylesheetSource);  64:             } catch (TransformerConfigurationException tce) {  65:                 tce.printStackTrace();  66:             }  67:             tFilter.setParent(r);

This method takes a source containing the stylesheet as its argument. (We’re using a StreamSource as a convenient way to get the stylesheet. In a later example, we’ll show how to get the stylesheet using SAX.) After creating the filter, you need to set its parent, which is the XMLReader providing the input event stream. In this case, the SAX parser you created earlier is the parent.

You now have the head and body of a SAX-based XSLT pipeline. All you need now is the tail or output stage. TrAX allows you to create identity transformers—that is, transformers that copy the source to the result. They don’t do any transformation (and hence have no stylesheet associated with them). Identity transformations are useful for a number of tasks, such as converting a SAX source to a DOM result and all the permutations of source and result types. You’ll use an identity transform to copy a SAX event stream to a StreamResult, serializing from SAX to an output file:

 68:               69:             TransformerHandler serializer = null;  70:             try {  71:                 serializer = sxFactory.newTransformerHandler();

Because you’re working with SAX events, you want a TransformerHandler that implements an identity transform. This is easily obtained by calling the no-argument version of newTransformerHandler. You then create a StreamResult that contains a FileOutputStream and supply this as the result for the TransformerHandler:

 72:                 FileOutputStream fos =   73:                     new FileOutputStream(output);  74:                 StreamResult result =   75:                     new StreamResult(fos);  76:                 serializer.setResult(result);  77:             } catch (TransformerConfigurationException tce) {  78:                 tce.printStackTrace();  79:             } catch (IllegalArgumentException iae) {  80:                 iae.printStackTrace();  81:             } catch (FileNotFoundException fnfe) {  82:                 fnfe.printStackTrace();  83:             }

All that’s left to do is connect the serializer as the tail of the SAX pipeline by setting the filter’s ContentHandler to be the serializer:

 84:             tFilter.setContentHandler(serializer);  85:               86:             try {  87:                 r.parse(new InputSource(input));  88:             } catch (IOException ioe) {  89:                 ioe.printStackTrace();  90:             } catch (SAXException se) {  91:                 se.printStackTrace();  92:             }  93:         }  94:     }  95: }

Performing the transformation is as simple as telling the head of the pipeline to start parsing an input document.

DOM

As you can see, the SAX version of the program is a significant departure because of the need to fit XSLT into the SAX event-processing model. That’s where all the extra interfaces came from. The DOM version of the program is much more similar to the original version:

  1: /*   2:  *    3:  * DOMTransformerMain.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch2;   9:   10: import java.io.FileNotFoundException;  11: import java.io.FileOutputStream;  12: import java.io.IOException;  13:   14: import javax.xml.parsers.DocumentBuilder;  15: import javax.xml.parsers.DocumentBuilderFactory;  16: import javax.xml.parsers.ParserConfigurationException;  17: import javax.xml.transform.Transformer;  18: import javax.xml.transform.TransformerConfigurationException;  19: import javax.xml.transform.TransformerException;  20: import javax.xml.transform.TransformerFactory;  21: import javax.xml.transform.dom.DOMResult;  22: import javax.xml.transform.dom.DOMSource;  23: import javax.xml.transform.stream.StreamResult;  24: import javax.xml.transform.stream.StreamSource;  25:   26: import org.w3c.dom.Document;  27: import org.xml.sax.SAXException;  28:   29: public class DOMTransformerMain {  30:   31:     public static void main(String[] args) {  32:         String input = args[0];  33:         String stylesheet = args[1];  34:         String output = args[2];  35:           36:         DocumentBuilderFactory bFactory =   37:             DocumentBuilderFactory.newInstance();  38:         bFactory.setNamespaceAware(true);  39:         DocumentBuilder builder = null;  40:           41:         try {  42:             builder = bFactory.newDocumentBuilder();  43:         } catch (ParserConfigurationException pce) {  44:             pce.printStackTrace();  45:         }  46:           47:         Document inputDoc = null;  48:           49:         try {  50:             inputDoc = builder.parse(input);  51:         } catch (SAXException se) {  52:             se.printStackTrace();  53:         } catch (IOException ioe) {  54:             ioe.printStackTrace();  55:         }

To build a DOM-based version of the program, you need to create a DOM tree that can be used as input to the transformation engine. In lines 36-55, you create a DOM tree by using the JAXP DocumentBuilder class to create a document that will be the input to the transformation engine.

Just as in the SAX case, you need to ask the TransformerFactory whether it supports DOM trees as sources and results:

 56:           57:         TransformerFactory xFactory =   58:             TransformerFactory.newInstance();  59:           60:         if (xFactory.getFeature(DOMSource.FEATURE) &&   61:             xFactory.getFeature(DOMResult.FEATURE)) {

The transformer is created using a stylesheet obtained via a StreamSource:

 62:             Transformer xformer = null;  63:             try {  64:                 StreamSource stylesheetSource =   65:                     new StreamSource(stylesheet);  66:                 xformer =   67:                     xFactory.newTransformer(stylesheetSource);  68:             } catch (TransformerConfigurationException tce) {  69:                 tce.printStackTrace();  70:             }

To prepare the DOM tree for use as the source to the transformer, you need to take the extra step of setting the system ID of the DOMSource. Doing so allows any URIs in the DOM document to be resolved. The output of the transformation is also a DOM tree:

 71:             DOMSource inputSource = new DOMSource(inputDoc);  72:             inputSource.setSystemId(input);

The transformer doesn’t know how to create a DOM tree, so you need to get a new DocumentBuilder and use it to create an empty Document node that the transformer can use for output (lines 74-80). This empty Document node is wrapped in a DOMResult so you can pass it to the transformer (line 81). Now you can call the transform method to obtain the transformed XML as a DOM tree (lines 82-87):

 73:               74:             builder = null;  75:             try {  76:                 builder = bFactory.newDocumentBuilder();  77:             } catch (ParserConfigurationException pce) {  78:                 pce.printStackTrace();  79:             }  80:             Document outputDoc = builder.newDocument();  81:             DOMResult outputResult = new DOMResult(outputDoc);  82:             try {  83:                 xformer.transform(inputSource, outputResult);  84:             } catch (TransformerException te) {  85:                 te.printStackTrace();  86:             }  87: 

Once again you use an identity transformer to serialize the output DOM tree into a file, so you create an identity transformer:

 88:             Transformer serializer = null;  89:             try {  90:                 serializer = xFactory.newTransformer();  91:             } catch (TransformerConfigurationException tce) {  92:                 tce.printStackTrace();  93:             }

The nice thing about using the DOM is that you can take the output DOM tree and wrap it as a source for input to the serializer. In SAX you had to create a TransformerHandler and hook up the event pipeline. Here you just wrap the output tree (lines 95-96), create a StreamResult around a FileOutputStream (lines 97-99), and execute the identity transformation (line 100):

 94:             try {  95:                 DOMSource outputSource =  96:                     new DOMSource(outputDoc);  97:                 FileOutputStream fos =   98:                     new FileOutputStream(output);  99:                 StreamResult result = new StreamResult(fos); 100:                 serializer.transform(outputSource, result); 101:             } catch (FileNotFoundException fnfe) { 102:                 fnfe.printStackTrace(); 103:             } catch (TransformerException te) { 104:                 te.printStackTrace(); 105:             } 106:         } 107:      } 108: }

Mix and Match

In the last TrAX example, we’ll show how you can use both SAX and DOM sources as inputs to a transformer. You’ll take the SAX example and modify it so that the stylesheet is provided via a DOM tree. You may wonder why you’d ever want to do such a thing. Here’s a scenario. You’re building a system that will transform XML documents received via a network connection. The fact that the documents are coming over a network connection means you probably want to use SAX, so you get all the benefits of the streaming model. The stylesheet for these documents is generated automatically by a program that takes a DOM tree representation of a stylesheet template and customizes the stylesheet before it’s used. The DOM is a good representation to use when you’re modifying an XML document. This is the scenario the example shows (we’ll only comment on the changes from the original SAX example):

  1: /*   2:  *    3:  * SAXDOMTransformerMain.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch2;   9:   10: import java.io.FileNotFoundException;  11: import java.io.FileOutputStream;  12: import java.io.IOException;  13:   14: import javax.xml.parsers.DocumentBuilder;  15: import javax.xml.parsers.DocumentBuilderFactory;  16: import javax.xml.parsers.ParserConfigurationException;  17: import javax.xml.parsers.SAXParserFactory;  18: import javax.xml.transform.TransformerConfigurationException;  19: import javax.xml.transform.TransformerFactory;  20: import javax.xml.transform.dom.DOMSource;  21: import javax.xml.transform.sax.SAXResult;  22: import javax.xml.transform.sax.SAXSource;  23: import javax.xml.transform.sax.SAXTransformerFactory;  24: import javax.xml.transform.sax.TransformerHandler;  25: import javax.xml.transform.stream.StreamResult;  26:   27: import org.w3c.dom.Document;  28: import org.xml.sax.InputSource;  29: import org.xml.sax.SAXException;  30: import org.xml.sax.XMLFilter;  31: import org.xml.sax.XMLReader;  32:   33: public class SAXDOMTransformerMain {  34:   35:     public static void main(String[] args) {  36:         String input = args[0];  37:         String stylesheet = args[1];  38:         String output = args[2];  39:           40:         SAXParserFactory pFactory =   41:             SAXParserFactory.newInstance();  42:         pFactory.setNamespaceAware(true);  43:         XMLReader r = null;  44:           45:         try {  46:             r = pFactory.newSAXParser().getXMLReader();  47:         } catch (SAXException se) {  48:             se.printStackTrace();  49:         } catch (ParserConfigurationException pce) {  50:             pce.printStackTrace();  51:         }  52:           53:         TransformerFactory xFactory =   54:             TransformerFactory.newInstance();  55:           56:         if (xFactory.getFeature(SAXSource.FEATURE) &&   57:             xFactory.getFeature(SAXResult.FEATURE)) {  58:             SAXTransformerFactory sxFactory =   59:                 (SAXTransformerFactory) xFactory;  60:             

Here’s where the changes are. You need to create a DOM tree that contains the stylesheet, so you get a DocumentBuilder via DocumentBuilderFactory:

 61:             DocumentBuilderFactory bFactory =   62:                 DocumentBuilderFactory.newInstance();  63:             bFactory.setNamespaceAware(true);  64:             DocumentBuilder builder = null;  65:               66:             try {  67:                 builder = bFactory.newDocumentBuilder();  68:             } catch (ParserConfigurationException pce) {  69:                 pce.printStackTrace();  70:             }  71:               72:             Document stylesheetDoc = null;  73:               74:             try {  75:                 stylesheetDoc = builder.parse(stylesheet);

Now you parse the stylesheet into a DOM tree. If you were going to do updates to the DOM representation of the stylesheet, you’d do so right here:

 76:             } catch (SAXException se) {  77:                 se.printStackTrace();  78:             } catch (IOException ioe) {  79:                 ioe.printStackTrace();  80:             }  81:               82:             XMLFilter tFilter = null;  83:             try {

Once you have the DOM form of the stylesheet fully computed, you wrap it in a DOMSource and create a transformation (in this case, an XMLFilter) that uses it as the stylesheet:

 84:                 DOMSource stylesheetSource =   85:                     new DOMSource(stylesheetDoc);  86:                 stylesheetSource.setSystemId(stylesheet);  87:                 tFilter =   88:                     sxFactory.newXMLFilter(stylesheetSource);  89:             } catch (TransformerConfigurationException tce) {  90:                 tce.printStackTrace();  91:             }  92:             tFilter.setParent(r);  93:               94:             TransformerHandler serializer = null;  95:             try {  96:                 serializer = sxFactory.newTransformerHandler();  97:                 FileOutputStream fos =   98:                     new FileOutputStream(output);  99:                 StreamResult result =  100:                     new StreamResult(fos); 101:                 serializer.setResult(result); 102:             } catch (TransformerConfigurationException tce) { 103:                 tce.printStackTrace(); 104:             } catch (IllegalArgumentException iae) { 105:                 iae.printStackTrace(); 106:             } catch (FileNotFoundException fnfe) { 107:                 fnfe.printStackTrace(); 108:             } 109:             tFilter.setContentHandler(serializer); 110:              111:             try { 112:                 tFilter.parse(new InputSource(input)); 113:             } catch (IOException ioe) { 114:                 ioe.printStackTrace(); 115:             } catch (SAXException se) { 116:                 se.printStackTrace(); 117:             } 118:         } 119:     } 120: }

Xalan Features

In addition to the JAXP-defined features like DOMSource.FEATURE, Xalan defines some features that only work when Xalan is the engine powering JAXP (remember that from JDK 1.4 forward, Xalan is the default XSLT engine for the JDK). These features are related to the Xalan Document Table Model (DTM).

The processing model for XSLT is defined in terms of selecting portions of trees and instantiating result elements into the result tree. Processing the document and the stylesheet both involve a lot of tree traversal operations. In order to improve the performance and memory usage of these trees, the Xalan developers created the DTM. The DTM is a non-object-oriented model of a document tree. Instead of creating objects for all the nodes in the document trees, the DTM identifies nodes via an integer handle. Integer handles are also defined and used for URIs, local names, and expanded names. The text content of nodes is left in a large string buffer and referenced by offset and length. The DTM provides a read API that looks somewhat like the DOM API, but with all node and string references replaced by integer handles. In areas where the DOM API was not a good fit for the XPath data model, the semantics of the XPath data model won out. In most cases, you won’t work with the DTM directly. If you’re writing your own extensions, you’ll talk to the DTM via a set of proxy classes that make the DTM look like the W3C DOM.

The other benefit of the DTM is that it can be constructed incrementally. As a result, Xalan can begin to perform transformations even though the entire tree for the input document has not been constructed. This works best when Xerces is being used as the XML parser. Incremental construction is still possible with a parser besides Xerces, but it involves multithreading inside Xalan to make it work.

Now that you have a general idea of what the DTM is, let’s turn back to those Xalan-specific features. You set them by calling the setAttribute method on a TransformerFactory instance. Unlike SAX features, they’re booleans—this means you need to use the constants defined by the Boolean class (Boolean.TRUE and Boolean.FALSE) as values for the attributes.

The first attribute is identified by the constant TransformerFactoryImpl.FEATURE_OPTIMIZE, which is set to true by default. When the optimize feature is on, Xalan performs structural rewrites on the stylesheet in order to improve transformation performance. This is generally a good thing. However, if you’re building an XSLT tool of some kind (say, an editor or debugger or something that needs direct access to the stylesheet structure), then you should set this attribute to false. Otherwise your tool will access the rewritten stylesheet, which could cause confusion for your users.

The second attribute is attached to the constant TransformerFactoryImpl.FEATURE_INCREMENTAL and controls whether transformations are performed incrementally. By default, transforms aren’t done incrementally, which means stylesheet parsing and transformation are performed in the same thread. When this feature is set to true, transformation is performed incrementally. This is implemented in one of two ways depending on the XML parser in use. If Xerces is the XML parser, then Xalan uses Xerces’ native pull parsing API to overlap parsing and transformation. Otherwise, Xalan creates a second thread and runs the XML parser in the second thread. Xalan takes care of multiplexing between its thread and the parsing thread. If you want to use this capability, we suggest that you use Xerces as your parser.

Xalan Specific Features

One of the most useful debugging tools in Xalan is the command-line interface to Xalan. To use this interface, you need a shell window that has the classpath properly set up for Xalan as described earlier in the chapter. One the classpath is correctly set, you can type the following:

java org.apache.xalan.xslt.Process –in <input file>                                     -xsl <stylesheet>                                    -out <output file>                                     <flags>

This command transforms the input file using the stylesheet and leaves the results in the output file. You can also supply some additional flags:

Flag

Meaning

-v

Display the version number.

-xml

Use an XML formatter on output.

-html

Use an HTML formatter on output.

-TT

Trace template execution.

-TG

Trace generation events.

-TS

Trace selection events.

You should definitely use the -html flag if your output is HTML. The three trace flags (-TT, -TG, and -TS) can help you debug a stylesheet that isn’t working the way you think it should. -TT will help you see the order in which templates are being applied, -TS shows what is actually being selected by various location paths, and -TG shows what’s being inserted in to the result tree.

XPath API

You can also use Xalan as an XPath library so that you can retrieve pieces of documents using XPath expressions. Xalan originally implemented its own XPath API. Recently the W3C’s DOM working group has produced an API for accessing a DOM tree via XPath. At the time of this writing, the DOM XPath API is a Candidate Recommendation.

Let’s look at a program that uses the DOM XPath API to select a subtree of a DOM using XPath:

  1: /*   2:  *    3:  * XPathMain.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch2;   9:   10: import java.io.IOException;  11:   12: import javax.xml.parsers.DocumentBuilder;  13: import javax.xml.parsers.DocumentBuilderFactory;  14: import javax.xml.parsers.ParserConfigurationException;  15:   16: import org.apache.xpath.domapi.XPathEvaluatorImpl;  17: import org.w3c.dom.Document;  18: import org.w3c.dom.Node;  19: import org.w3c.dom.xpath.XPathEvaluator;  20: import org.w3c.dom.xpath.XPathNSResolver;  21: import org.w3c.dom.xpath.XPathResult;  22: import org.xml.sax.SAXException;  23:   24:   25: public class XPathMain {  26:   27:     public static void main(String[] args) {  28:         String input = args[0];  29:         String xpath = args[1];  30:         DocumentBuilderFactory bFactory =   31:             DocumentBuilderFactory.newInstance();  32:         bFactory.setNamespaceAware(true);  33:         DocumentBuilder builder = null;  34:         try {  35:             builder = bFactory.newDocumentBuilder();  36:         } catch (ParserConfigurationException pce) {  37:             pce.printStackTrace();  38:         }  39:         Document doc = null;  40:         try {  41:             doc = builder.parse(input);  42:         } catch (SAXException se) {  43:             se.printStackTrace();  44:         } catch (IOException ioe) {  45:             ioe.printStackTrace();  46:         }  47:         

Everything up to this point should be familiar. You’re using JAXP to create the DOM tree you want to query using XPath.

Next you create an XPathEvaluator for the DOM tree using Xalan’s XPathEvaluatorImpl. You also create an XPathNSResolver (more about that after we finish discussing the program):

 48:         XPathEvaluator xpathEngine = new XPathEvaluatorImpl(doc);  49:         XPathNSResolver resolver =   50:             xpathEngine.createNSResolver(doc);  51:         

Once you have the XPathEvaluator, you call its evaluate method, passing it an XPath (in String form), the DOM tree, the XPathNSResolver, a constant telling what the result type should be, and a result node to be filled in. If the result node is null, as is the case in this example, then the XPathEvaluator should create a new node:

 52:         XPathResult result = (XPathResult)   53:             xpathEngine.evaluate(xpath,    54:                                  doc,  55:                                  resolver,   56:                                  XPathResult.ANY_TYPE,  57:                                  null);  58:         

XPathResult provides a variety of methods to access the result, depending on what the actual result is. Here you’re counting on the result to be a node iterator. If you supply the right input file and XPath, that will be true, but you’d want to do much more error checking in a real application. You ask for the first node from the iterator and print it out as long as it’s not null:

 59:         Node n = result.iterateNext();  60:         if (n != null) {  61:             System.out.println(n);  62:         }  63:     }  64: }

We need to expand on two details from the example. The first is the type of XPathResult. In the following table, an iterator produces nodes one at a time. If the document is modified during the iteration, the iteration becomes invalid. A snapshot is a list accessed by index position. Modifying the document won’t invalidate the snapshot, but reevaluating the XPath may yield a different snapshot.

XPathResult Constant Field

XPathResult Accessor Method

Description

ANY_TYPE

Any + getNumberValue()

Any XPath result type.

ANY_UNORDERED_NODE_TYPE

getSingleNodeValue()

A node set accessed as a single node.

BOOLEAN_TYPE

getBooleanValue()

A boolean value.

FIRST_ORDERED_NODE_TYPE

getSingleNodeValue()

A node set accessed as a single node.

ORDERED_NODE_ITERATOR_TYPE

iterateNext()

An iterator that produces nodes in document order.

ORDERED_NODE_SNAPSHOT_TYPE

snapshotItem(int i)

A snapshot list of nodes in document order.

STRING_TYPE

getStringValue

A String value.

UNORDERED_NODE_ITERATOR_TYPE

iterateNext()

An iterator that produces nodes in no particular order.

UNORDERED_NODE_SNAPSHOT_TYPE

snapshotItem(int i)

A snapshot list of nodes in no particular order.

We also need to explain the role of XPathNSResolver. Here’s the problem that XPathNSResolver solves. Assume you have a document that uses namespaces, such as the document in this chapter’s first code listing. If you write an XPath expression over such a document, which namespace prefix should you use in the XPath expression in order to correctly match the elements in the document? XPathNSResolver maps namespace prefixes to namespace URIs. The implementation of XPathNSResolver supplied by Xalan’s XPathEvaluatorImpl builds mappings for the prefixes that appear in the node used to create the XPathEvaluatorImpl. This allows you to use the prefixes that are defined in the input document. You may be in a situation where you process many documents that all use the same namespace (that is, they all use the same namespace URI) but that use different prefixes for the namespace. In this case, your XPath expressions will all fail to select any portion of the tree, because the prefixes in the documents will all be different, and therefore some won’t match the prefix you’re using in your XPath. You can solve this problem by providing your own implementation of the XPathNSResolver interface that provides a mapping from a namespace prefix of your choosing to the namespace URI being used by all the documents.

Xalan Applet Wrapper

One arena where XSLT can be useful is in a Java applet inside a Web browser. This approach uses the computing power of the machine running the browser instead of using up precious CPU cycles on the server machine. The Xalan developers have made it easy to use Xalan in an applet context by providing an applet that wraps Xalan. The wrapper is the class org.apache.xalan.client.XSLTProcessorApplet, and it’s ready to be used in an HTML <applet> tag. XSLTProcessorApplet has three methods you need to know about:

Method

Purpose

setDocumentURL(String)

Specifies the URL of the document to be transformed.

setStyleURL(String)

Specifies the URL of the stylesheet to be used in the transformation.

getHtmlText()

Assuming the document and stylesheet URLs have been set, performs the transformation and returns the result as a String.

You can specify the input document and stylesheet using the setXXXURL methods, or you can specify them as <param> tags in the <applet> tag. Here’s a simple HTML page that uses the XSLTProcessorApplet to transform the books database and render the HTML in the browser:

  1: <html>   2: <head></head>   3: <body>   4: <applet     5:     name="xslapplet"\

JavaScript uses the name in the applet tag to help identify the applet so you can call methods on it.

You need to provide the paths to the jar files that the applet uses:

  6:     code="org.apache.xalan.client.XSLTProcessorApplet.class"   7:     archive="bin/xalan.jar,bin/xml-apis.jar,bin/xercesImpl.jar "   8:     height="0"   9:     width"0"> 

Use the param tag method to specify the documentURL and styleURL:

 10:     <param name="documentURL" value="books.xml"/>  11:     <param name="styleURL" value="books.xslt"/>  12: </applet>  13: <script language="JavaScript">  14:   document.write(document.xslapplet.getHtmlText())  15: </script>  16: </body>

Here you call the applet’s getHtmlText method (the document and style URLs have already been set in the <param> tags) and then write the result into the document. If you used the setDocumentURL and setStyleURL methods to set the document and style URLs, then there would be additional JavaScript calls against document.xslapplet.

Debugging Xalan Problems

In this section we’ll look at a few techniques for tracking down problems in applications that are using Xalan. We’ll focus on three areas: making sure the Xalan configuration is correct (this is mostly the classpath), obtaining the position of transformation failures, and tracing the behavior of an embedded Xalan transformation engine.

The following program is an extended version of the simplest program that uses a Xalan transformer. We’ve modified the program to use Xalan’s EnvironmentCheck class to confirm that the Xalan environment is correctly set up. The program also shows how to install a custom TraceListener that traces the execution of the transformation:

  1: /*   2:  *    3:  * DebugMain.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch2;   9:   10: import java.io.FileNotFoundException;  11: import java.io.FileOutputStream;  12: import java.io.PrintWriter;  13: import java.io.StringWriter;  14: import java.util.TooManyListenersException;  15:   16: import javax.xml.transform.SourceLocator;  17: import javax.xml.transform.Transformer;  18: import javax.xml.transform.TransformerConfigurationException;  19: import javax.xml.transform.TransformerException;  20: import javax.xml.transform.TransformerFactory;  21: import javax.xml.transform.stream.StreamResult;  22: import javax.xml.transform.stream.StreamSource;  23:   24: import org.apache.log4j.BasicConfigurator;  25: import org.apache.log4j.Logger;  26: import org.apache.xalan.trace.TraceListener;  27: import org.apache.xalan.trace.TraceManager;  28: import org.apache.xalan.transformer.TransformerImpl;  29: import org.apache.xalan.xslt.EnvironmentCheck;  30:   31: public class DebugMain {  32:   33:     public static void main(String[] args) {  34:         BasicConfigurator.configure();  35:         Logger log = Logger.getLogger(DebugMain.class);

Lines 34-35 set up log4j, which you use to capture diagnostic output.

This is the beginning of the environment checking code. The Xalan EnvironmentCheck class has a method called checkEnvironment that dumps a status report on the environment to a PrintWriter in addition to returning a boolean that tells whether the environment is okay. You don’t want this output to show up unless you need it, so you create the PrintWriter from a StringWriter. That way you capture the output and can display it if the configuration isn’t right:

 36:           37:         StringWriter sw = new StringWriter();  38:         PrintWriter pw = new PrintWriter(sw);

Lines 39-44 call checkEnvironment and log the environment status if the environment isn’t okay:

 39:         boolean environmentOK =   40:             (new EnvironmentCheck()).checkEnvironment(pw);  41:         if (!environmentOK) {  42:             log.error(sw);  43:             System.exit(1);  44:         }  45:           46:         TransformerFactory xFactory =   47:             TransformerFactory.newInstance();  48:         StreamSource stylesheet = new StreamSource(args[1]);  49:         Transformer xformer = null;  50:         try {  51:             xformer = xFactory.newTransformer(stylesheet);

Xalan provides the TraceListener interface as an aid to debugging embedded Xalan processors and as an entry point for people building XSLT tools based on Xalan. Each Xalan transformer has a TraceManager that takes care of monitoring the various events that occur as Xalan is transforming a document. You can register your own implementation of the TraceListener interface with the TraceManager to get debugging information about that Transformer instance.

The TraceListener facility isn’t a part of the TrAX API, so first you check that the transformer is an instance of the Xalan implementation class and then downcast the transformer so you can access the TraceManager. Once you have the trace manager, you can create an instance of LoggingTraceListener, your custom TraceListener, and register it. We’ll look at the implementation of LoggingTraceListener next. The rest of the code should be familiar.

Here you see how to use the SourceLocator interface to get information about the position of a transformation failure. The key is that you can only obtain a SourceLocator from a TransformerException. Once you have the SourceLocator, you can use it to get the SystemID, line number, and column number of the failure. Note that this information isn’t always exact, but something is always better than nothing:

 52:             if (xformer instanceof TransformerImpl) {  53:                 TransformerImpl xformerImpl =   54:                     (TransformerImpl) xformer;  55:                 TraceManager tMgr =   56:                     xformerImpl.getTraceManager();  57:                 TraceListener logListener =   58:                     new LoggingTraceListener();  59:                 tMgr.addTraceListener(logListener);  60:             }  61:         } catch (TransformerConfigurationException tfce) {  62:             tfce.printStackTrace();  63:         } catch (TooManyListenersException tmle) {  64:             tmle.printStackTrace();  65:         }  66:         StreamSource input = new StreamSource(args[0]);  67:         StreamResult output = null;  68:         try {  69:             output =   70:                 new StreamResult(new FileOutputStream(args[2]));  71:         } catch (FileNotFoundException fnfe) {  72:             fnfe.printStackTrace();  73:         }  74:         try {  75:             xformer.transform(input, output);  76:         } catch (TransformerException xfe) {  77:             SourceLocator locator = xfe.getLocator();  78:             log.error("Transformer Exception at "+  79:                       locator.getSystemId()+" "+  80:                       locator.getLineNumber()+":"+  81:                       locator.getColumnNumber());  82:             log.error(xfe);  83:             xfe.printStackTrace();  84:         }  85:     }  86: }

The TraceListener interface provides a set of callbacks that are fired when Xalan performs specific actions. The callbacks are grouped around three kinds of events: TracerEvents, which are fired when a source node is matched; SelectionEvents, which are fired when a node is selected in the stylesheet tree; and GenerateEvents, which are fired when a node is generated in the result tree. You’ve probably noticed that these classes of events correspond to the tracing flags for the command-line interface to Xalan. This isn’t a coincidence; those tracing flags are implemented via a TraceListener.LoggingTraceListener (shown in the next listing) provides a similar kind of capability, but instead of printing the tracing output to the console, it uses log4j to log important data from each event.

  1: /*   2:  *    3:  * LoggingTraceListener.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch2;   9:   10: import javax.xml.transform.TransformerException;  11:   12: import org.apache.log4j.Logger;  13: import org.apache.xalan.templates.Constants;  14: import org.apache.xalan.templates.ElemTemplate;  15: import org.apache.xalan.templates.ElemTemplateElement;  16: import org.apache.xalan.templates.ElemTextLiteral;  17: import org.apache.xalan.trace.GenerateEvent;  18: import org.apache.xalan.trace.SelectionEvent;  19: import org.apache.xalan.trace.TraceListener;  20: import org.apache.xalan.trace.TracerEvent;  21: import org.apache.xml.serializer.SerializerTrace;  22: import org.w3c.dom.Node;  23:   24: public class LoggingTraceListener implements TraceListener {  25:     Logger log = Logger.getLogger(LoggingTraceListener.class);

In good log4j style, you obtain a logger specifically for this class so that you can turn logging on and off conveniently. In an alternate implementation, you could have three loggers, once for each event type, so that the log4j configuration could control each channel independently. In each event callback, you use a StringBuffer to build up the output that will be logged.

The logging code for GenerateEvents is straightforward; it’s a switch on the various event type constants that can be present in a GenerateEvent. The GenerateEvent types are modelled after SAX’s event callbacks:

 26:   27:     public void generated(GenerateEvent ge) {  28:         StringBuffer sb = new StringBuffer();  29:         String chars = null;  30:         switch (ge.m_eventtype) {  31:             case SerializerTrace.EVENTTYPE_STARTDOCUMENT :  32:                 sb.append("STARTDOCUMENT");  33:                 break;  34:             case SerializerTrace.EVENTTYPE_ENDDOCUMENT :  35:                 sb.append("ENDDOCUMENT");  36:                 break;  37:             case SerializerTrace.EVENTTYPE_STARTELEMENT :  38:                 sb.append("STARTELEMENT: " + ge.m_name);  39:                 break;  40:             case SerializerTrace.EVENTTYPE_ENDELEMENT :  41:                 sb.append("ENDELEMENT: " + ge.m_name);  42:                 break;  43:             case SerializerTrace.EVENTTYPE_CHARACTERS :  44:                 chars = new String(ge.m_characters, ge.m_start,   45:                                    ge.m_length);  46:                 sb.append("CHARACTERS: " + chars);  47:                 break;  48:             case SerializerTrace.EVENTTYPE_CDATA :  49:                 chars =  50:                     new String(ge.m_characters, ge.m_start,   51:                                ge.m_length);  52:                 sb.append("CDATA: " + chars);  53:                 break;  54:             case SerializerTrace.EVENTTYPE_COMMENT :  55:                 sb.append("COMMENT: " + ge.m_data);  56:                 break;  57:             case SerializerTrace.EVENTTYPE_PI :  58:                 sb.append("PI: " + ge.m_name + ", " + ge.m_data);  59:                 break;  60:             case SerializerTrace.EVENTTYPE_ENTITYREF :  61:                 sb.append("ENTITYREF: " + ge.m_name);  62:                 break;  63:             case SerializerTrace.EVENTTYPE_IGNORABLEWHITESPACE :  64:                 sb.append("IGNORABLEWHITESPACE");  65:                 break;  66:         }  67:         log.debug("Generating: " + sb);  68:     }  69: 

For SelectionEvents, you want to know the node in the stylesheet that was selecting a portion of the source tree. In order to get all the information you need here, you have to view the SelectionEvent as an ElemTemplateElement so you can get the name of the element in the stylesheet (line 73-79). The SelectionEvent contains the rest of the information you need (the attribute name and attribute value where the XPath expression occurred). This information is output in lines 78-82:

 70:     public void selected(SelectionEvent se)   71:         throws TransformerException {  72:         StringBuffer sb = new StringBuffer();  73:         ElemTemplateElement ete =   74:             (ElemTemplateElement) se.m_styleNode;  75:           76:         sb.append("<");  77:         sb.append(ete.getNodeName());  78:         sb.append(" ");  79:         sb.append(se.m_attributeName);  80:         sb.append("='");  81:         sb.append(se.m_xpath.getPatternString());  82:         sb.append("'> "); 

In addition to knowing which element in the stylesheet is doing the selecting, you would also like to know which node in the source tree was selected and what its value was. This information is easy to obtain because it’s avaiable from the SelectionEvent, as it should be:

 83:         Node sourceNode = se.m_sourceNode;  84:         sb.append(" ==> { node = ");  85:         sb.append(sourceNode.getNodeName());  86:         sb.append(", content = ");  87:         sb.append(se.m_selection.str());  88:         sb.append(" }");  89:         log.debug("Selecting:  "+sb);  90:     }  91: 

TracerEvents are fired as Xalan walks the elements in the stylesheet. You want to know what elements it’s processing at any given time (lines 95-96). There are two important TracerEvent cases. The first case is when a literal result element is encountered; you want to know the name of the literal result element that was encountered (lines 98-106). In the second case, Xalan matches a template element; you want to know the pattern the template was using and the name of the template, if the template is named (lines 108-117):

 92:     public void trace(TracerEvent te) {  93:         StringBuffer sb = new StringBuffer();  94:   95:         sb.append(te.m_styleNode.getNodeName());  96:         sb.append(" ");  97:   98:         switch (te.m_styleNode.getXSLToken()) {  99:             case Constants.ELEMNAME_TEXTLITERALRESULT: 100:                 ElemTextLiteral etl =  101:                     (ElemTextLiteral) te.m_styleNode; 102:                 char chars[] = etl.getChars(); 103:                 String charString =  104:                     new String(chars,0, chars.length).trim(); 105:                 sb.append(charString); 106:                 sb.append(" ");                 107:                 break; 108:             case Constants.ELEMNAME_TEMPLATE: 109:                 ElemTemplate et = (ElemTemplate) te.m_styleNode; 110:                 if (et.getMatch() != null) 111:                     sb.append("match = '"+ 112:                               et.getMatch().getPatternString()+ 113:                               "'"); 114:                 if (et.getName() != null) 115:                     sb.append("name='"+et.getName()+"" +"'"); 116:                 sb.append(" "); 117:                 break; 118:             default: 119:         }

After you’ve output the element being processed and any element-specific data, you also record the system ID of the stylesheet and the position in the stylesheet that Xalan is currently processing:

120:         sb.append("at "); 121:         sb.append(te.m_styleNode.getSystemId()); 122:         sb.append(" Line "); 123:         sb.append(te.m_styleNode.getLineNumber()); 124:         sb.append(", Column "); 125:         sb.append(te.m_styleNode.getColumnNumber()); 126:         log.debug("Tracing:    "+sb); 127:     } 128: }

Using LoggingTraceListener or something like it can be a big help in resolving weird stylesheet problems in deeply embedded environments such as servlets.

One last remark about debugging techniques: You can also invoke the Xalan EnvironmentCheck functionality from within a stylesheet by using the Xalan extensions mechanism. Here’s a stylesheet that lets you do that:

<?xml version="1.0"?> <xsl:stylesheet      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"      version="1.0"     xmlns:xalan="http://xml.apache.org/xalan"     exclude-result-prefixes="xalan"> <xsl:output indent="yes"/> <xsl:template match="/">   <html>]   <head></head>   <body>     <xsl:copy-of select="xalan:checkEnvironment()"/>   </body>   </html> </xsl:template> </xsl:stylesheet>

XSLTC

Until now, we’ve been discussing Xalan’s XSLT interpreter, which translates the XSLT stylesheet and applies the stylesheet while it’s being translated. Xalan also includes an XSLT compiler, XSLTC, which compiles a stylesheet into Java bytecodes. This bytecode representation of the stylesheet is called a translet. Translets can then be executed to perform transformations. The benefit of this approach is that it skips the cost of translating the stylesheet. If you’re using the same stylesheet over and over, the time spent translating the stylesheet can become significant.

Configuration Issues

To use XSLTC, you need to make sure you have all the appropriate jar files. If you chose the one-jar distribution, then you need to make sure xalan.jar, xml-apis.jar, and xercesImpl.jar are on the classpath. If you selected the two-jar distribution, make sure xsltc.jar, xml-apis.jar, and xercesImpl.jar are on theclasspath. If you’re using JDK 1.4 or later, then you should make sure these jar files are placed in the endorsed standards overrides directory.

Calling XSLTC from TrAX

In most of the TrAX examples, you’ve created Transformer objects using a TransformerFactory. Transformer objects aren’t thread safe. If you have multiple threads that need XSLT transformation services, then you should create one Transformer object for each thread. The most efficient way to do this is to use the TransformerFactory to create a Templates object which you then use to create as many Transformer instances as you need. If you need multiple threads to use multiple transformers, those transformers must be created via a Templates object, regardless of whether you’re using Xalan’s interpretive mode or XSLTC. When you use XSLTC, both transformers and templates are compiled. There is good synergy between using templates to create transformers and using XSLTC, because the situations where you’re using the same transformation on multiple threads are also situations where you’re likely to benefit by compiling your stylesheets into translets.

Let’s start by looking at a program that compiles an XSLT stylesheet into a translet. This program writes the translet class file into the current directory:

  1: /*   2:  *    3:  * XSLTCCompileMain.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch2;   9:   10: import java.util.Properties;  11:   12: import javax.xml.transform.Templates;  13: import javax.xml.transform.TransformerConfigurationException;  14: import javax.xml.transform.TransformerFactory;  15: import javax.xml.transform.stream.StreamSource;  16:   17: public class XSLTCCompileMain {  18:   19:     public static void main(String[] args) {  20:         String stylesheet = args[0];  21:           22:         String KEY = "javax.xml.transform.TransformerFactory";  23:         String VALUE =   24:             "org.apache.xalan.xsltc.trax.TransformerFactoryImpl";  25:         Properties sysProps = System.getProperties();  26:         sysProps.put(KEY,VALUE);  27:         System.setProperties(sysProps);

To use XSLTC, you must set the System property javax.xml.transform.TransformerFactory to org.apache.xalan.xsltc.trax.TransformerFactoryImpl (lines 22-27). After creating a TransformerFactory, you set the XSLT-specific generate-translet attribute to true. This value causes XSLT to write a class file for the translet. By default, translet class files are created in the current directory:

 28:           29:         TransformerFactory xFactory =   30:             TransformerFactory.newInstance();  31:         xFactory.setAttribute("generate-translet",Boolean.TRUE);

The translet is actually created when you call the TransformerFactory’s newTemplates method. You pass a StreamSource containing the stylesheet to newTemplates:

 32:         Templates translet = null;  33:         try {  34:             StreamSource stylesheetSource =   35:                 new StreamSource(stylesheet);  36:             translet =   37:                 xFactory.newTemplates(stylesheetSource);  38:         } catch (TransformerConfigurationException tce) {  39:             tce.printStackTrace();  40:         }  41:      }  42: }

Once you’ve compiled the translet, you can use it in a separate program to perform a transformation; see the following listing.

  1: /*   2:  *    3:  * XSLTCRunMain.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch2;   9:   10: import java.io.FileNotFoundException;  11: import java.io.FileOutputStream;  12: import java.util.Properties;  13:   14: import javax.xml.transform.Templates;  15: import javax.xml.transform.Transformer;  16: import javax.xml.transform.TransformerConfigurationException;  17: import javax.xml.transform.TransformerException;  18: import javax.xml.transform.TransformerFactory;  19: import javax.xml.transform.stream.StreamResult;  20: import javax.xml.transform.stream.StreamSource;  21:   22: public class XSLTCRunMain {  23:   24:     public static void main(String[] args) {  25:         String input = args[0];  26:         String translet = args[1];  27:         String output = args[2];

The translet argument is the name of the class created for the stylesheet. If the stylesheet that was compiled was called books.xslt, the default name for the translet file would be books.class. You should pass books as the value of the translet argument. We’ll look at options for controlling the naming later.

Again, you need to set the system property for the TransformerFactory to use XSLTC.

 28:           29:         String KEY = "javax.xml.transform.TransformerFactory";  30:         String VALUE =   31:             "org.apache.xalan.xsltc.trax.TransformerFactoryImpl";  32:         Properties sysProps = System.getProperties();  33:         sysProps.put(KEY,VALUE);  34:         System.setProperties(sysProps);

This time you need to tell the factory that it should search the classpath for translet class files:

 35:                   36:         TransformerFactory xFactory =   37:             TransformerFactory.newInstance();  38:         xFactory.setAttribute("use-classpath",Boolean.TRUE);

Now you resuscitate the translet from the translet class file, use it to create a template, and use that template to create a transformer:

 39:         Templates templates = null;  40:         Transformer xformer = null;  41:         try {  42:             StreamSource transletSource =   43:                 new StreamSource(translet);  44:             templates = xFactory.newTemplates(transletSource);  45:             xformer = templates.newTransformer();

All that’s left is to call the transformer on the right input document:

 46:         } catch (TransformerConfigurationException tce) {  47:             tce.printStackTrace();  48:         }  49:         try {  50:             StreamSource inputSource = new StreamSource(input);  51:             FileOutputStream fos = new FileOutputStream(output);  52:             StreamResult result = new StreamResult(fos);  53:             xformer.transform(inputSource, result);  54:         } catch (FileNotFoundException fnfe) {  55:             fnfe.printStackTrace();  56:         } catch (TransformerException te) {  57:             te.printStackTrace();  58:         }  59:     }  60: }

XSLTC TransformerFactory Attributes

In addition to the generate-translet and use-classpath attributes, XSLTC defines some attributes (shown in the following table) that can be used to tell the TransformerFactory how to generate the translet. These attributes affect the name and destination of the translet and are effective for a single call of either TransformerFactory.newTemplates or TransformerFactory.newTransfomer. They’re reset to their defaults for the next call.

Attribute

Purpose

Type

Default

translet-name

Specifies the name of the translet.

String

“GregorSamsa”

destination-directory

Specifies the directory where the translet class file is to be written.

String

null

package-name

Specifies the package in which the translet class should be placed.

String

null

jar-name

Specifies a jar file where the translet class file should be written.

XSLTC also defines the following attributes that remain in force until they’re turned off or the program exits.

String

null

auto-translet

If true, compares the timestamp of the translet class file with the timestamp of the stylesheet to determine whether to recompile the translet.

Boolean

Boolean.FALSE

enable-inlining

If true, inline methods generated by templates.

Boolean

Boolean.FALSE

Debug

If true, prints debugging messages.

Boolean

Boolean.FALSE

All the attributes in this section should be set on a TransformerFactory before you call newTemplates or newTransformer.

Smart Transformer Switch

The Smart Transformer Switch allows you to use Xalan interpretive mode for Transformer instances created directly from the TransformerFactory, while using XSLTC for Transformer instances created from a Templates instance. The Templates instance would be created from the same TransformerFactory. You enable this mode by setting the TrAX system property javax.xml.transform.TransformerFactory to the value org.apache.xalan.xsltc.trax.SmartTransformerFactoryImpl.

Command-Line Tools

Xalan provides command-line access to XSLT via a pair of classes, org.apache.xalan.xsltc .cmdline.Compile, and org.apache.xalan.xsltc.cmdline.Transform. These command-line tools can be useful for testing, debugging, and secure deployment. You can invoke the command-line interface to the compiler by typing

java org.apache.xalan.xsltc.cmdline.Compile [options] <stylesheet>

These are the important options to Compile:

  • -o <output>—Specifies the name of the translet class. The default name is taken from <stylesheet>.

  • -d <directory>—Specifies the directory where the translet class should be written.

  • -j <jarfile>—Writes the translet class into a jar file.

  • -p <package name>—Specifies the Java package the translet will be in.

  • -u stylesheet—Specifies the stylesheet via URL instead of filename.

The command-line compiler can be useful for situations where you want the efficiency of translets but don’t want to build compilation capability into your application. You can use the command-line compiler to build and update translets used by the running system.

You invoke the command-line translet runner by typing:

java org.apache.xalan.xsltc.cmdline.Transform [options]       <doc> <translet>

Here are the important options to Transform:

  • -j <jarfile>—Reads the translet from the jar file.

  • -u <document_url>—Used in place of <doc>, specifies the input document via URL.

  • -x—Turns on additional debugging output.

You can use the command-line translet runner to debug translets that have been generated by an embedded XSLTC compiler.

Xalan Extensions

XSLT provides a mechanism for extending the behavior of an XSLT processing engine. Templates in your stylesheet can contain extension elements that are taken from an extension namespace. The extension namespace is specified by the extension-element-prefixes attribute, which can appear on the <xsl:stylesheet> element, any literal result element, or on the extension element itself. The value of this attribute is a space-separated list of namespace prefixes. You must also define the namespace prefix in your stylesheet. The value of executing the extension element is inserted in the result tree.

Another useful type of extension is an extension function. Extension functions aren’t defined by the XSLT specification, but most XSLT processing engines support them in one way or another. The basic idea of extension functions is to expand the set of functions that can appear in XSLT/XPath expressions. We’ll look at both kinds of extensions in relation to Xalan.

Xalan allows both extension elements and extension functions. These extensions may be written in Java. They can also be written in a number of scripting languages, thanks to the use of the Bean Scripting Framework (BSF) from the Apache Jakarta project. BSF adds support for extensions written in JavaScript, NetRexx, Bean Markup Language (BML), Python, and Tool Command Language (TCL). On Windows machines, there is additional support for Win32 Active Scripting Languages (Jscript, VBScript, and PerlScript). To take advantage of BSF languages, you must put bsf.jar onto your classpath. You also need jar files or DLLs for the language you wish to use for the extension. You can find a table of languages and the components they need at http://jakarta.apache.org/bsf/projects.html.

Extension Elements

Xalan includes a few built-in extension elements. The redirect extension allows you to direct the output of a transformation to multiple files. It provides three extension elements: <write>, <open>, and <close>. The <write> extension opens a file, writes its contents to the file, and closes the file. The content of <write> is processed as a normal XSLT template body; it’s just that the output goes to the file specified by the select attribute of <write>. Here’s a sample stylesheet that uses <write> to take the familiar book inventory and copy the XML for each book into a file whose name is the name of the book:

  1: <?xml version="1.0" encoding="UTF-8"?>   2: <xsl:stylesheet version="1.0"    3:   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"   4:   xmlns:books="http://sauria.com/schemas/apache-xml-book/books"   5:   xmlns:redirect="http://xml.apache.org/xalan/redirect"   6:   extension-element-prefixes="redirect">

Lines 5-6 show the declaration of the redirect prefix and its use as an element extension prefix. Now write specifies that the filename where output is being redirected is the title of the book (line 10):

  7:  <xsl:output method="xml" version="1.0" encoding="UTF-8"    8:         indent="yes"/>   9:  <xsl:template match="books:book">  10:   <redirect:write select="books:title">

<xsl:copy-of> is used to make a copy of the entire book element:

 11:     <xsl:copy-of select="."/>  12:   </redirect:write>  13:  </xsl:template>  14: </xsl:stylesheet>

Extension Functions

Xalan’s library of extension functions is more extensive than its library of extension elements. It provides its own set of useful functions, and it provides an implementation of the EXSLT extension functions being developed by the community at www.exslt.org. This is a fairly rich set of extension functions and is the same across XSLT processors that support EXSLT, although for XSLT 1.0 processors, the details of invoking the functions may be different.

Xalan’s own extension functions are written in Java, and the method for accessing them relies on a special notation. You define a namespace prefix you use to call the extension function. You can use one of three formats to bind the namespace: to a specific class, to a specific package, or to Java in general. These formats give you access to successively more functionality:

  • Class format declaration—xmlns:extlib=”xalan://com.sauria.apachexml .XalanExtensionFunctions”

  • Package format declaration—xmlns:saurialib=”xalan://com.sauria.apachexml”

  • Java format declaration—xmlns:java=”http://xml.apache.org/xalan/java"

Once you’ve declared the namespace, you can start calling functions. To keep things simple, let’s assume you’ve used a class declaration. The simplest thing you can do is call a static method on the class. Here’s an example:

<xsl:value-of select="extlib:function(arg1, arg2)"/>

Next you instantiate the class and call a method on it. You instantiate the class as follows:

<xsl:variable name="ext" select="extlib:new()"/>

To invoke a method you do this:

<xsl:variable name="value" select="extlib:method($foo,arg1, arg2)"/>

There are similar machinations if you choose a package format or Java format namespace. You have to supply a little more information to instantiate a class. Here’s how you do it for a package format namespace:

<xsl:variable name="ext" select="saurialib:FooLib.new()"/>

The Java format version is:

<xsl:variable name="ext"                select="com.sauria.apachexml.XalanExtensionFunctions"/>

Xalan's Functions

Xalan provides a set of functions in the org.apache.xalan.lib.NodeInfo class. These static functions allow you to retrieve the systemId, publicId, lineNumber, and columnNumber of the current node. To use these functions, you must use TransformerFactory.setAttribute to set the TransformerFactoryImpl .FEATURE_SOURCE_LOCATION attribute to true. This attribute tells Xalan to provide a SourceLocator to be filled in with the systemId, line number, and column number of nodes in the source document.

Here’s the books stylesheet modified to use NodeInfo to display the line number of each book element:

  1: <?xml version="1.0" encoding="UTF-8"?>   2: <xsl:stylesheet version="1.0"    3:   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"   4:   xmlns:books="http://sauria.com/schemas/apache-xml-book/books"   5:   xmlns:nodeinfo="xalan://org.apache.xalan.lib.NodeInfo"

Here’s the (class format) declaration of the nodeinfo namespace prefix:

  6:   exclude-result-prefixes="books">   7:     <xsl:output method="html" version="4.0" encoding="UTF-8"    8:         indent="yes" omit-xml-declaration="yes"/>   9:     <xsl:template match="books:books">  10:       <html>  11:         <head><title>Book Inventory</title></head>  12:         <body>  13:           <xsl:apply-templates/>  14:         </body>  15:       </html>  16:     </xsl:template>  17:     <xsl:template match="books:book">  18:       <xsl:apply-templates/>  19:       <xsl:text>Line </xsl:text>  20:       <xsl:value-of select="nodeinfo:lineNumber()"/>  21:       <p />  22:     </xsl:template>  23:     <xsl:template match="books:title">  24:       <em><xsl:value-of select="."/></em><br />  25:     </xsl:template>  26:     <xsl:template match="books:author">  27:       <b><xsl:value-of select="."/></b><br />  28:     </xsl:template>  29:     <xsl:template match="books:isbn">  30:       <xsl:value-of select="."/><br />  31:     </xsl:template>  32:     <xsl:template match="books:month">  33:       <xsl:value-of select="."/>,   34:     </xsl:template>  35:     <xsl:template match="books:year">  36:       <xsl:value-of select="."/><br />  37:     </xsl:template>  38:     <xsl:template match="books:publisher">  39:       <xsl:value-of select="."/><br />  40:     </xsl:template>  41:     <xsl:template match="books:address">  42:       <xsl:value-of select="."/><br />  43:     </xsl:template>  44: </xsl:stylesheet>

You call NodeInfo’s lineNumber method.

Xalan has a few other extension functions, but their capabilities are duplicated in the EXSLT libraries, so you’re better off using the EXSLT versions.

EXSLT

The EXSLT library is actually a set of libraries of functions for use in XSLT processors. At the time of this writing, Xalan has support for the following function types:

  • Common—Functions that convert a result tree fragment into a node set and return the XSLT type of an object.

  • Math—Trigonometric functions, abs, log, min, max, power, random, and sqrt.

  • Set—Functions that perform set difference and intersection, determine whether two sets have the same node in common, or find the nodes preceding or following a particular node.

  • Date and time—Date, time, and date/time formatting functions.

  • Dynamic— Functions that perform transitive closure of a node list, map an expression over a node list, determine the min and max of an expression over a node list, and calculate the sum of an expression over a node list.

  • String—Alignment, concatenation, padding, splitting, and tokenizing functions.

Here’s the book stylesheet modified to use the EXSLT date and time library to timestamp the report:

  1: <?xml version="1.0" encoding="UTF-8"?>   2: <xsl:stylesheet version="1.0"    3:   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"   4:   xmlns:books="http://sauria.com/schemas/apache-xml-book/books"   5:   xmlns:datetime="http://exslt.org/dates-and-times"   6:   exclude-result-prefixes="books">   7:     <xsl:output method="html" version="4.0" encoding="UTF-8"    8:         indent="yes" omit-xml-declaration="yes"/>   9:     <xsl:template match="books:books">  10:       <html>  11:         <head><title>Book Inventory</title></head>  12:         <body>  13:           <xsl:apply-templates/>  14:             15:           <xsl:text>As of </xsl:text>  16:           <xsl:value-of select="datetime:dateTime()"/>  17:         </body>  18:       </html>  19:     </xsl:template>  20:     <xsl:template match="books:book">  21:        <xsl:apply-templates/>  22:       <p />  23:     </xsl:template>  24:     <xsl:template match="books:title">  25:       <em><xsl:value-of select="."/></em><br />  26:     </xsl:template>  27:     <xsl:template match="books:author">  28:       <b><xsl:value-of select="."/></b><br />  29:     </xsl:template>  30:     <xsl:template match="books:isbn">  31:       <xsl:value-of select="."/><br />  32:     </xsl:template>  33:     <xsl:template match="books:month">  34:       <xsl:value-of select="."/>,   35:     </xsl:template>  36:     <xsl:template match="books:year">  37:       <xsl:value-of select="."/><br />  38:     </xsl:template>  39:     <xsl:template match="books:publisher">  40:       <xsl:value-of select="."/><br />  41:     </xsl:template>  42:     <xsl:template match="books:address">  43:       <xsl:value-of select="."/><br />  44:     </xsl:template>  45: </xsl:stylesheet>

Writing Extensions

Writing extension elements can be a little tricky. The following version of the book stylesheet that uses a new extension element. The extension element determines how many of a given book you have on hand, based on the book’s ISBN number.

Here you declare the namespace prefixes you’re using. You use the class format for the declaration of the Java class, com.sauria.apachexml.ch3.InventoryExtension. You also tell Xalan that the prefix inv denotes an extension element:

  1: <?xml version="1.0" encoding="UTF-8"?>   2: <xsl:stylesheet version="1.0"    3:   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"   4:   xmlns:books="http://sauria.com/schemas/apache-xml-book/books"   5:   xmlns:xalan="http://xml.apache.org/xalan"   6:   xmlns:inv="xalan://com.sauria.apachexml.ch2.InventoryExtension"   7:   extension-element-prefixes="inv"

Now you use the <inv:onhand> extension element and include as its child the value of the isbn node in the source document. When the stylesheet is processed, you expect a count of the book inventory to be placed here:

  8:   exclude-result-prefixes="books">   9:     <xsl:output method="xml" version="1.0" encoding="UTF-8"   10:         indent="yes"/>  11:     <xsl:template match="books:books">  12:       <html>  13:         <head><title>Book Inventory</title></head>  14:         <body>  15:           <xsl:apply-templates/>  16:         </body>  17:       </html>  18:     </xsl:template>  19:     <xsl:template match="books:book">  20:        <xsl:apply-templates/>  21:       <p />  22:     </xsl:template>  23:     <xsl:template match="books:title">  24:       <em><xsl:value-of select="."/></em><br />  25:     </xsl:template>  26:     <xsl:template match="books:author">  27:       <b><xsl:value-of select="."/></b><br />  28:     </xsl:template>  29:     <xsl:template match="books:isbn">  30:       <xsl:value-of select="."/><br />  31:   32:       <inv:onhand>  33:        <xsl:value-of select="."/>  34:       </inv:onhand>  35:       <xsl:text> on hand</xsl:text><br />  36:     </xsl:template>  37:     <xsl:template match="books:month">  38:       <xsl:value-of select="."/>,   39:     </xsl:template>  40:     <xsl:template match="books:year">  41:       <xsl:value-of select="."/><br />  42:     </xsl:template>  43:     <xsl:template match="books:publisher">  44:       <xsl:value-of select="."/><br />  45:     </xsl:template>  46:     <xsl:template match="books:address">  47:       <xsl:value-of select="."/><br />  48:     </xsl:template>  49: </xsl:stylesheet>

Now let’s look at the Java implementation of the extension element. You define a method onhand whose name is the same as the name of the extension element. Xalan says the arguments to extension element methods are an XSLProcessorContext that contains the useful state of the transformation engine and an ElemExtensionCall representing the extension element.

  1: /*   2:  *    3:  * InventoryExtension.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch2;   9:   10: import javax.xml.transform.TransformerException;  11:   12: import org.apache.xalan.extensions.XSLProcessorContext;  13: import org.apache.xalan.templates.ElemExtensionCall;  14: import org.w3c.dom.Node;  15:   16: public class InventoryExtension {  17:     public String onhand(XSLProcessorContext context,  18:                        ElemExtensionCall extensionElt)   19:                 throws TransformerException {

The logic is simple. You want to take the content of the extension element and pass it to a function that tells you how many books are on hand:

 20:         Node parent = context.getContextNode();  21:         Node value = parent.getFirstChild();  22:         return lookup(value.getNodeValue());

The context’s getContextNode method gives the current node in the source tree, which is <isbn>. When you’re working with extensions, it looks like you’re working with a DOM tree, so you need the Text child of the <isbn> node to pass to the business logic:

 23:     }  24:    25:      private String lookup(String isbn) {  26:         char chars[] = isbn.toCharArray();  27:         int sum = 0;  28:         for (int i = 0; i < chars.length; i++) {  29:             sum += Character.getNumericValue(chars[i]);  30:         }  31:         return Integer.toString(sum);     32:     }  33: }

In this case, the business logic breaks the string into a character array and then returns the sum of the numeric value of the characters as the number of books on hand. You can imagine your JDBC, EJB, or Web service call here.

Writing extension functions is easier than writing extension elements, because in many cases you’re passing simple XSLT types to the Java code implementing the extension function. So, all you have to do is write a class that has a method that implements the functionality you need. The only trick is getting the correct arguments for that method. Xalan is very forgiving about this; it uses an approximation method to figure out whether a method can be called as an extension function. The rules for calling extension functions are complicated, but it’s possible to simplify them by constraining the types you use in your Java functions. To pass an XSLT type from the left side of the following table, give the method argument the type in right column.

XSLT Type

Java Type

Node Set

org.w3c.dom.NodeList

String

java.lang.String

Boolean

java.lang.Boolean

Number

java.lang.Double

Result Tree Fragment

org.w3c.dom.NodeList

XSLTC Extensions

XSLTC doesn’t offer the same level of support as Xalan when it comes to extensions. It doesn’t support extension elements at all. It supports extension functions written in Java, but it doesn’t support extension functions written using the Bean Scripting Framework. The interpretive redirect extension is supported. EXSLT support is also lagging a bit. Of the EXSLT modules that Xalan supports (it doesn’t support them all yet), XSLTC doesn’t support the dynamic functions library. Extension functions are used the same way as in interpreted Xalan.




Professional XML Development with Apache Tools. Xerces, Xalan, FOP, Cocoon, Axis, Xindice
Professional XML Development with Apache Tools: Xerces, Xalan, FOP, Cocoon, Axis, Xindice (Wrox Professional Guides)
ISBN: 0764543555
EAN: 2147483647
Year: 2003
Pages: 95

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net