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