001: /* Copyright 2002-2005 Elliotte Rusty Harold
002:
003: This library is free software; you can redistribute it and/or modify
004: it under the terms of version 2.1 of the GNU Lesser General Public
005: License as published by the Free Software Foundation.
006:
007: This library is distributed in the hope that it will be useful,
008: but WITHOUT ANY WARRANTY; without even the implied warranty of
009: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
010: GNU Lesser General Public License for more details.
011:
012: You should have received a copy of the GNU Lesser General Public
013: License along with this library; if not, write to the
014: Free Software Foundation, Inc., 59 Temple Place, Suite 330,
015: Boston, MA 02111-1307 USA
016:
017: You can contact Elliotte Rusty Harold by sending e-mail to
018: elharo@metalab.unc.edu. Please include the word "XOM" in the
019: subject line. The XOM home page is located at http://www.xom.nu/
020: */
021:
022: package nu.xom.xslt;
023:
024: import java.util.HashMap;
025: import java.util.Iterator;
026: import java.util.Map;
027:
028: import javax.xml.transform.ErrorListener;
029: import javax.xml.transform.OutputKeys;
030: import javax.xml.transform.Source;
031: import javax.xml.transform.Templates;
032: import javax.xml.transform.Transformer;
033: import javax.xml.transform.TransformerConfigurationException;
034: import javax.xml.transform.TransformerException;
035: import javax.xml.transform.TransformerFactoryConfigurationError;
036: import javax.xml.transform.TransformerFactory;
037:
038: import org.xml.sax.SAXParseException;
039:
040: import nu.xom.Document;
041: import nu.xom.Element;
042: import nu.xom.NodeFactory;
043: import nu.xom.Nodes;
044: import nu.xom.XMLException;
045:
046: /**
047: * <p>
048: * Serves as an interface to a TrAX aware XSLT processor such as Xalan
049: * or Saxon. The following example shows how to apply an XSL
050: * Transformation to a XOM document and get the transformation result
051: * in the form of a XOM <code>Nodes</code>:</p>
052: * <blockquote><pre>public static Nodes transform(Document in)
053: * throws XSLException, ParsingException, IOException {
054: * Builder builder = new Builder();
055: * Document stylesheet = builder.build("mystylesheet.xsl");
056: * XSLTransform transform = new XSLTransform(stylesheet);
057: * return transform.transform(doc);
058: * } </pre></blockquote>
059: *
060: * <p>
061: * XOM relies on TrAX to perform the transformation.
062: * The <code>javax.xml.transform.TransformerFactory</code> Java
063: * system property determines which XSLT engine TrAX uses. Its
064: * value should be the fully qualified name of the implementation
065: * of the abstract <code>javax.xml.transform.TransformerFactory</code>
066: * class. Values of this property for popular XSLT processors include:
067: * </p>
068: * <ul>
069: * <li>Saxon 6.x:
070: * <code>com.icl.saxon.TransformerFactoryImpl</code>
071: * </li>
072: * <li>Saxon 7.x and 8.x:
073: * <code>net.sf.saxon.TransformerFactoryImpl</code>
074: * </li>
075: * <li>Xalan interpretive:
076: * <code>org.apache.xalan.processor.TransformerFactoryImpl</code>
077: * </li>
078: * <li>Xalan XSLTC:
079: * <code>org.apache.xalan.xsltc.trax.TransformerFactoryImpl</code>
080: * </li>
081: * <li>jd.xslt:
082: * <code>jd.xml.xslt.trax.TransformerFactoryImpl</code>
083: * </li>
084: * <li>Oracle:
085: * <code>oracle.xml.jaxp.JXSAXTransformerFactory</code>
086: * </li>
087: * <li>Java 1.5 bundled Xalan:
088: * <code>com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl</code>
089: * </li>
090: * </ul>
091: * <p>
092: * This property can be set in all the usual ways a Java system
093: * property can be set. TrAX picks from them in this order:</p>
094: * <ol>
095: * <li>The most recent value specified by invoking
096: * <code>System.setProperty("javax.xml.transform.TransformerFactory",
097: * "<i><code>classname</code></i>")</code></li>
098: * <li>The value specified at the command line using the
099: * <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.parsers.DocumentBuilderFactory=<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.1b3
120: */
121: public final class XSLTransform {
122:
123: /**
124: * <p>
125: * The compiled form of the XSLT stylesheet that this object
126: * represents. This can be safely used across multiple threads
127: * unlike a <code>Transformer</code> object.
128: * </p>
129: */
130: private Templates templates;
131: private NodeFactory factory;
132: private Map parameters = new HashMap();
133: private static ErrorListener errorsAreFatal = new FatalListener();
134:
135: private static class FatalListener implements ErrorListener {
136:
137: public void warning(TransformerException exception) {
138: }
139:
140: public void error(TransformerException exception)
141: throws TransformerException {
142: throw exception;
143: }
144:
145: public void fatalError(TransformerException exception)
146: throws TransformerException {
147: throw exception;
148: }
149:
150: }
151:
152: // I could use one TransformerFactory field instead of local
153: // variables but then I'd have to synchronize it; and it would
154: // be hard to change the class used to transform
155:
156: /**
157: * <p>
158: * Creates a new <code>XSLTransform</code> by
159: * reading the stylesheet from the specified source.
160: * </p>
161: *
162: * @param source TrAX <code>Source</code> object from
163: * which the input document is read
164: *
165: * @throws XSLException when an <code>IOException</code>,
166: * format error, or something else prevents the stylesheet
167: * from being compiled
168: */
169: private XSLTransform(Source source) throws XSLException {
170:
171: try {
172: TransformerFactory factory = TransformerFactory
173: .newInstance();
174: factory.setErrorListener(errorsAreFatal);
175: this .templates = factory.newTemplates(source);
176: } catch (TransformerFactoryConfigurationError error) {
177: throw new XSLException(
178: "Could not locate a TrAX TransformerFactory", error);
179: } catch (TransformerConfigurationException ex) {
180: throw new XSLException("Syntax error in stylesheet", ex);
181: }
182:
183: }
184:
185: /**
186: * <p>
187: * Creates a new <code>XSLTransform</code> by
188: * reading the stylesheet from the supplied document.
189: * </p>
190: *
191: * @param stylesheet document containing the stylesheet
192: *
193: * @throws XSLException when the supplied document
194: * is not syntactically correct XSLT
195: */
196: public XSLTransform(Document stylesheet) throws XSLException {
197: this (stylesheet, new NodeFactory());
198: }
199:
200: /**
201: * <p>
202: * Creates a new <code>XSLTransform</code> by
203: * reading the stylesheet from the supplied document.
204: * The supplied factory will be used to create all nodes
205: * in the result tree, so that a transform can create
206: * instances of subclasses of the standard XOM classes.
207: * Because an XSL transformation generates a list of nodes rather
208: * than a document, the factory's <code>startMakingDocument</code>
209: * and <code>finishMakingDocument</code> methods are not called.
210: * </p>
211: *
212: * @param stylesheet document containing the stylesheet
213: * @param factory the factory used to build nodes in the result tree
214: *
215: * @throws XSLException when the supplied document
216: * is not syntactically correct XSLT
217: */
218: public XSLTransform(Document stylesheet, NodeFactory factory)
219: throws XSLException {
220:
221: this (new XOMSource(stylesheet));
222: if (factory == null)
223: this .factory = new NodeFactory();
224: else
225: this .factory = factory;
226:
227: }
228:
229: /**
230: * <p>
231: * Creates a new <code>Nodes</code> from the
232: * input <code>Document</code> by applying this object's
233: * stylesheet. The original <code>Document</code> is not
234: * changed.
235: * </p>
236: *
237: * @param in document to transform
238: *
239: * @return a <code>Nodes</code> containing the result of the
240: * transformation
241: *
242: * @throws XSLException if the transformation fails, normally
243: * due to an XSLT error
244: */
245: public Nodes transform(Document in) throws XSLException {
246: return transform(new XOMSource(in));
247: }
248:
249: /**
250: * <p>
251: * Supply a parameter to transformations performed by this object.
252: * The value is normally a <code>Boolean</code>,
253: * <code>Double</code>, or <code>String</code>. However, it may be
254: * another type if the underlying XSLT processor supports that
255: * type. Passing null for the value removes the parameter.
256: * </p>
257: *
258: * @param name the name of the parameter
259: * @param value the value of the parameter
260: */
261: public void setParameter(String name, Object value) {
262: this .setParameter(name, null, value);
263: }
264:
265: /**
266: * <p>
267: * Supply a parameter to transformations performed by this object.
268: * The value is normally a <code>Boolean</code>,
269: * <code>Double</code>, or <code>String</code>. However, it may be
270: * another type if the underlying XSLT processor supports that
271: * type. Passing null for the value removes the parameter.
272: * </p>
273: *
274: * @param name the name of the parameter
275: * @param namespace the namespace URI of the parameter
276: * @param value the value of the parameter
277: */
278: public void setParameter(String name, String namespace, Object value) {
279:
280: if (namespace == null || "".equals(namespace)) {
281: _setParameter(name, value);
282: } else {
283: _setParameter("{" + namespace + "}" + name, value);
284: }
285:
286: }
287:
288: private void _setParameter(String name, Object value) {
289:
290: if (value == null) {
291: parameters.remove(name);
292: } else {
293: parameters.put(name, value);
294: }
295:
296: }
297:
298: /**
299: * <p>
300: * Creates a new <code>Nodes</code> object from the
301: * input <code>Nodes</code> object by applying this object's
302: * stylesheet. The original <code>Nodes</code> object is not
303: * changed.
304: * </p>
305: *
306: * @param in document to transform
307: *
308: * @return a <code>Nodes</code> containing the result of
309: * the transformation
310: *
311: * @throws XSLException if the transformation fails, normally
312: * due to an XSLT error
313: */
314: public Nodes transform(Nodes in) throws XSLException {
315:
316: if (in.size() == 0)
317: return new Nodes();
318: XOMSource source = new XOMSource(in);
319: return transform(source);
320:
321: }
322:
323: /**
324: * <p>
325: * Creates a new <code>Nodes</code> object from the
326: * input <code>Source</code> object by applying this object's
327: * stylesheet.
328: * </p>
329: *
330: * @param in TrAX <code>Source</code> to transform
331: *
332: * @return a <code>Nodes</code> object containing the result of
333: * the transformation
334: *
335: * @throws XSLException if the transformation fails, normally
336: * due to an XSLT error
337: */
338: private Nodes transform(Source in) throws XSLException {
339:
340: try {
341: XOMResult out = new XOMResult(factory);
342: Transformer transformer = templates.newTransformer();
343: // work around Xalan bug
344: transformer.setOutputProperty(OutputKeys.METHOD, "xml");
345: // work around a Xalan 2.7.0 bug
346: transformer.setErrorListener(errorsAreFatal);
347: Iterator iterator = parameters.keySet().iterator();
348: while (iterator.hasNext()) {
349: String key = (String) iterator.next();
350: Object value = parameters.get(key);
351: transformer.setParameter(key, value);
352: }
353: transformer.transform(in, out);
354: return out.getResult();
355: } catch (Exception ex) {
356: // workaround bugs that wrap RuntimeExceptions
357: Throwable cause = ex;
358: if (cause instanceof TransformerException) {
359: TransformerException tex = (TransformerException) cause;
360: Throwable nested = tex.getException();
361: if (nested != null) {
362: cause = nested;
363: if (cause instanceof SAXParseException) {
364: nested = ((SAXParseException) cause)
365: .getException();
366: if (nested != null)
367: cause = nested;
368: }
369: }
370: }
371: throw new XSLException(ex.getMessage(), cause);
372: }
373:
374: }
375:
376: /**
377: * <p>
378: * Builds a <code>Document</code> object from a
379: * <code>Nodes</code> object. This is useful when the stylesheet
380: * is known to produce a well-formed document with a single root
381: * element. That is, the <code>Node</code> returned contains
382: * only comments, processing instructions, and exactly one
383: * element. If the stylesheet produces anything else,
384: * this method throws <code>XMLException</code>.
385: * </p>
386: *
387: * @param nodes the nodes to be placed in the new document
388: *
389: * @return a document containing the nodes
390: *
391: * @throws XMLException if <code>nodes</code> does not contain
392: * exactly one element or if it contains any text nodes or
393: * attributes
394: */
395: public static Document toDocument(Nodes nodes) {
396:
397: Element root = null;
398: int rootPosition = 0;
399: for (int i = 0; i < nodes.size(); i++) {
400: if (nodes.get(i) instanceof Element) {
401: rootPosition = i;
402: root = (Element) nodes.get(i);
403: break;
404: }
405: }
406:
407: if (root == null) {
408: throw new XMLException("No root element");
409: }
410:
411: Document result = new Document(root);
412:
413: for (int i = 0; i < rootPosition; i++) {
414: result.insertChild(nodes.get(i), i);
415: }
416:
417: for (int i = rootPosition + 1; i < nodes.size(); i++) {
418: result.appendChild(nodes.get(i));
419: }
420:
421: return result;
422:
423: }
424:
425: /**
426: * <p>
427: * Returns a string form of this <code>XSLTransform</code>,
428: * suitable for debugging.
429: * </p>
430: *
431: * @return debugging string
432: */
433: public String toString() {
434: return "[" + getClass().getName() + ": " + templates + "]";
435: }
436:
437: }
|