|
|||||||||||||||||||
| Source file | Conditionals | Statements | Methods | TOTAL | |||||||||||||||
| XSLTransform.java | 92.9% | 98.4% | 100% | 97.2% |
|
||||||||||||||
| 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 | } |
|
||||||||||