Clover coverage report - Clover results for XOM 1.2d1
Coverage timestamp: Wed Feb 8 2006 08:31:33 EST
file stats: LOC: 454   Methods: 14
NCLOC: 145   Classes: 2
 
 Source file Conditionals Statements Methods TOTAL
XSLTransform.java 92.9% 98.4% 100% 97.2%
coverage coverage
 1    /* Copyright 2002-2005 Elliotte Rusty Harold
 2   
 3    This library is free software; you can redistribute it and/or modify
 4    it under the terms of version 2.1 of the GNU Lesser General Public
 5    License as published by the Free Software Foundation.
 6   
 7    This library is distributed in the hope that it will be useful,
 8    but WITHOUT ANY WARRANTY; without even the implied warranty of
 9    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 10    GNU Lesser General Public License for more details.
 11   
 12    You should have received a copy of the GNU Lesser General Public
 13    License along with this library; if not, write to the
 14    Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 15    Boston, MA 02111-1307 USA
 16   
 17    You can contact Elliotte Rusty Harold by sending e-mail to
 18    elharo@metalab.unc.edu. Please include the word "XOM" in the
 19    subject line. The XOM home page is located at http://www.xom.nu/
 20    */
 21   
 22    package nu.xom.xslt;
 23   
 24    import java.util.HashMap;
 25    import java.util.Iterator;
 26    import java.util.Map;
 27   
 28    import javax.xml.transform.ErrorListener;
 29    import javax.xml.transform.OutputKeys;
 30    import javax.xml.transform.Source;
 31    import javax.xml.transform.Templates;
 32    import javax.xml.transform.Transformer;
 33    import javax.xml.transform.TransformerConfigurationException;
 34    import javax.xml.transform.TransformerException;
 35    import javax.xml.transform.TransformerFactoryConfigurationError;
 36    import javax.xml.transform.TransformerFactory;
 37   
 38    import org.xml.sax.SAXParseException;
 39   
 40    import nu.xom.Document;
 41    import nu.xom.Element;
 42    import nu.xom.NodeFactory;
 43    import nu.xom.Nodes;
 44    import nu.xom.XMLException;
 45   
 46    /**
 47    * <p>
 48    * Serves as an interface to a TrAX aware XSLT processor such as Xalan
 49    * or Saxon. The following example shows how to apply an XSL
 50    * Transformation to a XOM document and get the transformation result
 51    * in the form of a XOM <code>Nodes</code>:</p>
 52    * <blockquote><pre>public static Nodes transform(Document in)
 53    * throws XSLException, ParsingException, IOException {
 54    * Builder builder = new Builder();
 55    * Document stylesheet = builder.build("mystylesheet.xsl");
 56    * XSLTransform transform = new XSLTransform(stylesheet);
 57    * return transform.transform(doc);
 58    * } </pre></blockquote>
 59    *
 60    * <p>
 61    * XOM relies on TrAX to perform the transformation.
 62    * The <code>javax.xml.transform.TransformerFactory</code> Java
 63    * system property determines which XSLT engine TrAX uses. Its
 64    * value should be the fully qualified name of the implementation
 65    * of the abstract <code>javax.xml.transform.TransformerFactory</code>
 66    * class. Values of this property for popular XSLT processors include:
 67    * </p>
 68    * <ul>
 69    * <li>Saxon 6.x:
 70    * <code>com.icl.saxon.TransformerFactoryImpl</code>
 71    * </li>
 72    * <li>Saxon 7.x and 8.x:
 73    * <code>net.sf.saxon.TransformerFactoryImpl</code>
 74    * </li>
 75    * <li>Xalan interpretive:
 76    * <code>org.apache.xalan.processor.TransformerFactoryImpl</code>
 77    * </li>
 78    * <li>Xalan XSLTC:
 79    * <code>org.apache.xalan.xsltc.trax.TransformerFactoryImpl</code>
 80    * </li>
 81    * <li>jd.xslt:
 82    * <code>jd.xml.xslt.trax.TransformerFactoryImpl</code>
 83    * </li>
 84    * <li>Oracle:
 85    * <code>oracle.xml.jaxp.JXSAXTransformerFactory</code>
 86    * </li>
 87    * <li>Java 1.5 bundled Xalan:
 88    * <code>com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl</code>
 89    * </li>
 90    * </ul>
 91    * <p>
 92    * This property can be set in all the usual ways a Java system
 93    * property can be set. TrAX picks from them in this order:</p>
 94    * <ol>
 95    * <li>The most recent value specified by invoking
 96    * <code>System.setProperty("javax.xml.transform.TransformerFactory",
 97    * "<i><code>classname</code></i>")</code></li>
 98    * <li>The value specified at the command line using the
 99    * <samp>-Djavax.xml.transform.TransformerFactory=<i>classname</i></samp>
 100    * option to the <b>java</b> interpreter</li>
 101    * <li>The class named in the <code>lib/jaxp.properties</code>
 102    * properties file in the JRE directory, in a line like this one:
 103    * <pre>javax.xml.transform.TransformerFactory=<i>classname</i></pre>
 104    * </li>
 105    * <li>The class named in the
 106    * <code>META-INF/services/javax.xml.transform.TransformerFactory</code>
 107    * file in the JAR archives available to the runtime</li>
 108    * <li>Finally, if all of the above options fail,
 109    * a default implementation is chosen. In Sun's JDK 1.4.0 and 1.4.1,
 110    * this is Xalan 2.2d10. In JDK 1.4.2, this is Xalan 2.4.
 111    * In JDK 1.4.2_02, this is Xalan 2.4.1.
 112    * In JDK 1.4.2_03, 1.5 beta 2, and 1.5 RC1 this is Xalan 2.5.2.
 113    * In JDK 1.4.2_05, this is Xalan 2.4.1. (Yes, Sun appears to have
 114    * reverted to 2.4.1 in 1.4.2_05.)
 115    * </li>
 116    * </ol>
 117    *
 118    * @author Elliotte Rusty Harold
 119    * @version 1.2d1
 120    */
 121    public final class XSLTransform {
 122   
 123   
 124    /**
 125    * <p>
 126    * The compiled form of the XSLT stylesheet that this object
 127    * represents. This can be safely used across multiple threads
 128    * unlike a <code>Transformer</code> object.
 129    * </p>
 130    */
 131    private Templates templates;
 132    private NodeFactory factory;
 133    private Map parameters = new HashMap();
 134    private static ErrorListener errorsAreFatal = new FatalListener();
 135   
 136   
 137    private static class FatalListener implements ErrorListener {
 138   
 139  20 public void warning(TransformerException exception) {}
 140   
 141  78 public void error(TransformerException exception)
 142    throws TransformerException {
 143  78 throw exception;
 144    }
 145   
 146  153 public void fatalError(TransformerException exception)
 147    throws TransformerException {
 148  153 throw exception;
 149    }
 150   
 151    }
 152   
 153    // I could use one TransformerFactory field instead of local
 154    // variables but then I'd have to synchronize it; and it would
 155    // be hard to change the class used to transform
 156   
 157   
 158    /**
 159    * <p>
 160    * Creates a new <code>XSLTransform</code> by
 161    * reading the stylesheet from the specified source.
 162    * </p>
 163    *
 164    * @param source TrAX <code>Source</code> object from
 165    * which the input document is read
 166    *
 167    * @throws XSLException when an <code>IOException</code>,
 168    * format error, or something else prevents the stylesheet
 169    * from being compiled
 170    */
 171  539 private XSLTransform(Source source) throws XSLException {
 172   
 173  539 try {
 174  539 TransformerFactory factory
 175    = TransformerFactory.newInstance();
 176  539 factory.setErrorListener(errorsAreFatal);
 177  539 this.templates = factory.newTemplates(source);
 178    }
 179    catch (TransformerFactoryConfigurationError error) {
 180  0 throw new XSLException(
 181    "Could not locate a TrAX TransformerFactory", error
 182    );
 183    }
 184    catch (TransformerConfigurationException ex) {
 185  104 throw new XSLException(
 186    "Syntax error in stylesheet", ex
 187    );
 188    }
 189   
 190    }
 191   
 192   
 193    /**
 194    * <p>
 195    * Creates a new <code>XSLTransform</code> by
 196    * reading the stylesheet from the supplied document.
 197    * </p>
 198    *
 199    * @param stylesheet document containing the stylesheet
 200    *
 201    * @throws XSLException when the supplied document
 202    * is not syntactically correct XSLT
 203    */
 204  331 public XSLTransform(Document stylesheet) throws XSLException {
 205  331 this(stylesheet, new NodeFactory());
 206    }
 207   
 208   
 209    /**
 210    * <p>
 211    * Creates a new <code>XSLTransform</code> by
 212    * reading the stylesheet from the supplied document.
 213    * The supplied factory will be used to create all nodes
 214    * in the result tree, so that a transform can create
 215    * instances of subclasses of the standard XOM classes.
 216    * Because an XSL transformation generates a list of nodes rather
 217    * than a document, the factory's <code>startMakingDocument</code>
 218    * and <code>finishMakingDocument</code> methods are not called.
 219    * </p>
 220    *
 221    * @param stylesheet document containing the stylesheet
 222    * @param factory the factory used to build nodes in the result tree
 223    *
 224    * @throws XSLException when the supplied document
 225    * is not syntactically correct XSLT
 226    */
 227  435 public XSLTransform(Document stylesheet, NodeFactory factory)
 228    throws XSLException {
 229   
 230  435 this(new XOMSource(stylesheet));
 231  1 if (factory == null) this.factory = new NodeFactory();
 232  434 else this.factory = factory;
 233   
 234    }
 235   
 236   
 237    /**
 238    * <p>
 239    * Creates a new <code>Nodes</code> from the
 240    * input <code>Document</code> by applying this object's
 241    * stylesheet. The original <code>Document</code> is not
 242    * changed.
 243    * </p>
 244    *
 245    * @param in document to transform
 246    *
 247    * @return a <code>Nodes</code> containing the result of the
 248    * transformation
 249    *
 250    * @throws XSLException if the transformation fails, normally
 251    * due to an XSLT error
 252    */
 253  432 public Nodes transform(Document in) throws XSLException {
 254  432 return transform(new XOMSource(in));
 255    }
 256   
 257   
 258    /**
 259    * <p>
 260    * Supply a parameter to transformations performed by this object.
 261    * The value is normally a <code>Boolean</code>,
 262    * <code>Double</code>, or <code>String</code>. However, it may be
 263    * another type if the underlying XSLT processor supports that
 264    * type. Passing null for the value removes the parameter.
 265    * </p>
 266    *
 267    * @param name the name of the parameter
 268    * @param value the value of the parameter
 269    */
 270  3 public void setParameter(String name, Object value) {
 271  3 this.setParameter(name, null, value);
 272    }
 273   
 274   
 275    /**
 276    * <p>
 277    * Supply a parameter to transformations performed by this object.
 278    * The value is normally a <code>Boolean</code>,
 279    * <code>Double</code>, or <code>String</code>. However, it may be
 280    * another type if the underlying XSLT processor supports that
 281    * type. Passing null for the value removes the parameter.
 282    * </p>
 283    *
 284    * @param name the name of the parameter
 285    * @param namespace the namespace URI of the parameter
 286    * @param value the value of the parameter
 287    */
 288  6 public void setParameter(String name, String namespace, Object value) {
 289   
 290   
 291  6 if (namespace == null || "".equals(namespace)) {
 292  3 _setParameter(name, value);
 293    }
 294    else {
 295  3 _setParameter("{" + namespace + "}" + name, value);
 296    }
 297   
 298    }
 299   
 300   
 301  6 private void _setParameter(String name, Object value) {
 302   
 303  6 if (value == null) {
 304  2 parameters.remove(name);
 305    }
 306    else {
 307  4 parameters.put(name, value);
 308    }
 309   
 310    }
 311   
 312   
 313    /**
 314    * <p>
 315    * Creates a new <code>Nodes</code> object from the
 316    * input <code>Nodes</code> object by applying this object's
 317    * stylesheet. The original <code>Nodes</code> object is not
 318    * changed.
 319    * </p>
 320    *
 321    * @param in document to transform
 322    *
 323    * @return a <code>Nodes</code> containing the result of
 324    * the transformation
 325    *
 326    * @throws XSLException if the transformation fails, normally
 327    * due to an XSLT error
 328    */
 329  2 public Nodes transform(Nodes in) throws XSLException {
 330   
 331  1 if (in.size() == 0) return new Nodes();
 332  1 XOMSource source = new XOMSource(in);
 333  1 return transform(source);
 334   
 335    }
 336   
 337   
 338    /**
 339    * <p>
 340    * Creates a new <code>Nodes</code> object from the
 341    * input <code>Source</code> object by applying this object's
 342    * stylesheet.
 343    * </p>
 344    *
 345    * @param in TrAX <code>Source</code> to transform
 346    *
 347    * @return a <code>Nodes</code> object containing the result of
 348    * the transformation
 349    *
 350    * @throws XSLException if the transformation fails, normally
 351    * due to an XSLT error
 352    */
 353  433 private Nodes transform(Source in) throws XSLException {
 354   
 355  433 try {
 356  433 XOMResult out = new XOMResult(factory);
 357  433 Transformer transformer = templates.newTransformer();
 358    // work around Xalan bug
 359  433 transformer.setOutputProperty(OutputKeys.METHOD, "xml");
 360    // work around a Xalan 2.7.0 bug
 361  433 transformer.setErrorListener(errorsAreFatal);
 362  433 Iterator iterator = parameters.keySet().iterator();
 363  433 while (iterator.hasNext()) {
 364  2 String key = (String) iterator.next();
 365  2 Object value = parameters.get(key);
 366  2 transformer.setParameter(key, value);
 367    }
 368  433 transformer.transform(in, out);
 369  407 return out.getResult();
 370    }
 371    catch (Exception ex) {
 372    // workaround bugs that wrap RuntimeExceptions
 373  26 Throwable cause = ex;
 374  26 if (cause instanceof TransformerException) {
 375  26 TransformerException tex = (TransformerException) cause;
 376  26 Throwable nested = tex.getException();
 377  26 if (nested != null) {
 378  18 cause = nested;
 379  18 if (cause instanceof SAXParseException) {
 380  5 nested = ((SAXParseException) cause).getException();
 381  5 if (nested != null) cause = nested;
 382    }
 383    }
 384    }
 385  26 throw new XSLException(ex.getMessage(), cause);
 386    }
 387   
 388    }
 389   
 390   
 391    /**
 392    * <p>
 393    * Builds a <code>Document</code> object from a
 394    * <code>Nodes</code> object. This is useful when the stylesheet
 395    * is known to produce a well-formed document with a single root
 396    * element. That is, the <code>Node</code> returned contains
 397    * only comments, processing instructions, and exactly one
 398    * element. If the stylesheet produces anything else,
 399    * this method throws <code>XMLException</code>.
 400    * </p>
 401    *
 402    * @param nodes the nodes to be placed in the new document
 403    *
 404    * @return a document containing the nodes
 405    *
 406    * @throws XMLException if <code>nodes</code> does not contain
 407    * exactly one element or if it contains any text nodes or
 408    * attributes
 409    */
 410  188 public static Document toDocument(Nodes nodes) {
 411   
 412  188 Element root = null;
 413  188 int rootPosition = 0;
 414  188 for (int i = 0; i < nodes.size(); i++) {
 415  194 if (nodes.get(i) instanceof Element) {
 416  184 rootPosition = i;
 417  184 root = (Element) nodes.get(i);
 418  184 break;
 419    }
 420    }
 421   
 422  188 if (root == null) {
 423  4 throw new XMLException("No root element");
 424    }
 425   
 426  184 Document result = new Document(root);
 427   
 428  184 for (int i = 0; i < rootPosition; i++) {
 429  10 result.insertChild(nodes.get(i), i);
 430    }
 431   
 432  181 for (int i = rootPosition+1; i < nodes.size(); i++) {
 433  4 result.appendChild(nodes.get(i));
 434    }
 435   
 436  179 return result;
 437   
 438    }
 439   
 440   
 441    /**
 442    * <p>
 443    * Returns a string form of this <code>XSLTransform</code>,
 444    * suitable for debugging.
 445    * </p>
 446    *
 447    * @return debugging string
 448    */
 449  1 public String toString() {
 450  1 return "[" + getClass().getName() + ": " + templates + "]";
 451    }
 452   
 453   
 454    }