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    }