http://xml.apache.org/http://www.apache.org/http://www.w3.org/

Overview

Compiler design

Whitespace
xsl:sort
Keys
Comment design

lang()
Unparsed entities

Runtime

Internal DOM
Namespaces

Translet & TrAX

To-do list

By G. Todd Miller -updated May 11, 2001

See also: Calling XSLTC with the TrAX/JAXP API

Abstract
 

This document describes what I have so far as a prototype for integrating Translets with the JAXP TrAX. In a nutshell, a new Transformer factory class has be written that extends the JAXP SAXTransformerFactory class. The newfactory delivers Translets as Transformers, and creates Templates. Calling the Transformer transform() method will cause the given XML document to be transformed by a translet that has been compiled from the supplied stylesheet. The switch that determines what XSLT processor gets to transform the XML document is based on the value of a JAXP system property for the Transformers factory.


TrAX
 

The Java API for XML Processing (JAXP) includes an XSLT framework based on the Transformation API for XML (TrAX). In a typical JAXP transformation application the steps involved in transforming an XML document with an XSLT stylesheet are: (1) create an instance of the TransformerFactory class, (2) from the factory instance and a given XSLT stylesheet, create a new Transformer object, (3) call the Transformer objects transform() method on a given XML document and a specified Result object. Alternatively, one could also ask the instance of the TransformerFactory for a Templates object given an XSLT stylesheet. From the Templates object, a new Transformer can be created, and again, its transform() method can be called with an XML document and specified Result object.

The code below illustrates a simple JAXP transformation application (Proto.java) that creates the Transformer directly.

import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;

public class Proto {
  public static void main(String[] args){
    Proto app = new Proto();
    app.run(args);
  }

  public void run(String[] args){
    if (args.length != 2) {
      usage();
    }
    String inputFilename = args[0];
    String stylesheet = args[1];
    Transformer transformer;
    TransformerFactory factory = TransformerFactory.newInstance();
    try {
      transformer = factory.newTransformer(new StreamSource(stylesheet));
      transformer.transform(new StreamSource(inputFilename),
                            new StreamResult(System.out));
    } catch (Exception e) {
      // nothing...
    }
  }

  public void usage() {
    System.err.println(
       "Usage: run <xml_file> <xsl_file>");
    System.exit(1);
  }
}

The use of Templates is useful when multiple instances of the same transformer are needed. For example transforming multiple documents by the same transformer, each working in a separate thread. In this case, the TransformerFactory is used to create a Templates object for a given stylesheet. Each Transformer instance is then created by the Templates object. In this way the Templates object can compile the stylesheet once, and each time a new instance of the transformer is asked for, a clone is provided rather than recompile the stylesheet again and again. The Proto class above would be modified as follows:

try {
  Templates templates = factory.newTemplates(new StreamSource(stylesheet));
  transformer = templates.newTransformer();
  transformer.transform(new StreamSource(inputFilename),
                        new StreamResult(System.out));
} catch (Exception e) {
  // nothing...
}

The JAXP TransformerFactory is configurable. The API supports configuring the factory to: (1) use attributes which are passed down to the underlying XSL processor, these are vendor dependent, (2) register an ErrorListener that provides callbacks to handle error and warning message generated by the XSL processor, (3) register an URIResolver which can be used to resolve URIs encountered in xsl:include, xsl:import,anddocument() functions.

The JAXP TransformerFactory can be queried at runtme to discover what features it supports. For example, an application might want to know if a particular factory implementation supports the use of SAX events as a source, or whether it can write out transformation results as a DOM. The factory API queries with the getFeature() method. In the Proto code above, I could add the following code before the try-catch block:

if (!(factory.getFeature(StreamSource.FEATURE)) ||
    !(factory.getFeature(StreamResult.FEATURE))) {
       System.err.println(
         "Stream Source/Result not supported by Transformer Factory\n" +
         "exiting.");
       System.exit(1);
}

Translet Integration
 

The crux of the integration strategy is the pluggable TransformerFactory class. The JAXP specifies that the actual TransformerFactory implementation be controlled by the a Java system property (javax.xml.transformer.TransformerFactory) . This system property can be specified in the usual ways, for example in a properties file or on the command line as a -D optionpassed to thejava engine itself. The strategy involves writing a TransformerFactory for Translets. In the JAXP the TransformerFactory is an abstract class. In Xalan, the system property specifies the implementation class TransformerFactoryImpl (org.apache.xalan.processor.TransformerFactoryImpl). This implementation is an extension of the abstract SAXTransformerFactory class, which in turn is an extension of the abstract TransformerFactory class (javax.xml.transform.sax.SAXTransformerFactory).

In this prototype integration I have constructed an XSLTC TransformerFactory as an extension of the abstract SAXTransformerFactory class.


SAXTransformerFactory for Translets
 

The prototype XSLTC TransformerFactory needs to accomplish the following:

SAXTransformerFactory Methods
 

The methods of the abstract SAXTransformerFactory class have been stubbed out as follows.

public TemplatesHandler newTemplatesHandler() { return null; }
public TransformerHandler newTransformerHandler() { return null; }
public TransformerHandler newTransformerHandler(Source src) {
return null;
}
public TransformerHandler newTransformerHandler(Templates templates) {
return null;
}
public XMLFilter newXMLFilter(Source src) { return null; }
public XMLFilter newXMLFilter(Templates templates) { return null; }

These stubbed out methods will need to be replaced with real implementations.


TransformerFactory Methods
 

The methods of the abstract TransformerFactory class have been stubbed out as follows:

public ErrorListener getErrorListener() { return null; }
public void setErrorListener(ErrorListener listener) { }
public Object getAttribute(String name) { return null; }
public void setAttribute(String name, Object value) { }
public boolean getFeature(String name) { return false; }
public URIResolver getURIResolver() { return null; }
public void setURIResolver(URIResolver resolver) { }
public Source getAssociatedStylesheet(Source src, String media,
String title, String charset) { return null; }
public Templates newTemplates(Source xslSrc) throws
TransformerConfigurationException { return null; }
public Transformer newTransformer() throws
TransformerConfigurationException { return null; }

The methods listed above fall into 4 categories: (1) Transform creation, (2) Templates creation, (3) feature discovery, and (4) others that will remain unimplemented at this time.

Transformer Creation
 

The JAXP specifies a method that takes the stylesheet as a Source object and returns a Transformer.

public Transformer newTransformer(Source xslSrc)

This method needs to return a Transformer to comply with the JAXP API, but also needs to return a Translet. In the XSLTC/Xalan API, a Translet is an interface that is implemented by the runtime abstract class AbstractTranslet (org.apache.xalan.xsltc.Translet and org.apache.xalan.xsltc.runtime.AbstractTranslet, repectively). Therefore, I had to change the AbstractTranslet class so that it will extend the abstract Transformer class while still implementing the Translet interface. Once this is done, then the newTransformer() method of the new TransformerFactory can return a Translet that is a Transformer.

In order to have AbstractTranslet extend Transformer, several abstract methods have to be implemented in AbstractTranslet class. These are:

public void clearParameters() { }
public ErrorListener getErrorListener() { return null; }
public Properties getOutputProperties() throws IllegalArgumentException {
  return null;
}
public String getOutputProperty(String name)
  throws IllegalArgumentException{ return ""; }
//public Object getParameter(String name) { return null; }
public URIResolver getURIResolver() { return null; }
public void setErrorListener(ErrorListener listener)
  throws IllegalArgumentException { }
public void setOutputProperties(Properties props)
  throws IllegalArgumentException { }
public void setOutputProperty(String name, String value)
  throws IllegalArgumentException { }
public void setParameter(String name, Object value) { }
public void setURIResolver(URIResolver resolver) { }
public void transform(Source xmlsrc, Result outputTarget)
  throws TransformerException { ... }

Mapping these methods to some existing ones in AbstractTranslet still has to be done. In particular the transform() method which is explained further in a following section.

Now that XSLTC򳠁bstractTranslet is a Transformer, the new Transformer factory class can implement the newTransformer() method. This method needs to do two fundamental things: (1) create a translet from the given XSLT stylesheet and (2) instantiate the translet so it can be returned as a Transformer.

The code belows hows my current implementation of the newTransformer() method in the new factory class:

public class ToddsTransformerFactoryImpl extends SAXTransformerFactory {
  ...
  public Transformer newTransformer(Source stylesheet) throws
    TransformerConfigurationException
  {
    XSLTC xsltc = new XSLTC();
    xsltc.init();
    String stylesheetName = stylesheet.getSystemId();
    int index = stylesheetName.indexOf(򮒩;
    String transletName = stylesheetName.substring(0,index);
    boolean isSuccessful = true;
    try {
      File file = new File(stylesheetName);
      URL url = file.toURL();
      isSuccessful = xsltc.compile(url);
    } catch (MalformedURLException e) {
        throw new TransformerConfigurationException(
               "URL for stylesheet 򢠫 stylesheetName +
               "򠣡n not be formed.");
    }

    if (!isSuccessful) {
      throw new TransformerConfigurationException(
          "Compilation of stylesheet 򢠫 stylesheetName + "򠦡iled.");
    }
    
    Translet translet = null;
    try {
      Class clazz = Class.forName(transletName);
      translet = (Translet)clazz.newInstance();
      ((AbstractTranslet)translet).setTransletName(transletName);
     } catch (ClassNotFoundException e) {
         throw new TransformerConfigurationException(
              "Translet class 򢠫 transletName + "򠮯t found.");
     } catch (InstantiationException e) {
         throw new TransformerConfigurationException(
              "Translet class 򢠫 transletName +
              "򠣯uld not be instantiated");
     } catch (IllegalAccessException e) {
         throw new TransformerConfigurationException(
             "Translet class 򢠫 transletName + "򠣯uld not be accessed.");
     }
     return (AbstractTranslet)translet;
  }
}

Templates Creation
 

The JAXP specifies a method that takes the stylesheet as a Source object and returns a Templates object.

public Templates newTemplates(Source xslSrc)

To implement this method I needed to create a new class that would be a Templates for Translets object. The new class TransletTemplates (org.apache.xalan.xsltc.runtime.TransletTemplates) implements the Templates interface as is shown below:

package org.apache.xalan.xsltc.runtime;

import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXTransformerFactory;
import org.apache.xalan.xsltc.runtime.AbstractTranslet;
import java.util.Properties;

public class TransletTemplates implements Templates {
  public TransletTemplates(Transformer translet) {
    _translet = (AbstractTranslet)translet;
  }

  public Properties getOutputProperties() { /*TBD*/ return null; }

  public Transformer newTransformer() throws
    TransformerConfigurationException
  {
    if (_translet == null) {
      throw new TransformerConfigurationException(
              "Error: Null Translet");
    }
    return _translet;
  }
  private AbstractTranslet _translet = null;
}

The factory method newTemplates() simply creates a Transformer for the stylesheet, and returns a newTransletTemplates object. The implementation of newTemplates() is shown below:

public class TransformerFactoryImpl extends SAXTransformerFactory {
...
  public Templates newTemplates(Source stylesheet) throws
    TransformerConfigurationException
  {
    Transformer translet = newTransformer(stylesheet);
    return new TransletTemplates(translet);
  }
}

This implementation only satisfies the JAXP, it does not address caching of previously created translets. This type of mechanism belongs in Templates,where a given stylesheet will be compiled one time only, but the generated translet can be cloned to create multiple instances upon demand.


Feature Discovery
 

The JAXP specifies a method that allows programs to discover what features the factory is capable of supporting.

public boolean getFeature(String featureName)

At this time the new transformer factory supports Stream sources and results, and the SAX Transform factory feature. The getFeature() implementation is shown below:

public class TransformerFactoryImpl extends SAXTransformerFactory {
  ...
  public boolean getFeature(String name) {
    if ((StreamSource.FEATURE == name) ||
        (StreamResult.FEATURE == name) ||
        (SAXTransformerFactory.FEATURE == name)) {
        return true;
    } else if ((StreamSource.FEATURE.equals(name))
            || (StreamResult.FEATURE.equals(name))
            || (SAXTransformerFactory.FEATURE.equals(name))) {
         return true;
    } else {
         return false;
    }
  }
}

As the other features are implemented, this methods will need to be updated.


Limitations
 

As you can see, the prototype will compile the stylesheet everytime. This obviously needs to be smarter. This is where the question I already have this translet?eds to be answered. Also for now the translet bytecodes are written out to a File in the current working directory, and then instantiated using a Class.forName, newInstance() pair. This also needs work. Note the return statement, the Translet is cast to an AbstractTranslet which now is a Transformer.




AbstractTranslet򳠴ransform() Method
 

Now the JAXP program (Proto) shown in the first section has a way to get a Transformer factory that can return Translets. The next step of the Proto.java program is calling the transform() method and having the Translet process the given XML document.

This requires some surgery in the AbstractTranslet class again. The AbstractTranslet already has a transform() method, but its signature is not the same as the JAXP Transform signature. In the AbstractTranslet, transform methods expect a DOM argument, in some cases a NodeIterator, and a TransletOutputHandler argument. The JAXP Transform򳠴ransform() signature requires only the XML document as a Source object reference and an outputTarget as a Result object reference. My implementation of the JAXP transform() signature is as shown:

public void transform(Source xmlsrc, Result outputTarget)
  throws TransformerException
{
  doTransform( xmlsrc.getSystemId(),
    ((StreamResult)outputTarget).getOutputStream() );
}

This implementation leverages code derived from XSLTC򳠄efaultRun class. In that class there is a doTransform() method that carries out the work of: (1) parsing the input XML document (using a JAXP SAXParserFactory and SAXParser), (2) creating a DOMImpl (org.apache.xalan.xsltc.dom.DOMImpl), (3) creating a SAXOutputHandler, and finally (4) calling the translet to transform the input XML document (from the DOMImpl).

The code for doTransform is shown below:

public abstract class AbstractTranslet extends Transformer implements Translet{
  ...
  private void doTransform(String xmlDocName, OutputStream ostream) {
    try {
      final Translet translet = (Translet)this; // GTM added

      // Create a SAX parser and get the XMLReader object it uses
      final SAXParserFactory factory = SAXParserFactory.newInstance();
      final SAXParser parser = factory.newSAXParser();
      final XMLReader reader = parser.getXMLReader();

      // Set the DOM򳠄OM builder as the XMLReader򳠓AX2 content handler
      final DOMImpl dom = new DOMImpl();
      reader.setContentHandler(dom.getBuilder());
      // Create a DTD monitor and pass it to the XMLReader object
      final DTDMonitor dtdMonitor = new DTDMonitor();
      dtdMonitor.handleDTD(reader);
      dom.setDocumentURI(xmlDocName);
      /****************
      if (_uri)
        reader.parse(xmlDocName);
      else
      *******************/
      reader.parse("file:"+(new File(xmlDocName).getAbsolutePath()));

      // Set size of key/id indices
      setIndexSize(dom.getSize());
      // If there are any elements with ID attributes, build an index
      dtdMonitor.buildIdIndex(dom, 0, this);

      setUnparsedEntityURIs(dtdMonitor.getUnparsedEntityURIs());
    
      // Transform the document
      String encoding = translet.getOutputEncoding();
      if (encoding == null) encoding = "UTF-8";
    
      //TextOutput textOutput = new TextOutput(System.out, encoding);
      DefaultSAXOutputHandler saxHandler = new
         DefaultSAXOutputHandler(ostream, encoding);
      TextOutput textOutput = new TextOutput(saxHandler, encoding);
      translet.transform(dom, textOutput);
      textOutput.flush();
    }
    catch (TransletException e) {
      ...
    }
    catch (RuntimeException e) {
      ...
    }
    catch (FileNotFoundException e) {
      ...
    }
    catch (MalformedURLException e) {
      ...
    }
    catch (UnknownHostException e) {
      ...
    }
    catch (Exception e) {
      ...
    }
  }
}

Conclusion
 

This is the current state of the integration of Translet and TrAX. The JAXP program illustrated in the first section (Proto.java) compiles and runs with the changes outlined above. The new transformer factory TransformerFactoryImpl supports transformer, templates creation, and the feature discovery mechanism. The transformers returned from the factory are in fact AbstractTranslet objects. The new class TransletTemplates implements the Templates interface for translets. This is the result of a first pass implementation, there are many stubbed out methods that need to be implemented, translet caching in Templates needs to be implemented and support for Source and Result types beyond simple stream types, such as DOM and SAX.


Appendix
 
The Run script
 

By changing the FAC variable I can switch between the two Transformer factory implementations for testing.

#!/bin/ksh
JAXP=/usr/local/jaxp-1.1/jaxp.jar
CRIMSON=/usr/local/jaxp-1.1/crimson.jar
XSLT=/net/bigblock/files18/tmiller/xml-xalan/java/build/classes
XML=/net/bigblock/files18/tmiller/xml-xalan/java/bin/xml.jar
BCEL=/net/bigblock/files18/tmiller/xml-xalan/java/bin/BCEL.jar
JCUP=/net/bigblock/files18/tmiller/xml-xalan/java/bin/java_cup.jar
JCUPRT=/net/bigblock/files18/tmiller/xml-xalan/java/bin/runtime.jar
CLASSPATH=${JAXP}:${CRIMSON}:${XSLT}:${XML}:${BCEL}:${JCUP}:${JCUPRT}:.
FAC=org.apache.xalan.xsltc.runtime.TransformerFactoryImpl
#FAC=org.apache.xalan.processor.TransformerFactoryImpl
java -classpath ${CLASSPATH} \
     -Djavax.xml.transform.TransformerFactory=${FAC} \
     Proto $@

TransformerFactoryImpl.java
 
package org.apache.xalan.xsltc.runtime;

import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.URIResolver;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TemplatesHandler;
import javax.xml.transform.sax.TransformerHandler;

import org.xml.sax.XMLFilter;

import org.apache.xalan.xsltc.Translet;
import org.apache.xalan.xsltc.compiler.XSLTC;
import org.apache.xalan.xsltc.runtime.AbstractTranslet;

import java.io.File;
import java.net.URL;
import java.net.MalformedURLException;

/**
 * Implementation of a JAXP1.1 SAXTransformerFactory for Translets.
 */
public class TransformerFactoryImpl extends SAXTransformerFactory {
  public TransformerFactoryImpl() { /* nothing yet */ }

  //////////////////////////////////////////////////////
  // SAXTransformerFactory (subclass of TransformerFactory)
  //
  public TemplatesHandler newTemplatesHandler() { /*TBD*/ return null; }
  public TransformerHandler newTransformerHandler() { /*TBD*/ return null; }
  public TransformerHandler newTransformerHandler(Source src) {
     /*TBD*/ return null;
  }
  public TransformerHandler newTransformerHandler(Templates templates) {
     /*TBD*/ return null;
  }
  public XMLFilter newXMLFilter(Source src) { /*TBD*/ return null; }
  public XMLFilter newXMLFilter(Templates templates) { /*TBD*/ return null; }

  //
  // End SAXTransformerFactory methods
  //////////////////////////////////////////////////////
  //////////////////////////////////////////////////////
  // TransformerFactory
  //
  public ErrorListener getErrorListener() { /*TBD*/ return null; }
  public void setErrorListener(ErrorListener listener) {/*TBD*/ }
  public Object getAttribute(String name) { /*TBD*/ return null; }
  public void setAttribute(String name, Object value) { /*TBD*/ }
  public boolean getFeature(String name) {
    if ((StreamSource.FEATURE == name) ||
        (StreamResult.FEATURE == name) ||
        (SAXTransformerFactory.FEATURE == name)) {
        return true;
     } else if ((StreamSource.FEATURE.equals(name))
             || (StreamResult.FEATURE.equals(name))
             || (SAXTransformerFactory.FEATURE.equals(name))) {
        return true;
     } else {
        return false;
    }
  }
  public URIResolver getURIResolver() { /*TBD*/ return null; }
  public void setURIResolver(URIResolver resolver) {/*TBD*/ }
  public Source getAssociatedStylesheet(Source src, String media,
       String title, String charset) { /*TBD*/ return null; }
  public Transformer newTransformer() throws
    TransformerConfigurationException { /*TBD*/ return null; }
  //
  // End TransformerFactory methods
  //////////////////////////////////////////////////////
  public Transformer newTransformer(Source stylesheet) throws
  TransformerConfigurationException
  {
    XSLTC xsltc = new XSLTC();
    xsltc.init();
    String stylesheetName = stylesheet.getSystemId();
    int index = stylesheetName.indexOf(򮒩;
    String transletName = stylesheetName.substring(0,index);
    boolean isSuccessful = true;
    try {
      File file = new File(stylesheetName);
      URL url = file.toURL();
      isSuccessful = xsltc.compile(url);
    } catch (MalformedURLException e) {
        throw new TransformerConfigurationException(
            "URL for stylesheet 򢠫 stylesheetName +
            "򠣡n not be formed.");
    }
    if (!isSuccessful) {
      throw new TransformerConfigurationException(
           "Compilation of stylesheet 򢠫 stylesheetName + "򠦡iled.");
    }
    
    Translet translet = null;
    try {
      Class clazz = Class.forName(transletName);
      translet = (Translet)clazz.newInstance();
      ((AbstractTranslet)translet).setTransletName(transletName);
    } catch (ClassNotFoundException e) {
       throw new TransformerConfigurationException(
            "Translet class 򢠫 transletName + "򠮯t found.");
    } catch (InstantiationException e) {
        throw new TransformerConfigurationException(
            "Translet class 򢠫 transletName +
            "򠣯uld not be instantiated");
    } catch (IllegalAccessException e) {
        throw new TransformerConfigurationException(
            "Translet class 򢠫 transletName + "򠣯uld not be accessed.");
    }
    return (AbstractTranslet)translet;
  }
  public Templates newTemplates(Source stylesheet) throws
  TransformerConfigurationException
  {
  Transformer translet = newTransformer(stylesheet);
  return new TransletTemplates(translet);
  }
}

TransletTemplates.java
 
package org.apache.xalan.xsltc.runtime;

import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXTransformerFactory;

import org.apache.xalan.xsltc.runtime.AbstractTranslet;
import java.util.Properties;

/**
 * Implementation of a JAXP1.1 Templates object for Translets.
 */
public class TransletTemplates implements Templates {
  public TransletTemplates(Transformer translet) {
    _translet = (AbstractTranslet)translet;
  }
  public Properties getOutputProperties() { /*TBD*/ return null; }
  public Transformer newTransformer() throws
    TransformerConfigurationException
  {
    if (_translet == null) {
      throw new TransformerConfigurationException(
           "Error: Null Translet");
    }
    return _translet;
  }
  private AbstractTranslet _translet = null;
}

The Makefile
 
JAXP=/usr/local/jaxp-1.1/jaxp.jar
CRIMSON=/usr/local/jaxp-1.1/crimson.jar
XSLT=/net/bigblock/files18/tmiller/xml-xalan/java/build/classes
CLASSPATH=${JAXP}:${CRIMSON}:${XSLT}
SRCS=\
Proto.java
all:
javac -classpath ${CLASSPATH} ${SRCS}



Copyright © 2001 The Apache Software Foundation. All Rights Reserved.