001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 /* 019 * $Id: TransformerImpl.java 1225432 2011-12-29 05:01:46Z mrglavas $ 020 */ 021 022 package org.apache.xalan.xsltc.trax; 023 024 import java.io.File; 025 import java.io.FileOutputStream; 026 import java.io.IOException; 027 import java.io.InputStream; 028 import java.io.OutputStream; 029 import java.io.Reader; 030 import java.io.Writer; 031 import java.net.URL; 032 import java.net.URLConnection; 033 import java.net.UnknownServiceException; 034 import java.util.Enumeration; 035 import java.util.Properties; 036 import java.util.StringTokenizer; 037 import java.util.Vector; 038 039 import javax.xml.parsers.DocumentBuilder; 040 import javax.xml.parsers.DocumentBuilderFactory; 041 import javax.xml.parsers.ParserConfigurationException; 042 import javax.xml.transform.ErrorListener; 043 import javax.xml.transform.OutputKeys; 044 import javax.xml.transform.Result; 045 import javax.xml.transform.Source; 046 import javax.xml.transform.Transformer; 047 import javax.xml.transform.TransformerException; 048 import javax.xml.transform.URIResolver; 049 import javax.xml.transform.dom.DOMResult; 050 import javax.xml.transform.dom.DOMSource; 051 import javax.xml.transform.sax.SAXResult; 052 import javax.xml.transform.sax.SAXSource; 053 import javax.xml.transform.stream.StreamResult; 054 import javax.xml.transform.stream.StreamSource; 055 056 import org.apache.xalan.xsltc.DOM; 057 import org.apache.xalan.xsltc.DOMCache; 058 import org.apache.xalan.xsltc.StripFilter; 059 import org.apache.xalan.xsltc.Translet; 060 import org.apache.xalan.xsltc.TransletException; 061 import org.apache.xalan.xsltc.compiler.util.ErrorMsg; 062 import org.apache.xalan.xsltc.dom.DOMWSFilter; 063 import org.apache.xalan.xsltc.dom.SAXImpl; 064 import org.apache.xalan.xsltc.dom.XSLTCDTMManager; 065 import org.apache.xalan.xsltc.runtime.AbstractTranslet; 066 import org.apache.xalan.xsltc.runtime.Hashtable; 067 import org.apache.xalan.xsltc.runtime.output.TransletOutputHandlerFactory; 068 import org.apache.xml.dtm.DTMWSFilter; 069 import org.apache.xml.serializer.OutputPropertiesFactory; 070 import org.apache.xml.serializer.SerializationHandler; 071 import org.apache.xml.utils.SystemIDResolver; 072 import org.apache.xml.utils.XMLReaderManager; 073 import org.xml.sax.ContentHandler; 074 import org.xml.sax.InputSource; 075 import org.xml.sax.SAXException; 076 import org.xml.sax.XMLReader; 077 import org.xml.sax.ext.LexicalHandler; 078 079 /** 080 * @author Morten Jorgensen 081 * @author G. Todd Miller 082 * @author Santiago Pericas-Geertsen 083 */ 084 public final class TransformerImpl extends Transformer 085 implements DOMCache, ErrorListener 086 { 087 private final static String EMPTY_STRING = ""; 088 private final static String NO_STRING = "no"; 089 private final static String YES_STRING = "yes"; 090 private final static String XML_STRING = "xml"; 091 092 private final static String LEXICAL_HANDLER_PROPERTY = 093 "http://xml.org/sax/properties/lexical-handler"; 094 private static final String NAMESPACE_FEATURE = 095 "http://xml.org/sax/features/namespaces"; 096 097 /** 098 * A reference to the translet or null if the identity transform. 099 */ 100 private AbstractTranslet _translet = null; 101 102 /** 103 * The output method of this transformation. 104 */ 105 private String _method = null; 106 107 /** 108 * The output encoding of this transformation. 109 */ 110 private String _encoding = null; 111 112 /** 113 * The systemId set in input source. 114 */ 115 private String _sourceSystemId = null; 116 117 /** 118 * An error listener for runtime errors. 119 */ 120 private ErrorListener _errorListener = this; 121 122 /** 123 * A reference to a URI resolver for calls to document(). 124 */ 125 private URIResolver _uriResolver = null; 126 127 /** 128 * Output properties of this transformer instance. 129 */ 130 private Properties _properties, _propertiesClone; 131 132 /** 133 * A reference to an output handler factory. 134 */ 135 private TransletOutputHandlerFactory _tohFactory = null; 136 137 /** 138 * A reference to a internal DOM represenation of the input. 139 */ 140 private DOM _dom = null; 141 142 /** 143 * Number of indent spaces to add when indentation is on. 144 */ 145 private int _indentNumber; 146 147 /** 148 * A reference to the transformer factory that this templates 149 * object belongs to. 150 */ 151 private TransformerFactoryImpl _tfactory = null; 152 153 /** 154 * A reference to the output stream, if we create one in our code. 155 */ 156 private OutputStream _ostream = null; 157 158 /** 159 * A reference to the XSLTCDTMManager which is used to build the DOM/DTM 160 * for this transformer. 161 */ 162 private XSLTCDTMManager _dtmManager = null; 163 164 /** 165 * A reference to an object that creates and caches XMLReader objects. 166 */ 167 private XMLReaderManager _readerManager = XMLReaderManager.getInstance(); 168 169 /** 170 * A flag indicating whether we use incremental building of the DTM. 171 */ 172 //private boolean _isIncremental = false; 173 174 /** 175 * A flag indicating whether this transformer implements the identity 176 * transform. 177 */ 178 private boolean _isIdentity = false; 179 180 /** 181 * State of the secure processing feature. 182 */ 183 private boolean _isSecureProcessing = false; 184 185 /** 186 * A hashtable to store parameters for the identity transform. These 187 * are not needed during the transformation, but we must keep track of 188 * them to be fully complaint with the JAXP API. 189 */ 190 private Hashtable _parameters = null; 191 192 /** 193 * This class wraps an ErrorListener into a MessageHandler in order to 194 * capture messages reported via xsl:message. 195 */ 196 static class MessageHandler 197 extends org.apache.xalan.xsltc.runtime.MessageHandler 198 { 199 private ErrorListener _errorListener; 200 201 public MessageHandler(ErrorListener errorListener) { 202 _errorListener = errorListener; 203 } 204 205 public void displayMessage(String msg) { 206 if(_errorListener == null) { 207 System.err.println(msg); 208 } 209 else { 210 try { 211 _errorListener.warning(new TransformerException(msg)); 212 } 213 catch (TransformerException e) { 214 // ignored 215 } 216 } 217 } 218 } 219 220 protected TransformerImpl(Properties outputProperties, int indentNumber, 221 TransformerFactoryImpl tfactory) 222 { 223 this(null, outputProperties, indentNumber, tfactory); 224 _isIdentity = true; 225 // _properties.put(OutputKeys.METHOD, "xml"); 226 } 227 228 protected TransformerImpl(Translet translet, Properties outputProperties, 229 int indentNumber, TransformerFactoryImpl tfactory) 230 { 231 _translet = (AbstractTranslet) translet; 232 _properties = createOutputProperties(outputProperties); 233 _propertiesClone = (Properties) _properties.clone(); 234 _indentNumber = indentNumber; 235 _tfactory = tfactory; 236 //_isIncremental = tfactory._incremental; 237 } 238 239 /** 240 * Return the state of the secure processing feature. 241 */ 242 public boolean isSecureProcessing() { 243 return _isSecureProcessing; 244 } 245 246 /** 247 * Set the state of the secure processing feature. 248 */ 249 public void setSecureProcessing(boolean flag) { 250 _isSecureProcessing = flag; 251 } 252 253 /** 254 * Returns the translet wrapped inside this Transformer or 255 * null if this is the identity transform. 256 */ 257 protected AbstractTranslet getTranslet() { 258 return _translet; 259 } 260 261 public boolean isIdentity() { 262 return _isIdentity; 263 } 264 265 /** 266 * Implements JAXP's Transformer.transform() 267 * 268 * @param source Contains the input XML document 269 * @param result Will contain the output from the transformation 270 * @throws TransformerException 271 */ 272 public void transform(Source source, Result result) 273 throws TransformerException 274 { 275 if (!_isIdentity) { 276 if (_translet == null) { 277 ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_NO_TRANSLET_ERR); 278 throw new TransformerException(err.toString()); 279 } 280 // Pass output properties to the translet 281 transferOutputProperties(_translet); 282 } 283 284 final SerializationHandler toHandler = getOutputHandler(result); 285 if (toHandler == null) { 286 ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_NO_HANDLER_ERR); 287 throw new TransformerException(err.toString()); 288 } 289 290 if (_uriResolver != null && !_isIdentity) { 291 _translet.setDOMCache(this); 292 } 293 294 // Pass output properties to handler if identity 295 if (_isIdentity) { 296 transferOutputProperties(toHandler); 297 } 298 299 transform(source, toHandler, _encoding); 300 301 if (result instanceof DOMResult) { 302 ((DOMResult)result).setNode(_tohFactory.getNode()); 303 } 304 } 305 306 /** 307 * Create an output handler for the transformation output based on 308 * the type and contents of the TrAX Result object passed to the 309 * transform() method. 310 */ 311 public SerializationHandler getOutputHandler(Result result) 312 throws TransformerException 313 { 314 // Get output method using get() to ignore defaults 315 _method = (String) _properties.get(OutputKeys.METHOD); 316 317 // Get encoding using getProperty() to use defaults 318 _encoding = (String) _properties.getProperty(OutputKeys.ENCODING); 319 320 _tohFactory = TransletOutputHandlerFactory.newInstance(); 321 _tohFactory.setEncoding(_encoding); 322 if (_method != null) { 323 _tohFactory.setOutputMethod(_method); 324 } 325 326 // Set indentation number in the factory 327 if (_indentNumber >= 0) { 328 _tohFactory.setIndentNumber(_indentNumber); 329 } 330 331 // Return the content handler for this Result object 332 try { 333 // Result object could be SAXResult, DOMResult, or StreamResult 334 if (result instanceof SAXResult) { 335 final SAXResult target = (SAXResult)result; 336 final ContentHandler handler = target.getHandler(); 337 338 _tohFactory.setHandler(handler); 339 340 /** 341 * Fix for bug 24414 342 * If the lexicalHandler is set then we need to get that 343 * for obtaining the lexical information 344 */ 345 LexicalHandler lexicalHandler = target.getLexicalHandler(); 346 347 if (lexicalHandler != null ) { 348 _tohFactory.setLexicalHandler(lexicalHandler); 349 } 350 351 _tohFactory.setOutputType(TransletOutputHandlerFactory.SAX); 352 return _tohFactory.getSerializationHandler(); 353 } 354 else if (result instanceof DOMResult) { 355 _tohFactory.setNode(((DOMResult) result).getNode()); 356 _tohFactory.setNextSibling(((DOMResult) result).getNextSibling()); 357 _tohFactory.setOutputType(TransletOutputHandlerFactory.DOM); 358 return _tohFactory.getSerializationHandler(); 359 } 360 else if (result instanceof StreamResult) { 361 // Get StreamResult 362 final StreamResult target = (StreamResult) result; 363 364 // StreamResult may have been created with a java.io.File, 365 // java.io.Writer, java.io.OutputStream or just a String 366 // systemId. 367 368 _tohFactory.setOutputType(TransletOutputHandlerFactory.STREAM); 369 370 // try to get a Writer from Result object 371 final Writer writer = target.getWriter(); 372 if (writer != null) { 373 _tohFactory.setWriter(writer); 374 return _tohFactory.getSerializationHandler(); 375 } 376 377 // or try to get an OutputStream from Result object 378 final OutputStream ostream = target.getOutputStream(); 379 if (ostream != null) { 380 _tohFactory.setOutputStream(ostream); 381 return _tohFactory.getSerializationHandler(); 382 } 383 384 // or try to get just a systemId string from Result object 385 String systemId = result.getSystemId(); 386 if (systemId == null) { 387 ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_NO_RESULT_ERR); 388 throw new TransformerException(err.toString()); 389 } 390 391 // System Id may be in one of several forms, (1) a uri 392 // that starts with 'file:', (2) uri that starts with 'http:' 393 // or (3) just a filename on the local system. 394 URL url = null; 395 if (systemId.startsWith("file:")) { 396 url = new URL(systemId); 397 _tohFactory.setOutputStream( 398 _ostream = new FileOutputStream(url.getFile())); 399 return _tohFactory.getSerializationHandler(); 400 } 401 else if (systemId.startsWith("http:")) { 402 url = new URL(systemId); 403 final URLConnection connection = url.openConnection(); 404 _tohFactory.setOutputStream(_ostream = connection.getOutputStream()); 405 return _tohFactory.getSerializationHandler(); 406 } 407 else { 408 // system id is just a filename 409 url = new File(systemId).toURL(); 410 _tohFactory.setOutputStream( 411 _ostream = new FileOutputStream(url.getFile())); 412 return _tohFactory.getSerializationHandler(); 413 } 414 } 415 } 416 // If we cannot write to the location specified by the SystemId 417 catch (UnknownServiceException e) { 418 throw new TransformerException(e); 419 } 420 catch (ParserConfigurationException e) { 421 throw new TransformerException(e); 422 } 423 // If we cannot create the file specified by the SystemId 424 catch (IOException e) { 425 throw new TransformerException(e); 426 } 427 return null; 428 } 429 430 /** 431 * Set the internal DOM that will be used for the next transformation 432 */ 433 protected void setDOM(DOM dom) { 434 _dom = dom; 435 } 436 437 /** 438 * Builds an internal DOM from a TrAX Source object 439 */ 440 private DOM getDOM(Source source) throws TransformerException { 441 try { 442 DOM dom = null; 443 444 if (source != null) { 445 DTMWSFilter wsfilter; 446 if (_translet != null && _translet instanceof StripFilter) { 447 wsfilter = new DOMWSFilter(_translet); 448 } else { 449 wsfilter = null; 450 } 451 452 boolean hasIdCall = (_translet != null) ? _translet.hasIdCall() 453 : false; 454 455 if (_dtmManager == null) { 456 _dtmManager = 457 (XSLTCDTMManager)_tfactory.getDTMManagerClass() 458 .newInstance(); 459 } 460 dom = (DOM)_dtmManager.getDTM(source, false, wsfilter, true, 461 false, false, 0, hasIdCall); 462 } else if (_dom != null) { 463 dom = _dom; 464 _dom = null; // use only once, so reset to 'null' 465 } else { 466 return null; 467 } 468 469 if (!_isIdentity) { 470 // Give the translet the opportunity to make a prepass of 471 // the document, in case it can extract useful information early 472 _translet.prepassDocument(dom); 473 } 474 475 return dom; 476 477 } 478 catch (Exception e) { 479 if (_errorListener != null) { 480 postErrorToListener(e.getMessage()); 481 } 482 throw new TransformerException(e); 483 } 484 } 485 486 /** 487 * Returns the {@link org.apache.xalan.xsltc.trax.TransformerFactoryImpl} 488 * object that create this <code>Transformer</code>. 489 */ 490 protected TransformerFactoryImpl getTransformerFactory() { 491 return _tfactory; 492 } 493 494 /** 495 * Returns the {@link org.apache.xalan.xsltc.runtime.output.TransletOutputHandlerFactory} 496 * object that create the <code>TransletOutputHandler</code>. 497 */ 498 protected TransletOutputHandlerFactory getTransletOutputHandlerFactory() { 499 return _tohFactory; 500 } 501 502 private void transformIdentity(Source source, SerializationHandler handler) 503 throws Exception 504 { 505 // Get systemId from source 506 if (source != null) { 507 _sourceSystemId = source.getSystemId(); 508 } 509 510 if (source instanceof StreamSource) { 511 final StreamSource stream = (StreamSource) source; 512 final InputStream streamInput = stream.getInputStream(); 513 final Reader streamReader = stream.getReader(); 514 final XMLReader reader = _readerManager.getXMLReader(); 515 516 try { 517 // Hook up reader and output handler 518 try { 519 reader.setProperty(LEXICAL_HANDLER_PROPERTY, handler); 520 } 521 catch (SAXException e) { 522 // Falls through 523 } 524 reader.setContentHandler(handler); 525 526 // Create input source from source 527 InputSource input; 528 if (streamInput != null) { 529 input = new InputSource(streamInput); 530 input.setSystemId(_sourceSystemId); 531 } 532 else if (streamReader != null) { 533 input = new InputSource(streamReader); 534 input.setSystemId(_sourceSystemId); 535 } 536 else if (_sourceSystemId != null) { 537 input = new InputSource(_sourceSystemId); 538 } 539 else { 540 ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_NO_SOURCE_ERR); 541 throw new TransformerException(err.toString()); 542 } 543 544 // Start pushing SAX events 545 reader.parse(input); 546 } finally { 547 _readerManager.releaseXMLReader(reader); 548 } 549 } else if (source instanceof SAXSource) { 550 final SAXSource sax = (SAXSource) source; 551 XMLReader reader = sax.getXMLReader(); 552 final InputSource input = sax.getInputSource(); 553 boolean userReader = true; 554 555 try { 556 // Create a reader if not set by user 557 if (reader == null) { 558 reader = _readerManager.getXMLReader(); 559 userReader = false; 560 } 561 562 // Hook up reader and output handler 563 try { 564 reader.setProperty(LEXICAL_HANDLER_PROPERTY, handler); 565 } 566 catch (SAXException e) { 567 // Falls through 568 } 569 reader.setContentHandler(handler); 570 571 // Start pushing SAX events 572 reader.parse(input); 573 } finally { 574 if (!userReader) { 575 _readerManager.releaseXMLReader(reader); 576 } 577 } 578 } else if (source instanceof DOMSource) { 579 final DOMSource domsrc = (DOMSource) source; 580 new DOM2TO(domsrc.getNode(), handler).parse(); 581 } else if (source instanceof XSLTCSource) { 582 final DOM dom = ((XSLTCSource) source).getDOM(null, _translet); 583 ((SAXImpl)dom).copy(handler); 584 } else { 585 ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_NO_SOURCE_ERR); 586 throw new TransformerException(err.toString()); 587 } 588 } 589 590 /** 591 * Internal transformation method - uses the internal APIs of XSLTC 592 */ 593 private void transform(Source source, SerializationHandler handler, 594 String encoding) throws TransformerException 595 { 596 try { 597 /* 598 * According to JAXP1.2, new SAXSource()/StreamSource() 599 * should create an empty input tree, with a default root node. 600 * new DOMSource()creates an empty document using DocumentBuilder. 601 * newDocument(); Use DocumentBuilder.newDocument() for all 3 602 * situations, since there is no clear spec. how to create 603 * an empty tree when both SAXSource() and StreamSource() are used. 604 */ 605 if ((source instanceof StreamSource && source.getSystemId()==null 606 && ((StreamSource)source).getInputStream()==null && 607 ((StreamSource)source).getReader()==null)|| 608 (source instanceof SAXSource && 609 ((SAXSource)source).getInputSource()==null && 610 ((SAXSource)source).getXMLReader()==null )|| 611 (source instanceof DOMSource && 612 ((DOMSource)source).getNode()==null)){ 613 DocumentBuilderFactory builderF = 614 DocumentBuilderFactory.newInstance(); 615 DocumentBuilder builder = 616 builderF.newDocumentBuilder(); 617 String systemID = source.getSystemId(); 618 source = new DOMSource(builder.newDocument()); 619 620 // Copy system ID from original, empty Source to new 621 if (systemID != null) { 622 source.setSystemId(systemID); 623 } 624 } 625 if (_isIdentity) { 626 transformIdentity(source, handler); 627 } else { 628 _translet.transform(getDOM(source), handler); 629 } 630 } catch (TransletException e) { 631 if (_errorListener != null) postErrorToListener(e.getMessage()); 632 throw new TransformerException(e); 633 } catch (RuntimeException e) { 634 if (_errorListener != null) postErrorToListener(e.getMessage()); 635 throw new TransformerException(e); 636 } catch (Exception e) { 637 if (_errorListener != null) postErrorToListener(e.getMessage()); 638 throw new TransformerException(e); 639 } finally { 640 _dtmManager = null; 641 } 642 643 // If we create an output stream for the Result, we need to close it after the transformation. 644 if (_ostream != null) { 645 try { 646 _ostream.close(); 647 } 648 catch (IOException e) {} 649 _ostream = null; 650 } 651 } 652 653 /** 654 * Implements JAXP's Transformer.getErrorListener() 655 * Get the error event handler in effect for the transformation. 656 * 657 * @return The error event handler currently in effect 658 */ 659 public ErrorListener getErrorListener() { 660 return _errorListener; 661 } 662 663 /** 664 * Implements JAXP's Transformer.setErrorListener() 665 * Set the error event listener in effect for the transformation. 666 * Register a message handler in the translet in order to forward 667 * xsl:messages to error listener. 668 * 669 * @param listener The error event listener to use 670 * @throws IllegalArgumentException 671 */ 672 public void setErrorListener(ErrorListener listener) 673 throws IllegalArgumentException { 674 if (listener == null) { 675 ErrorMsg err = new ErrorMsg(ErrorMsg.ERROR_LISTENER_NULL_ERR, 676 "Transformer"); 677 throw new IllegalArgumentException(err.toString()); 678 } 679 _errorListener = listener; 680 681 // Register a message handler to report xsl:messages 682 if (_translet != null) 683 _translet.setMessageHandler(new MessageHandler(_errorListener)); 684 } 685 686 /** 687 * Inform TrAX error listener of an error 688 */ 689 private void postErrorToListener(String message) { 690 try { 691 _errorListener.error(new TransformerException(message)); 692 } 693 catch (TransformerException e) { 694 // ignored - transformation cannot be continued 695 } 696 } 697 698 /** 699 * Inform TrAX error listener of a warning 700 */ 701 private void postWarningToListener(String message) { 702 try { 703 _errorListener.warning(new TransformerException(message)); 704 } 705 catch (TransformerException e) { 706 // ignored - transformation cannot be continued 707 } 708 } 709 710 /** 711 * The translet stores all CDATA sections set in the <xsl:output> element 712 * in a Hashtable. This method will re-construct the whitespace separated 713 * list of elements given in the <xsl:output> element. 714 */ 715 private String makeCDATAString(Hashtable cdata) { 716 // Return a 'null' string if no CDATA section elements were specified 717 if (cdata == null) return null; 718 719 StringBuffer result = new StringBuffer(); 720 721 // Get an enumeration of all the elements in the hashtable 722 Enumeration elements = cdata.keys(); 723 if (elements.hasMoreElements()) { 724 result.append((String)elements.nextElement()); 725 while (elements.hasMoreElements()) { 726 String element = (String)elements.nextElement(); 727 result.append(' '); 728 result.append(element); 729 } 730 } 731 732 return(result.toString()); 733 } 734 735 /** 736 * Implements JAXP's Transformer.getOutputProperties(). 737 * Returns a copy of the output properties for the transformation. This is 738 * a set of layered properties. The first layer contains properties set by 739 * calls to setOutputProperty() and setOutputProperties() on this class, 740 * and the output settings defined in the stylesheet's <xsl:output> 741 * element makes up the second level, while the default XSLT output 742 * settings are returned on the third level. 743 * 744 * @return Properties in effect for this Transformer 745 */ 746 public Properties getOutputProperties() { 747 return (Properties) _properties.clone(); 748 } 749 750 /** 751 * Implements JAXP's Transformer.getOutputProperty(). 752 * Get an output property that is in effect for the transformation. The 753 * property specified may be a property that was set with setOutputProperty, 754 * or it may be a property specified in the stylesheet. 755 * 756 * @param name A non-null string that contains the name of the property 757 * @throws IllegalArgumentException if the property name is not known 758 */ 759 public String getOutputProperty(String name) 760 throws IllegalArgumentException 761 { 762 if (!validOutputProperty(name)) { 763 ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_UNKNOWN_PROP_ERR, name); 764 throw new IllegalArgumentException(err.toString()); 765 } 766 return _properties.getProperty(name); 767 } 768 769 /** 770 * Implements JAXP's Transformer.setOutputProperties(). 771 * Set the output properties for the transformation. These properties 772 * will override properties set in the Templates with xsl:output. 773 * Unrecognised properties will be quitely ignored. 774 * 775 * @param properties The properties to use for the Transformer 776 * @throws IllegalArgumentException Never, errors are ignored 777 */ 778 public void setOutputProperties(Properties properties) 779 throws IllegalArgumentException 780 { 781 if (properties != null) { 782 final Enumeration names = properties.propertyNames(); 783 784 while (names.hasMoreElements()) { 785 final String name = (String) names.nextElement(); 786 787 // Ignore lower layer properties 788 if (isDefaultProperty(name, properties)) continue; 789 790 if (validOutputProperty(name)) { 791 _properties.setProperty(name, properties.getProperty(name)); 792 } 793 else { 794 ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_UNKNOWN_PROP_ERR, name); 795 throw new IllegalArgumentException(err.toString()); 796 } 797 } 798 } 799 else { 800 _properties = _propertiesClone; 801 } 802 } 803 804 /** 805 * Implements JAXP's Transformer.setOutputProperty(). 806 * Get an output property that is in effect for the transformation. The 807 * property specified may be a property that was set with 808 * setOutputProperty(), or it may be a property specified in the stylesheet. 809 * 810 * @param name The name of the property to set 811 * @param value The value to assign to the property 812 * @throws IllegalArgumentException Never, errors are ignored 813 */ 814 public void setOutputProperty(String name, String value) 815 throws IllegalArgumentException 816 { 817 if (!validOutputProperty(name)) { 818 ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_UNKNOWN_PROP_ERR, name); 819 throw new IllegalArgumentException(err.toString()); 820 } 821 _properties.setProperty(name, value); 822 } 823 824 /** 825 * Internal method to pass any properties to the translet prior to 826 * initiating the transformation 827 */ 828 private void transferOutputProperties(AbstractTranslet translet) 829 { 830 // Return right now if no properties are set 831 if (_properties == null) return; 832 833 // Get a list of all the defined properties 834 Enumeration names = _properties.propertyNames(); 835 while (names.hasMoreElements()) { 836 // Note the use of get() instead of getProperty() 837 String name = (String) names.nextElement(); 838 String value = (String) _properties.get(name); 839 840 // Ignore default properties 841 if (value == null) continue; 842 843 // Pass property value to translet - override previous setting 844 if (name.equals(OutputKeys.ENCODING)) { 845 translet._encoding = value; 846 } 847 else if (name.equals(OutputKeys.METHOD)) { 848 translet._method = value; 849 } 850 else if (name.equals(OutputKeys.DOCTYPE_PUBLIC)) { 851 translet._doctypePublic = value; 852 } 853 else if (name.equals(OutputKeys.DOCTYPE_SYSTEM)) { 854 translet._doctypeSystem = value; 855 } 856 else if (name.equals(OutputKeys.MEDIA_TYPE)) { 857 translet._mediaType = value; 858 } 859 else if (name.equals(OutputKeys.STANDALONE)) { 860 translet._standalone = value; 861 } 862 else if (name.equals(OutputKeys.VERSION)) { 863 translet._version = value; 864 } 865 else if (name.equals(OutputKeys.OMIT_XML_DECLARATION)) { 866 translet._omitHeader = 867 (value != null && value.toLowerCase().equals("yes")); 868 } 869 else if (name.equals(OutputKeys.INDENT)) { 870 translet._indent = 871 (value != null && value.toLowerCase().equals("yes")); 872 } 873 else if (name.equals(OutputKeys.CDATA_SECTION_ELEMENTS)) { 874 if (value != null) { 875 translet._cdata = null; // clear previous setting 876 StringTokenizer e = new StringTokenizer(value); 877 while (e.hasMoreTokens()) { 878 translet.addCdataElement(e.nextToken()); 879 } 880 } 881 } 882 } 883 } 884 885 /** 886 * This method is used to pass any properties to the output handler 887 * when running the identity transform. 888 */ 889 public void transferOutputProperties(SerializationHandler handler) 890 { 891 // Return right now if no properties are set 892 if (_properties == null) return; 893 894 String doctypePublic = null; 895 String doctypeSystem = null; 896 897 // Get a list of all the defined properties 898 Enumeration names = _properties.propertyNames(); 899 while (names.hasMoreElements()) { 900 // Note the use of get() instead of getProperty() 901 String name = (String) names.nextElement(); 902 String value = (String) _properties.get(name); 903 904 // Ignore default properties 905 if (value == null) continue; 906 907 // Pass property value to translet - override previous setting 908 if (name.equals(OutputKeys.DOCTYPE_PUBLIC)) { 909 doctypePublic = value; 910 } 911 else if (name.equals(OutputKeys.DOCTYPE_SYSTEM)) { 912 doctypeSystem = value; 913 } 914 else if (name.equals(OutputKeys.MEDIA_TYPE)) { 915 handler.setMediaType(value); 916 } 917 else if (name.equals(OutputKeys.STANDALONE)) { 918 handler.setStandalone(value); 919 } 920 else if (name.equals(OutputKeys.VERSION)) { 921 handler.setVersion(value); 922 } 923 else if (name.equals(OutputKeys.OMIT_XML_DECLARATION)) { 924 handler.setOmitXMLDeclaration( 925 value != null && value.toLowerCase().equals("yes")); 926 } 927 else if (name.equals(OutputKeys.INDENT)) { 928 handler.setIndent( 929 value != null && value.toLowerCase().equals("yes")); 930 } 931 else if (name.equals(OutputKeys.CDATA_SECTION_ELEMENTS)) { 932 if (value != null) { 933 StringTokenizer e = new StringTokenizer(value); 934 Vector uriAndLocalNames = null; 935 while (e.hasMoreTokens()) { 936 final String token = e.nextToken(); 937 938 // look for the last colon, as the String may be 939 // something like "http://abc.com:local" 940 int lastcolon = token.lastIndexOf(':'); 941 String uri; 942 String localName; 943 if (lastcolon > 0) { 944 uri = token.substring(0, lastcolon); 945 localName = token.substring(lastcolon+1); 946 } else { 947 // no colon at all, lets hope this is the 948 // local name itself then 949 uri = null; 950 localName = token; 951 } 952 953 if (uriAndLocalNames == null) { 954 uriAndLocalNames = new Vector(); 955 } 956 // add the uri/localName as a pair, in that order 957 uriAndLocalNames.addElement(uri); 958 uriAndLocalNames.addElement(localName); 959 } 960 handler.setCdataSectionElements(uriAndLocalNames); 961 } 962 } 963 } 964 965 // Call setDoctype() if needed 966 if (doctypePublic != null || doctypeSystem != null) { 967 handler.setDoctype(doctypeSystem, doctypePublic); 968 } 969 } 970 971 /** 972 * Internal method to create the initial set of properties. There 973 * are two layers of properties: the default layer and the base layer. 974 * The latter contains properties defined in the stylesheet or by 975 * the user using this API. 976 */ 977 private Properties createOutputProperties(Properties outputProperties) { 978 final Properties defaults = new Properties(); 979 setDefaults(defaults, "xml"); 980 981 // Copy propeties set in stylesheet to base 982 final Properties base = new Properties(defaults); 983 if (outputProperties != null) { 984 final Enumeration names = outputProperties.propertyNames(); 985 while (names.hasMoreElements()) { 986 final String name = (String) names.nextElement(); 987 base.setProperty(name, outputProperties.getProperty(name)); 988 } 989 } 990 else { 991 base.setProperty(OutputKeys.ENCODING, _translet._encoding); 992 if (_translet._method != null) 993 base.setProperty(OutputKeys.METHOD, _translet._method); 994 } 995 996 // Update defaults based on output method 997 final String method = base.getProperty(OutputKeys.METHOD); 998 if (method != null) { 999 if (method.equals("html")) { 1000 setDefaults(defaults,"html"); 1001 } 1002 else if (method.equals("text")) { 1003 setDefaults(defaults,"text"); 1004 } 1005 } 1006 1007 return base; 1008 } 1009 1010 /** 1011 * Internal method to get the default properties from the 1012 * serializer factory and set them on the property object. 1013 * @param props a java.util.Property object on which the properties are set. 1014 * @param method The output method type, one of "xml", "text", "html" ... 1015 */ 1016 private void setDefaults(Properties props, String method) 1017 { 1018 final Properties method_props = 1019 OutputPropertiesFactory.getDefaultMethodProperties(method); 1020 { 1021 final Enumeration names = method_props.propertyNames(); 1022 while (names.hasMoreElements()) 1023 { 1024 final String name = (String)names.nextElement(); 1025 props.setProperty(name, method_props.getProperty(name)); 1026 } 1027 } 1028 } 1029 /** 1030 * Verifies if a given output property name is a property defined in 1031 * the JAXP 1.1 / TrAX spec 1032 */ 1033 private boolean validOutputProperty(String name) { 1034 return (name.equals(OutputKeys.ENCODING) || 1035 name.equals(OutputKeys.METHOD) || 1036 name.equals(OutputKeys.INDENT) || 1037 name.equals(OutputKeys.DOCTYPE_PUBLIC) || 1038 name.equals(OutputKeys.DOCTYPE_SYSTEM) || 1039 name.equals(OutputKeys.CDATA_SECTION_ELEMENTS) || 1040 name.equals(OutputKeys.MEDIA_TYPE) || 1041 name.equals(OutputKeys.OMIT_XML_DECLARATION) || 1042 name.equals(OutputKeys.STANDALONE) || 1043 name.equals(OutputKeys.VERSION) || 1044 name.charAt(0) == '{'); 1045 } 1046 1047 /** 1048 * Checks if a given output property is default (2nd layer only) 1049 */ 1050 private boolean isDefaultProperty(String name, Properties properties) { 1051 return (properties.get(name) == null); 1052 } 1053 1054 /** 1055 * Implements JAXP's Transformer.setParameter() 1056 * Add a parameter for the transformation. The parameter is simply passed 1057 * on to the translet - no validation is performed - so any unused 1058 * parameters are quitely ignored by the translet. 1059 * 1060 * @param name The name of the parameter 1061 * @param value The value to assign to the parameter 1062 */ 1063 public void setParameter(String name, Object value) { 1064 1065 if (value == null) { 1066 ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_INVALID_SET_PARAM_VALUE, name); 1067 throw new IllegalArgumentException(err.toString()); 1068 } 1069 1070 if (_isIdentity) { 1071 if (_parameters == null) { 1072 _parameters = new Hashtable(); 1073 } 1074 _parameters.put(name, value); 1075 } 1076 else { 1077 _translet.addParameter(name, value); 1078 } 1079 } 1080 1081 /** 1082 * Implements JAXP's Transformer.clearParameters() 1083 * Clear all parameters set with setParameter. Clears the translet's 1084 * parameter stack. 1085 */ 1086 public void clearParameters() { 1087 if (_isIdentity && _parameters != null) { 1088 _parameters.clear(); 1089 } 1090 else { 1091 _translet.clearParameters(); 1092 } 1093 } 1094 1095 /** 1096 * Implements JAXP's Transformer.getParameter() 1097 * Returns the value of a given parameter. Note that the translet will not 1098 * keep values for parameters that were not defined in the stylesheet. 1099 * 1100 * @param name The name of the parameter 1101 * @return An object that contains the value assigned to the parameter 1102 */ 1103 public final Object getParameter(String name) { 1104 if (_isIdentity) { 1105 return (_parameters != null) ? _parameters.get(name) : null; 1106 } 1107 else { 1108 return _translet.getParameter(name); 1109 } 1110 } 1111 1112 /** 1113 * Implements JAXP's Transformer.getURIResolver() 1114 * Set the object currently used to resolve URIs used in document(). 1115 * 1116 * @return The URLResolver object currently in use 1117 */ 1118 public URIResolver getURIResolver() { 1119 return _uriResolver; 1120 } 1121 1122 /** 1123 * Implements JAXP's Transformer.setURIResolver() 1124 * Set an object that will be used to resolve URIs used in document(). 1125 * 1126 * @param resolver The URIResolver to use in document() 1127 */ 1128 public void setURIResolver(URIResolver resolver) { 1129 _uriResolver = resolver; 1130 } 1131 1132 /** 1133 * This class should only be used as a DOMCache for the translet if the 1134 * URIResolver has been set. 1135 * 1136 * The method implements XSLTC's DOMCache interface, which is used to 1137 * plug in an external document loader into a translet. This method acts 1138 * as an adapter between TrAX's URIResolver interface and XSLTC's 1139 * DOMCache interface. This approach is simple, but removes the 1140 * possibility of using external document caches with XSLTC. 1141 * 1142 * @param baseURI The base URI used by the document call. 1143 * @param href The href argument passed to the document function. 1144 * @param translet A reference to the translet requesting the document 1145 */ 1146 public DOM retrieveDocument(String baseURI, String href, Translet translet) { 1147 try { 1148 // Argument to document function was: document(''); 1149 if (href.length() == 0) { 1150 href = baseURI; 1151 } 1152 1153 /* 1154 * Fix for bug 24188 1155 * Incase the _uriResolver.resolve(href,base) is null 1156 * try to still retrieve the document before returning null 1157 * and throwing the FileNotFoundException in 1158 * org.apache.xalan.xsltc.dom.LoadDocument 1159 * 1160 */ 1161 Source resolvedSource = _uriResolver.resolve(href, baseURI); 1162 if (resolvedSource == null) { 1163 StreamSource streamSource = new StreamSource( 1164 SystemIDResolver.getAbsoluteURI(href, baseURI)); 1165 return getDOM(streamSource) ; 1166 } 1167 1168 return getDOM(resolvedSource); 1169 } 1170 catch (TransformerException e) { 1171 if (_errorListener != null) 1172 postErrorToListener("File not found: " + e.getMessage()); 1173 return(null); 1174 } 1175 } 1176 1177 /** 1178 * Receive notification of a recoverable error. 1179 * The transformer must continue to provide normal parsing events after 1180 * invoking this method. It should still be possible for the application 1181 * to process the document through to the end. 1182 * 1183 * @param e The warning information encapsulated in a transformer 1184 * exception. 1185 * @throws TransformerException if the application chooses to discontinue 1186 * the transformation (always does in our case). 1187 */ 1188 public void error(TransformerException e) 1189 throws TransformerException 1190 { 1191 Throwable wrapped = e.getException(); 1192 if (wrapped != null) { 1193 System.err.println(new ErrorMsg(ErrorMsg.ERROR_PLUS_WRAPPED_MSG, 1194 e.getMessageAndLocation(), 1195 wrapped.getMessage())); 1196 } else { 1197 System.err.println(new ErrorMsg(ErrorMsg.ERROR_MSG, 1198 e.getMessageAndLocation())); 1199 } 1200 throw e; 1201 } 1202 1203 /** 1204 * Receive notification of a non-recoverable error. 1205 * The application must assume that the transformation cannot continue 1206 * after the Transformer has invoked this method, and should continue 1207 * (if at all) only to collect addition error messages. In fact, 1208 * Transformers are free to stop reporting events once this method has 1209 * been invoked. 1210 * 1211 * @param e The warning information encapsulated in a transformer 1212 * exception. 1213 * @throws TransformerException if the application chooses to discontinue 1214 * the transformation (always does in our case). 1215 */ 1216 public void fatalError(TransformerException e) 1217 throws TransformerException 1218 { 1219 Throwable wrapped = e.getException(); 1220 if (wrapped != null) { 1221 System.err.println(new ErrorMsg(ErrorMsg.FATAL_ERR_PLUS_WRAPPED_MSG, 1222 e.getMessageAndLocation(), 1223 wrapped.getMessage())); 1224 } else { 1225 System.err.println(new ErrorMsg(ErrorMsg.FATAL_ERR_MSG, 1226 e.getMessageAndLocation())); 1227 } 1228 throw e; 1229 } 1230 1231 /** 1232 * Receive notification of a warning. 1233 * Transformers can use this method to report conditions that are not 1234 * errors or fatal errors. The default behaviour is to take no action. 1235 * After invoking this method, the Transformer must continue with the 1236 * transformation. It should still be possible for the application to 1237 * process the document through to the end. 1238 * 1239 * @param e The warning information encapsulated in a transformer 1240 * exception. 1241 * @throws TransformerException if the application chooses to discontinue 1242 * the transformation (never does in our case). 1243 */ 1244 public void warning(TransformerException e) 1245 throws TransformerException 1246 { 1247 Throwable wrapped = e.getException(); 1248 if (wrapped != null) { 1249 System.err.println(new ErrorMsg(ErrorMsg.WARNING_PLUS_WRAPPED_MSG, 1250 e.getMessageAndLocation(), 1251 wrapped.getMessage())); 1252 } else { 1253 System.err.println(new ErrorMsg(ErrorMsg.WARNING_MSG, 1254 e.getMessageAndLocation())); 1255 } 1256 } 1257 1258 /** 1259 * This method resets the Transformer to its original configuration 1260 * Transformer code is reset to the same state it was when it was 1261 * created 1262 * @since 1.5 1263 */ 1264 public void reset() { 1265 1266 _method = null; 1267 _encoding = null; 1268 _sourceSystemId = null; 1269 _errorListener = this; 1270 _uriResolver = null; 1271 _dom = null; 1272 _parameters = null; 1273 _indentNumber = 0; 1274 setOutputProperties (null); 1275 1276 } 1277 }