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 }