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: TransformerIdentityImpl.java 575747 2007-09-14 16:28:37Z kcormier $
020     */
021    package org.apache.xalan.transformer;
022    
023    import java.io.IOException;
024    import java.util.Hashtable;
025    import java.util.Properties;
026    
027    import javax.xml.XMLConstants;
028    import javax.xml.parsers.DocumentBuilder;
029    import javax.xml.parsers.DocumentBuilderFactory;
030    import javax.xml.parsers.ParserConfigurationException;
031    import javax.xml.transform.ErrorListener;
032    import javax.xml.transform.OutputKeys;
033    import javax.xml.transform.Result;
034    import javax.xml.transform.Source;
035    import javax.xml.transform.Transformer;
036    import javax.xml.transform.TransformerException;
037    import javax.xml.transform.URIResolver;
038    import javax.xml.transform.dom.DOMResult;
039    import javax.xml.transform.dom.DOMSource;
040    import javax.xml.transform.sax.SAXResult;
041    import javax.xml.transform.sax.SAXSource;
042    import javax.xml.transform.sax.TransformerHandler;
043    import javax.xml.transform.stream.StreamSource;
044    import javax.xml.transform.stream.StreamResult;
045    
046    import org.apache.xalan.res.XSLMessages;
047    import org.apache.xalan.res.XSLTErrorResources;
048    import org.apache.xalan.templates.OutputProperties;
049    import org.apache.xml.serializer.Serializer;
050    import org.apache.xml.serializer.SerializerFactory;
051    import org.apache.xml.serializer.Method;
052    import org.apache.xml.utils.DOMBuilder;
053    import org.apache.xml.utils.XMLReaderManager;
054    
055    import org.w3c.dom.Document;
056    import org.w3c.dom.DocumentFragment;
057    import org.w3c.dom.Node;
058    
059    import org.xml.sax.Attributes;
060    import org.xml.sax.ContentHandler;
061    import org.xml.sax.DTDHandler;
062    import org.xml.sax.InputSource;
063    import org.xml.sax.Locator;
064    import org.xml.sax.SAXException;
065    import org.xml.sax.XMLReader;
066    import org.xml.sax.ext.DeclHandler;
067    import org.xml.sax.ext.LexicalHandler;
068    
069    /**
070     * This class implements an identity transformer for
071     * {@link javax.xml.transform.sax.SAXTransformerFactory#newTransformerHandler()}
072     * and {@link javax.xml.transform.TransformerFactory#newTransformer()}.  It
073     * simply feeds SAX events directly to a serializer ContentHandler, if the
074     * result is a stream.  If the result is a DOM, it will send the events to
075     * {@link org.apache.xml.utils.DOMBuilder}.  If the result is another
076     * content handler, it will simply pass the events on.
077     */
078    public class TransformerIdentityImpl extends Transformer
079            implements TransformerHandler, DeclHandler
080    {
081    
082      /**
083       * Constructor TransformerIdentityImpl creates an identity transform.
084       *
085       */
086      public TransformerIdentityImpl(boolean isSecureProcessing)
087      {
088        m_outputFormat = new OutputProperties(Method.XML);
089        m_isSecureProcessing = isSecureProcessing;
090      }
091    
092      /**
093       * Constructor TransformerIdentityImpl creates an identity transform.
094       *
095       */
096      public TransformerIdentityImpl()
097      {
098        this(false);
099      }
100    
101      /**
102       * Enables the user of the TransformerHandler to set the
103       * to set the Result for the transformation.
104       *
105       * @param result A Result instance, should not be null.
106       *
107       * @throws IllegalArgumentException if result is invalid for some reason.
108       */
109      public void setResult(Result result) throws IllegalArgumentException
110      {
111        if(null == result)
112          throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_RESULT_NULL, null)); //"Result should not be null");        
113        m_result = result;
114      }
115    
116      /**
117       * Set the base ID (URI or system ID) from where relative
118       * URLs will be resolved.
119       * @param systemID Base URI for the source tree.
120       */
121      public void setSystemId(String systemID)
122      {
123        m_systemID = systemID;
124      }
125    
126      /**
127       * Get the base ID (URI or system ID) from where relative
128       * URLs will be resolved.
129       * @return The systemID that was set with {@link #setSystemId}.
130       */
131      public String getSystemId()
132      {
133        return m_systemID;
134      }
135    
136      /**
137       * Get the Transformer associated with this handler, which
138       * is needed in order to set parameters and output properties.
139       *
140       * @return non-null reference to the transformer.
141       */
142      public Transformer getTransformer()
143      {
144        return this;
145      }
146    
147      /**
148       * Reset the status of the transformer.
149       */
150      public void reset()
151      {
152        m_flushedStartDoc = false;
153        m_foundFirstElement = false;
154        m_outputStream = null;
155        clearParameters();
156        m_result = null;
157        m_resultContentHandler = null;
158        m_resultDeclHandler = null;
159        m_resultDTDHandler = null;
160        m_resultLexicalHandler = null;
161        m_serializer = null;
162        m_systemID = null;
163        m_URIResolver = null;
164        m_outputFormat = new OutputProperties(Method.XML);
165      }
166    
167      /**
168       * Create a result ContentHandler from a Result object, based
169       * on the current OutputProperties.
170       *
171       * @param outputTarget Where the transform result should go,
172       * should not be null.
173       *
174       * @return A valid ContentHandler that will create the
175       * result tree when it is fed SAX events.
176       *
177       * @throws TransformerException
178       */
179      private void createResultContentHandler(Result outputTarget)
180              throws TransformerException
181      {
182    
183        if (outputTarget instanceof SAXResult)
184        {
185          SAXResult saxResult = (SAXResult) outputTarget;
186    
187          m_resultContentHandler = saxResult.getHandler();
188          m_resultLexicalHandler = saxResult.getLexicalHandler();
189    
190          if (m_resultContentHandler instanceof Serializer)
191          {
192    
193            // Dubious but needed, I think.
194            m_serializer = (Serializer) m_resultContentHandler;
195          }
196        }
197        else if (outputTarget instanceof DOMResult)
198        {
199          DOMResult domResult = (DOMResult) outputTarget;
200          Node outputNode = domResult.getNode();
201          Node nextSibling = domResult.getNextSibling();
202          Document doc;
203          short type;
204    
205          if (null != outputNode)
206          {
207            type = outputNode.getNodeType();
208            doc = (Node.DOCUMENT_NODE == type)
209                  ? (Document) outputNode : outputNode.getOwnerDocument();
210          }
211          else
212          {
213            try
214            {
215              DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
216    
217              dbf.setNamespaceAware(true);
218    
219              if (m_isSecureProcessing)
220              {
221                try
222                {
223                  dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
224                }
225                catch (ParserConfigurationException pce) {}
226              }
227    
228              DocumentBuilder db = dbf.newDocumentBuilder();
229    
230              doc = db.newDocument();
231            }
232            catch (ParserConfigurationException pce)
233            {
234              throw new TransformerException(pce);
235            }
236    
237            outputNode = doc;
238            type = outputNode.getNodeType();
239    
240            ((DOMResult) outputTarget).setNode(outputNode);
241          }
242    
243          DOMBuilder domBuilder =
244            (Node.DOCUMENT_FRAGMENT_NODE == type)
245            ? new DOMBuilder(doc, (DocumentFragment) outputNode)
246            : new DOMBuilder(doc, outputNode);
247          
248          if (nextSibling != null)
249            domBuilder.setNextSibling(nextSibling);
250          
251          m_resultContentHandler = domBuilder;
252          m_resultLexicalHandler = domBuilder;
253        }
254        else if (outputTarget instanceof StreamResult)
255        {
256          StreamResult sresult = (StreamResult) outputTarget;
257    
258          try
259          {
260            Serializer serializer =
261              SerializerFactory.getSerializer(m_outputFormat.getProperties());
262    
263            m_serializer = serializer;
264    
265            if (null != sresult.getWriter())
266              serializer.setWriter(sresult.getWriter());
267            else if (null != sresult.getOutputStream())
268              serializer.setOutputStream(sresult.getOutputStream());
269            else if (null != sresult.getSystemId())
270            {
271              String fileURL = sresult.getSystemId();
272    
273              if (fileURL.startsWith("file:///")) {
274                if (fileURL.substring(8).indexOf(":") >0) {
275                  fileURL = fileURL.substring(8);
276                } else  {
277                  fileURL = fileURL.substring(7);
278                }
279              } else if (fileURL.startsWith("file:/")) {
280                if (fileURL.substring(6).indexOf(":") >0) {
281                  fileURL = fileURL.substring(6);
282                } else {
283                  fileURL = fileURL.substring(5);
284                }
285              }
286    
287              m_outputStream = new java.io.FileOutputStream(fileURL);
288              serializer.setOutputStream(m_outputStream);
289            }
290            else
291              throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_NO_OUTPUT_SPECIFIED, null)); //"No output specified!");
292    
293            m_resultContentHandler = serializer.asContentHandler();
294          }
295          catch (IOException ioe)
296          {
297            throw new TransformerException(ioe);
298          }
299        }
300        else
301        {
302          throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_CANNOT_TRANSFORM_TO_RESULT_TYPE, new Object[]{outputTarget.getClass().getName()})); //"Can't transform to a Result of type "
303                                        // + outputTarget.getClass().getName()
304                                        // + "!");
305        }
306    
307        if (m_resultContentHandler instanceof DTDHandler)
308          m_resultDTDHandler = (DTDHandler) m_resultContentHandler;
309        
310        if (m_resultContentHandler instanceof DeclHandler)
311          m_resultDeclHandler = (DeclHandler) m_resultContentHandler;
312    
313        if (m_resultContentHandler instanceof LexicalHandler)
314          m_resultLexicalHandler = (LexicalHandler) m_resultContentHandler;
315      }
316    
317      /**
318       * Process the source tree to the output result.
319       * @param source  The input for the source tree.
320       *
321       * @param outputTarget The output target.
322       *
323       * @throws TransformerException If an unrecoverable error occurs
324       * during the course of the transformation.
325       */
326      public void transform(Source source, Result outputTarget)
327              throws TransformerException
328      {
329    
330        createResultContentHandler(outputTarget);
331        
332        /*
333         * According to JAXP1.2, new SAXSource()/StreamSource()
334         * should create an empty input tree, with a default root node. 
335         * new DOMSource()creates an empty document using DocumentBuilder.
336         * newDocument(); Use DocumentBuilder.newDocument() for all 3 situations,
337         * since there is no clear spec. how to create an empty tree when
338         * both SAXSource() and StreamSource() are used.
339         */
340        if ((source instanceof StreamSource && source.getSystemId()==null &&
341           ((StreamSource)source).getInputStream()==null &&
342           ((StreamSource)source).getReader()==null)||
343           (source instanceof SAXSource &&
344           ((SAXSource)source).getInputSource()==null &&
345           ((SAXSource)source).getXMLReader()==null )||
346           (source instanceof DOMSource && ((DOMSource)source).getNode()==null)){
347          try {
348            DocumentBuilderFactory builderF = DocumentBuilderFactory.newInstance();
349            DocumentBuilder builder = builderF.newDocumentBuilder();
350            String systemID = source.getSystemId();
351            source = new DOMSource(builder.newDocument());
352    
353            // Copy system ID from original, empty Source to new Source
354            if (systemID != null) {
355              source.setSystemId(systemID);
356            }
357          } catch (ParserConfigurationException e){
358            throw new TransformerException(e.getMessage());
359          }           
360        }
361        
362        try
363        {
364          if (source instanceof DOMSource)
365          {
366            DOMSource dsource = (DOMSource) source;
367      
368            m_systemID = dsource.getSystemId();
369      
370            Node dNode = dsource.getNode();
371      
372            if (null != dNode)
373            {
374              try
375              {
376                if(dNode.getNodeType() == Node.ATTRIBUTE_NODE)
377                  this.startDocument();
378                try
379                {
380                  if(dNode.getNodeType() == Node.ATTRIBUTE_NODE)
381                  {
382                    String data = dNode.getNodeValue();
383                    char[] chars = data.toCharArray();
384                    characters(chars, 0, chars.length);
385                  }
386                  else
387                  { 
388                    org.apache.xml.serializer.TreeWalker walker;
389                    walker = new org.apache.xml.serializer.TreeWalker(this, m_systemID);
390                    walker.traverse(dNode);
391                  }
392                }
393                finally
394                {
395                  if(dNode.getNodeType() == Node.ATTRIBUTE_NODE)
396                    this.endDocument();
397                }
398              }
399              catch (SAXException se)
400              {
401                throw new TransformerException(se);
402              }
403      
404              return;
405            }
406            else
407            {
408              String messageStr = XSLMessages.createMessage(
409                XSLTErrorResources.ER_ILLEGAL_DOMSOURCE_INPUT, null);
410      
411              throw new IllegalArgumentException(messageStr);
412            }
413          }
414      
415          InputSource xmlSource = SAXSource.sourceToInputSource(source);
416      
417          if (null == xmlSource)
418          {
419            throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_CANNOT_TRANSFORM_SOURCE_TYPE, new Object[]{source.getClass().getName()})); //"Can't transform a Source of type "
420                                           //+ source.getClass().getName() + "!");
421          }
422      
423          if (null != xmlSource.getSystemId())
424            m_systemID = xmlSource.getSystemId();
425      
426          XMLReader reader = null;
427          boolean managedReader = false;
428      
429          try
430          {
431            if (source instanceof SAXSource) {
432              reader = ((SAXSource) source).getXMLReader();
433            }
434              
435            if (null == reader) {
436              try {
437                reader = XMLReaderManager.getInstance().getXMLReader();
438                managedReader = true;
439              } catch (SAXException se) {
440                throw new TransformerException(se);
441              }
442            } else {
443              try {
444                reader.setFeature("http://xml.org/sax/features/namespace-prefixes",
445                                  true);
446              } catch (org.xml.sax.SAXException se) {
447                // We don't care.
448              }
449            }
450    
451            // Get the input content handler, which will handle the 
452            // parse events and create the source tree. 
453            ContentHandler inputHandler = this;
454      
455            reader.setContentHandler(inputHandler);
456      
457            if (inputHandler instanceof org.xml.sax.DTDHandler)
458              reader.setDTDHandler((org.xml.sax.DTDHandler) inputHandler);
459      
460            try
461            {
462              if (inputHandler instanceof org.xml.sax.ext.LexicalHandler)
463                reader.setProperty("http://xml.org/sax/properties/lexical-handler",
464                                   inputHandler);
465      
466              if (inputHandler instanceof org.xml.sax.ext.DeclHandler)
467                reader.setProperty(
468                  "http://xml.org/sax/properties/declaration-handler",
469                  inputHandler);
470            }
471            catch (org.xml.sax.SAXException se){}
472      
473            try
474            {
475              if (inputHandler instanceof org.xml.sax.ext.LexicalHandler)
476                reader.setProperty("http://xml.org/sax/handlers/LexicalHandler",
477                                   inputHandler);
478      
479              if (inputHandler instanceof org.xml.sax.ext.DeclHandler)
480                reader.setProperty("http://xml.org/sax/handlers/DeclHandler",
481                                   inputHandler);
482            }
483            catch (org.xml.sax.SAXNotRecognizedException snre){}
484      
485            reader.parse(xmlSource);
486          }
487          catch (org.apache.xml.utils.WrappedRuntimeException wre)
488          {
489            Throwable throwable = wre.getException();
490      
491            while (throwable
492                   instanceof org.apache.xml.utils.WrappedRuntimeException)
493            {
494              throwable =
495                ((org.apache.xml.utils.WrappedRuntimeException) throwable).getException();
496            }
497      
498            throw new TransformerException(wre.getException());
499          }
500          catch (org.xml.sax.SAXException se)
501          {
502            throw new TransformerException(se);
503          }
504          catch (IOException ioe)
505          {
506            throw new TransformerException(ioe);
507          } finally {
508            if (managedReader) {
509              XMLReaderManager.getInstance().releaseXMLReader(reader);
510            }
511          }
512        }
513        finally
514        {
515          if(null != m_outputStream)
516          {
517            try
518            {
519              m_outputStream.close();
520            }
521            catch(IOException ioe){}
522            m_outputStream = null;
523          }
524        }
525      }
526    
527      /**
528       * Add a parameter for the transformation.
529       *
530       * <p>Pass a qualified name as a two-part string, the namespace URI
531       * enclosed in curly braces ({}), followed by the local name. If the
532       * name has a null URL, the String only contain the local name. An
533       * application can safely check for a non-null URI by testing to see if the first
534       * character of the name is a '{' character.</p>
535       * <p>For example, if a URI and local name were obtained from an element
536       * defined with &lt;xyz:foo xmlns:xyz="http://xyz.foo.com/yada/baz.html"/&gt;,
537       * then the qualified name would be "{http://xyz.foo.com/yada/baz.html}foo". Note that
538       * no prefix is used.</p>
539       *
540       * @param name The name of the parameter, which may begin with a namespace URI
541       * in curly braces ({}).
542       * @param value The value object.  This can be any valid Java object. It is
543       * up to the processor to provide the proper object coersion or to simply
544       * pass the object on for use in an extension.
545       */
546      public void setParameter(String name, Object value)
547      {
548        if (value == null) {
549          throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_SET_PARAM_VALUE, new Object[]{name}));
550        }
551        
552        if (null == m_params)
553        {
554          m_params = new Hashtable();
555        }
556    
557        m_params.put(name, value);
558      }
559    
560      /**
561       * Get a parameter that was explicitly set with setParameter
562       * or setParameters.
563       *
564       * <p>This method does not return a default parameter value, which
565       * cannot be determined until the node context is evaluated during
566       * the transformation process.
567       *
568       *
569       * @param name Name of the parameter.
570       * @return A parameter that has been set with setParameter.
571       */
572      public Object getParameter(String name)
573      {
574    
575        if (null == m_params)
576          return null;
577    
578        return m_params.get(name);
579      }
580    
581      /**
582       * Clear all parameters set with setParameter.
583       */
584      public void clearParameters()
585      {
586    
587        if (null == m_params)
588          return;
589    
590        m_params.clear();
591      }
592    
593      /**
594       * Set an object that will be used to resolve URIs used in
595       * document().
596       *
597       * <p>If the resolver argument is null, the URIResolver value will
598       * be cleared, and the default behavior will be used.</p>
599       *
600       * @param resolver An object that implements the URIResolver interface,
601       * or null.
602       */
603      public void setURIResolver(URIResolver resolver)
604      {
605        m_URIResolver = resolver;
606      }
607    
608      /**
609       * Get an object that will be used to resolve URIs used in
610       * document(), etc.
611       *
612       * @return An object that implements the URIResolver interface,
613       * or null.
614       */
615      public URIResolver getURIResolver()
616      {
617        return m_URIResolver;
618      }
619    
620      /**
621       * Set the output properties for the transformation.  These
622       * properties will override properties set in the Templates
623       * with xsl:output.
624       *
625       * <p>If argument to this function is null, any properties
626       * previously set are removed, and the value will revert to the value
627       * defined in the templates object.</p>
628       *
629       * <p>Pass a qualified property key name as a two-part string, the namespace URI
630       * enclosed in curly braces ({}), followed by the local name. If the
631       * name has a null URL, the String only contain the local name. An
632       * application can safely check for a non-null URI by testing to see if the first
633       * character of the name is a '{' character.</p>
634       * <p>For example, if a URI and local name were obtained from an element
635       * defined with &lt;xyz:foo xmlns:xyz="http://xyz.foo.com/yada/baz.html"/&gt;,
636       * then the qualified name would be "{http://xyz.foo.com/yada/baz.html}foo". Note that
637       * no prefix is used.</p>
638       *
639       * @param oformat A set of output properties that will be
640       * used to override any of the same properties in affect
641       * for the transformation.
642       *
643       * @see javax.xml.transform.OutputKeys
644       * @see java.util.Properties
645       *
646       * @throws IllegalArgumentException if any of the argument keys are not
647       * recognized and are not namespace qualified.
648       */
649      public void setOutputProperties(Properties oformat)
650              throws IllegalArgumentException
651      {
652    
653        if (null != oformat)
654        {
655    
656          // See if an *explicit* method was set.
657          String method = (String) oformat.get(OutputKeys.METHOD);
658    
659          if (null != method)
660            m_outputFormat = new OutputProperties(method);
661          else
662            m_outputFormat = new OutputProperties();
663    
664          m_outputFormat.copyFrom(oformat);
665        }
666        else {
667          // if oformat is null JAXP says that any props previously set are removed
668          // and we are to revert back to those in the templates object (i.e. Stylesheet).
669          m_outputFormat = null;
670        }
671      }
672    
673      /**
674       * Get a copy of the output properties for the transformation.
675       *
676       * <p>The properties returned should contain properties set by the user,
677       * and properties set by the stylesheet, and these properties
678       * are "defaulted" by default properties specified by <a href="http://www.w3.org/TR/xslt#output">section 16 of the
679       * XSL Transformations (XSLT) W3C Recommendation</a>.  The properties that
680       * were specifically set by the user or the stylesheet should be in the base
681       * Properties list, while the XSLT default properties that were not
682       * specifically set should be the default Properties list.  Thus,
683       * getOutputProperties().getProperty(String key) will obtain any
684       * property in that was set by {@link #setOutputProperty},
685       * {@link #setOutputProperties}, in the stylesheet, <em>or</em> the default
686       * properties, while
687       * getOutputProperties().get(String key) will only retrieve properties
688       * that were explicitly set by {@link #setOutputProperty},
689       * {@link #setOutputProperties}, or in the stylesheet.</p>
690       *
691       * <p>Note that mutation of the Properties object returned will not
692       * effect the properties that the transformation contains.</p>
693       *
694       * <p>If any of the argument keys are not recognized and are not
695       * namespace qualified, the property will be ignored.  In other words the
696       * behaviour is not orthogonal with setOutputProperties.</p>
697       *
698       * @return A copy of the set of output properties in effect
699       * for the next transformation.
700       *
701       * @see javax.xml.transform.OutputKeys
702       * @see java.util.Properties
703       */
704      public Properties getOutputProperties()
705      {
706        return (Properties) m_outputFormat.getProperties().clone();
707      }
708    
709      /**
710       * Set an output property that will be in effect for the
711       * transformation.
712       *
713       * <p>Pass a qualified property name as a two-part string, the namespace URI
714       * enclosed in curly braces ({}), followed by the local name. If the
715       * name has a null URL, the String only contain the local name. An
716       * application can safely check for a non-null URI by testing to see if the first
717       * character of the name is a '{' character.</p>
718       * <p>For example, if a URI and local name were obtained from an element
719       * defined with &lt;xyz:foo xmlns:xyz="http://xyz.foo.com/yada/baz.html"/&gt;,
720       * then the qualified name would be "{http://xyz.foo.com/yada/baz.html}foo". Note that
721       * no prefix is used.</p>
722       *
723       * <p>The Properties object that was passed to {@link #setOutputProperties} won't
724       * be effected by calling this method.</p>
725       *
726       * @param name A non-null String that specifies an output
727       * property name, which may be namespace qualified.
728       * @param value The non-null string value of the output property.
729       *
730       * @throws IllegalArgumentException If the property is not supported, and is
731       * not qualified with a namespace.
732       *
733       * @see javax.xml.transform.OutputKeys
734       */
735      public void setOutputProperty(String name, String value)
736              throws IllegalArgumentException
737      {
738    
739        if (!OutputProperties.isLegalPropertyKey(name))
740          throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_OUTPUT_PROPERTY_NOT_RECOGNIZED, new Object[]{name})); //"output property not recognized: "
741                                             //+ name);
742    
743        m_outputFormat.setProperty(name, value);
744      }
745    
746      /**
747       * Get an output property that is in effect for the
748       * transformation.  The property specified may be a property
749       * that was set with setOutputProperty, or it may be a
750       * property specified in the stylesheet.
751       *
752       * @param name A non-null String that specifies an output
753       * property name, which may be namespace qualified.
754       *
755       * @return The string value of the output property, or null
756       * if no property was found.
757       *
758       * @throws IllegalArgumentException If the property is not supported.
759       *
760       * @see javax.xml.transform.OutputKeys
761       */
762      public String getOutputProperty(String name) throws IllegalArgumentException
763      {
764    
765        String value = null;
766        OutputProperties props = m_outputFormat;
767    
768        value = props.getProperty(name);
769    
770        if (null == value)
771        {
772          if (!OutputProperties.isLegalPropertyKey(name))
773            throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_OUTPUT_PROPERTY_NOT_RECOGNIZED, new Object[]{name})); //"output property not recognized: "
774                                              // + name);
775        }
776    
777        return value;
778      }
779    
780      /**
781       * Set the error event listener in effect for the transformation.
782       *
783       * @param listener The new error listener.
784       * @throws IllegalArgumentException if listener is null.
785       */
786      public void setErrorListener(ErrorListener listener)
787              throws IllegalArgumentException
788      {
789          if (listener == null)
790            throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_NULL_ERROR_HANDLER, null));
791          else
792            m_errorListener = listener;
793      }
794    
795      /**
796       * Get the error event handler in effect for the transformation.
797       *
798       * @return The current error handler, which should never be null.
799       */
800      public ErrorListener getErrorListener()
801      {
802        return m_errorListener;
803      }
804    
805      ////////////////////////////////////////////////////////////////////
806      // Default implementation of DTDHandler interface.
807      ////////////////////////////////////////////////////////////////////
808    
809      /**
810       * Receive notification of a notation declaration.
811       *
812       * <p>By default, do nothing.  Application writers may override this
813       * method in a subclass if they wish to keep track of the notations
814       * declared in a document.</p>
815       *
816       * @param name The notation name.
817       * @param publicId The notation public identifier, or null if not
818       *                 available.
819       * @param systemId The notation system identifier.
820       * @throws org.xml.sax.SAXException Any SAX exception, possibly
821       *            wrapping another exception.
822       * @see org.xml.sax.DTDHandler#notationDecl
823       *
824       * @throws SAXException
825       */
826      public void notationDecl(String name, String publicId, String systemId)
827              throws SAXException
828      {
829        if (null != m_resultDTDHandler)
830          m_resultDTDHandler.notationDecl(name, publicId, systemId);
831      }
832    
833      /**
834       * Receive notification of an unparsed entity declaration.
835       *
836       * <p>By default, do nothing.  Application writers may override this
837       * method in a subclass to keep track of the unparsed entities
838       * declared in a document.</p>
839       *
840       * @param name The entity name.
841       * @param publicId The entity public identifier, or null if not
842       *                 available.
843       * @param systemId The entity system identifier.
844       * @param notationName The name of the associated notation.
845       * @throws org.xml.sax.SAXException Any SAX exception, possibly
846       *            wrapping another exception.
847       * @see org.xml.sax.DTDHandler#unparsedEntityDecl
848       *
849       * @throws SAXException
850       */
851      public void unparsedEntityDecl(
852              String name, String publicId, String systemId, String notationName)
853                throws SAXException
854      {
855    
856        if (null != m_resultDTDHandler)
857          m_resultDTDHandler.unparsedEntityDecl(name, publicId, systemId,
858                                                notationName);
859      }
860    
861      ////////////////////////////////////////////////////////////////////
862      // Default implementation of ContentHandler interface.
863      ////////////////////////////////////////////////////////////////////
864    
865      /**
866       * Receive a Locator object for document events.
867       *
868       * <p>By default, do nothing.  Application writers may override this
869       * method in a subclass if they wish to store the locator for use
870       * with other document events.</p>
871       *
872       * @param locator A locator for all SAX document events.
873       * @see org.xml.sax.ContentHandler#setDocumentLocator
874       * @see org.xml.sax.Locator
875       */
876      public void setDocumentLocator(Locator locator)
877      {
878        try
879        {
880          if (null == m_resultContentHandler)
881            createResultContentHandler(m_result);
882        }
883        catch (TransformerException te)
884        {
885          throw new org.apache.xml.utils.WrappedRuntimeException(te);
886        }
887    
888        m_resultContentHandler.setDocumentLocator(locator);
889      }
890    
891      /**
892       * Receive notification of the beginning of the document.
893       *
894       * <p>By default, do nothing.  Application writers may override this
895       * method in a subclass to take specific actions at the beginning
896       * of a document (such as allocating the root node of a tree or
897       * creating an output file).</p>
898       *
899       * @throws org.xml.sax.SAXException Any SAX exception, possibly
900       *            wrapping another exception.
901       * @see org.xml.sax.ContentHandler#startDocument
902       *
903       * @throws SAXException
904       */
905      public void startDocument() throws SAXException
906      {
907    
908        try
909        {
910          if (null == m_resultContentHandler)
911            createResultContentHandler(m_result);
912        }
913        catch (TransformerException te)
914        {
915          throw new SAXException(te.getMessage(), te);
916        }
917    
918        // Reset for multiple transforms with this transformer.
919        m_flushedStartDoc = false;
920        m_foundFirstElement = false;
921      }
922      
923      boolean m_flushedStartDoc = false;
924      
925      protected final void flushStartDoc()
926         throws SAXException
927      {
928        if(!m_flushedStartDoc)
929        {
930          if (m_resultContentHandler == null)
931          {
932            try
933            {
934              createResultContentHandler(m_result);
935            }
936            catch(TransformerException te)
937            {
938                throw new SAXException(te);
939            }
940          }
941          m_resultContentHandler.startDocument();
942          m_flushedStartDoc = true;
943        }
944      }
945    
946      /**
947       * Receive notification of the end of the document.
948       *
949       * <p>By default, do nothing.  Application writers may override this
950       * method in a subclass to take specific actions at the end
951       * of a document (such as finalising a tree or closing an output
952       * file).</p>
953       *
954       * @throws org.xml.sax.SAXException Any SAX exception, possibly
955       *            wrapping another exception.
956       * @see org.xml.sax.ContentHandler#endDocument
957       *
958       * @throws SAXException
959       */
960      public void endDocument() throws SAXException
961      {
962        flushStartDoc();
963        m_resultContentHandler.endDocument();
964      }
965    
966      /**
967       * Receive notification of the start of a Namespace mapping.
968       *
969       * <p>By default, do nothing.  Application writers may override this
970       * method in a subclass to take specific actions at the start of
971       * each Namespace prefix scope (such as storing the prefix mapping).</p>
972       *
973       * @param prefix The Namespace prefix being declared.
974       * @param uri The Namespace URI mapped to the prefix.
975       * @throws org.xml.sax.SAXException Any SAX exception, possibly
976       *            wrapping another exception.
977       * @see org.xml.sax.ContentHandler#startPrefixMapping
978       *
979       * @throws SAXException
980       */
981      public void startPrefixMapping(String prefix, String uri)
982              throws SAXException
983      {
984        flushStartDoc();
985        m_resultContentHandler.startPrefixMapping(prefix, uri);
986      }
987    
988      /**
989       * Receive notification of the end of a Namespace mapping.
990       *
991       * <p>By default, do nothing.  Application writers may override this
992       * method in a subclass to take specific actions at the end of
993       * each prefix mapping.</p>
994       *
995       * @param prefix The Namespace prefix being declared.
996       * @throws org.xml.sax.SAXException Any SAX exception, possibly
997       *            wrapping another exception.
998       * @see org.xml.sax.ContentHandler#endPrefixMapping
999       *
1000       * @throws SAXException
1001       */
1002      public void endPrefixMapping(String prefix) throws SAXException
1003      {
1004        flushStartDoc();
1005        m_resultContentHandler.endPrefixMapping(prefix);
1006      }
1007    
1008      /**
1009       * Receive notification of the start of an element.
1010       *
1011       * <p>By default, do nothing.  Application writers may override this
1012       * method in a subclass to take specific actions at the start of
1013       * each element (such as allocating a new tree node or writing
1014       * output to a file).</p>
1015       *
1016       * @param uri The Namespace URI, or the empty string if the
1017       *        element has no Namespace URI or if Namespace
1018       *        processing is not being performed.
1019       * @param localName The local name (without prefix), or the
1020       *        empty string if Namespace processing is not being
1021       *        performed.
1022       * @param qName The qualified name (with prefix), or the
1023       *        empty string if qualified names are not available.
1024       * @param attributes The specified or defaulted attributes.
1025       * @throws org.xml.sax.SAXException Any SAX exception, possibly
1026       *            wrapping another exception.
1027       * @see org.xml.sax.ContentHandler#startElement
1028       *
1029       * @throws SAXException
1030       */
1031      public void startElement(
1032              String uri, String localName, String qName, Attributes attributes)
1033                throws SAXException
1034      {
1035    
1036        if (!m_foundFirstElement && null != m_serializer)
1037        {
1038          m_foundFirstElement = true;
1039    
1040          Serializer newSerializer;
1041    
1042          try
1043          {
1044            newSerializer = SerializerSwitcher.switchSerializerIfHTML(uri,
1045                    localName, m_outputFormat.getProperties(), m_serializer);
1046          }
1047          catch (TransformerException te)
1048          {
1049            throw new SAXException(te);
1050          }
1051    
1052          if (newSerializer != m_serializer)
1053          {
1054            try
1055            {
1056              m_resultContentHandler = newSerializer.asContentHandler();
1057            }
1058            catch (IOException ioe)  // why?
1059            {
1060              throw new SAXException(ioe);
1061            }
1062    
1063            if (m_resultContentHandler instanceof DTDHandler)
1064              m_resultDTDHandler = (DTDHandler) m_resultContentHandler;
1065    
1066            if (m_resultContentHandler instanceof LexicalHandler)
1067              m_resultLexicalHandler = (LexicalHandler) m_resultContentHandler;
1068    
1069            m_serializer = newSerializer;
1070          }
1071        }
1072        flushStartDoc();
1073        m_resultContentHandler.startElement(uri, localName, qName, attributes);
1074      }
1075    
1076      /**
1077       * Receive notification of the end of an element.
1078       *
1079       * <p>By default, do nothing.  Application writers may override this
1080       * method in a subclass to take specific actions at the end of
1081       * each element (such as finalising a tree node or writing
1082       * output to a file).</p>
1083       *
1084       * @param uri The Namespace URI, or the empty string if the
1085       *        element has no Namespace URI or if Namespace
1086       *        processing is not being performed.
1087       * @param localName The local name (without prefix), or the
1088       *        empty string if Namespace processing is not being
1089       *        performed.
1090       * @param qName The qualified name (with prefix), or the
1091       *        empty string if qualified names are not available.
1092       *
1093       * @throws org.xml.sax.SAXException Any SAX exception, possibly
1094       *            wrapping another exception.
1095       * @see org.xml.sax.ContentHandler#endElement
1096       *
1097       * @throws SAXException
1098       */
1099      public void endElement(String uri, String localName, String qName)
1100              throws SAXException
1101      {
1102        m_resultContentHandler.endElement(uri, localName, qName);
1103      }
1104    
1105      /**
1106       * Receive notification of character data inside an element.
1107       *
1108       * <p>By default, do nothing.  Application writers may override this
1109       * method to take specific actions for each chunk of character data
1110       * (such as adding the data to a node or buffer, or printing it to
1111       * a file).</p>
1112       *
1113       * @param ch The characters.
1114       * @param start The start position in the character array.
1115       * @param length The number of characters to use from the
1116       *               character array.
1117       * @throws org.xml.sax.SAXException Any SAX exception, possibly
1118       *            wrapping another exception.
1119       * @see org.xml.sax.ContentHandler#characters
1120       *
1121       * @throws SAXException
1122       */
1123      public void characters(char ch[], int start, int length) throws SAXException
1124      {
1125        flushStartDoc();
1126        m_resultContentHandler.characters(ch, start, length);
1127      }
1128    
1129      /**
1130       * Receive notification of ignorable whitespace in element content.
1131       *
1132       * <p>By default, do nothing.  Application writers may override this
1133       * method to take specific actions for each chunk of ignorable
1134       * whitespace (such as adding data to a node or buffer, or printing
1135       * it to a file).</p>
1136       *
1137       * @param ch The whitespace characters.
1138       * @param start The start position in the character array.
1139       * @param length The number of characters to use from the
1140       *               character array.
1141       * @throws org.xml.sax.SAXException Any SAX exception, possibly
1142       *            wrapping another exception.
1143       * @see org.xml.sax.ContentHandler#ignorableWhitespace
1144       *
1145       * @throws SAXException
1146       */
1147      public void ignorableWhitespace(char ch[], int start, int length)
1148              throws SAXException
1149      {
1150        m_resultContentHandler.ignorableWhitespace(ch, start, length);
1151      }
1152    
1153      /**
1154       * Receive notification of a processing instruction.
1155       *
1156       * <p>By default, do nothing.  Application writers may override this
1157       * method in a subclass to take specific actions for each
1158       * processing instruction, such as setting status variables or
1159       * invoking other methods.</p>
1160       *
1161       * @param target The processing instruction target.
1162       * @param data The processing instruction data, or null if
1163       *             none is supplied.
1164       * @throws org.xml.sax.SAXException Any SAX exception, possibly
1165       *            wrapping another exception.
1166       * @see org.xml.sax.ContentHandler#processingInstruction
1167       *
1168       * @throws SAXException
1169       */
1170      public void processingInstruction(String target, String data)
1171              throws SAXException
1172      {
1173        flushStartDoc();
1174        m_resultContentHandler.processingInstruction(target, data);
1175      }
1176    
1177      /**
1178       * Receive notification of a skipped entity.
1179       *
1180       * <p>By default, do nothing.  Application writers may override this
1181       * method in a subclass to take specific actions for each
1182       * processing instruction, such as setting status variables or
1183       * invoking other methods.</p>
1184       *
1185       * @param name The name of the skipped entity.
1186       * @throws org.xml.sax.SAXException Any SAX exception, possibly
1187       *            wrapping another exception.
1188       * @see org.xml.sax.ContentHandler#processingInstruction
1189       *
1190       * @throws SAXException
1191       */
1192      public void skippedEntity(String name) throws SAXException
1193      {
1194        flushStartDoc();
1195        m_resultContentHandler.skippedEntity(name);
1196      }
1197    
1198      /**
1199       * Report the start of DTD declarations, if any.
1200       *
1201       * <p>Any declarations are assumed to be in the internal subset
1202       * unless otherwise indicated by a {@link #startEntity startEntity}
1203       * event.</p>
1204       *
1205       * <p>Note that the start/endDTD events will appear within
1206       * the start/endDocument events from ContentHandler and
1207       * before the first startElement event.</p>
1208       *
1209       * @param name The document type name.
1210       * @param publicId The declared public identifier for the
1211       *        external DTD subset, or null if none was declared.
1212       * @param systemId The declared system identifier for the
1213       *        external DTD subset, or null if none was declared.
1214       * @throws SAXException The application may raise an
1215       *            exception.
1216       * @see #endDTD
1217       * @see #startEntity
1218       */
1219      public void startDTD(String name, String publicId, String systemId)
1220              throws SAXException
1221      {
1222        flushStartDoc();
1223        if (null != m_resultLexicalHandler)
1224          m_resultLexicalHandler.startDTD(name, publicId, systemId);
1225      }
1226    
1227      /**
1228       * Report the end of DTD declarations.
1229       *
1230       * @throws SAXException The application may raise an exception.
1231       * @see #startDTD
1232       */
1233      public void endDTD() throws SAXException
1234      {
1235        if (null != m_resultLexicalHandler)
1236          m_resultLexicalHandler.endDTD();
1237      }
1238    
1239      /**
1240       * Report the beginning of an entity in content.
1241       *
1242       * <p><strong>NOTE:</entity> entity references in attribute
1243       * values -- and the start and end of the document entity --
1244       * are never reported.</p>
1245       *
1246       * <p>The start and end of the external DTD subset are reported
1247       * using the pseudo-name "[dtd]".  All other events must be
1248       * properly nested within start/end entity events.</p>
1249       *
1250       * <p>Note that skipped entities will be reported through the
1251       * {@link org.xml.sax.ContentHandler#skippedEntity skippedEntity}
1252       * event, which is part of the ContentHandler interface.</p>
1253       *
1254       * @param name The name of the entity.  If it is a parameter
1255       *        entity, the name will begin with '%'.
1256       * @throws SAXException The application may raise an exception.
1257       * @see #endEntity
1258       * @see org.xml.sax.ext.DeclHandler#internalEntityDecl
1259       * @see org.xml.sax.ext.DeclHandler#externalEntityDecl
1260       */
1261      public void startEntity(String name) throws SAXException
1262      {
1263        if (null != m_resultLexicalHandler)
1264          m_resultLexicalHandler.startEntity(name);
1265      }
1266    
1267      /**
1268       * Report the end of an entity.
1269       *
1270       * @param name The name of the entity that is ending.
1271       * @throws SAXException The application may raise an exception.
1272       * @see #startEntity
1273       */
1274      public void endEntity(String name) throws SAXException
1275      {
1276        if (null != m_resultLexicalHandler)
1277          m_resultLexicalHandler.endEntity(name);
1278      }
1279    
1280      /**
1281       * Report the start of a CDATA section.
1282       *
1283       * <p>The contents of the CDATA section will be reported through
1284       * the regular {@link org.xml.sax.ContentHandler#characters
1285       * characters} event.</p>
1286       *
1287       * @throws SAXException The application may raise an exception.
1288       * @see #endCDATA
1289       */
1290      public void startCDATA() throws SAXException
1291      {
1292        if (null != m_resultLexicalHandler)
1293          m_resultLexicalHandler.startCDATA();
1294      }
1295    
1296      /**
1297       * Report the end of a CDATA section.
1298       *
1299       * @throws SAXException The application may raise an exception.
1300       * @see #startCDATA
1301       */
1302      public void endCDATA() throws SAXException
1303      {
1304        if (null != m_resultLexicalHandler)
1305          m_resultLexicalHandler.endCDATA();
1306      }
1307    
1308      /**
1309       * Report an XML comment anywhere in the document.
1310       *
1311       * <p>This callback will be used for comments inside or outside the
1312       * document element, including comments in the external DTD
1313       * subset (if read).</p>
1314       *
1315       * @param ch An array holding the characters in the comment.
1316       * @param start The starting position in the array.
1317       * @param length The number of characters to use from the array.
1318       * @throws SAXException The application may raise an exception.
1319       */
1320      public void comment(char ch[], int start, int length) throws SAXException
1321      {
1322        flushStartDoc();
1323        if (null != m_resultLexicalHandler)
1324          m_resultLexicalHandler.comment(ch, start, length);
1325      }
1326      
1327      // Implement DeclHandler
1328      
1329      /**
1330         * Report an element type declaration.
1331         *
1332         * <p>The content model will consist of the string "EMPTY", the
1333         * string "ANY", or a parenthesised group, optionally followed
1334         * by an occurrence indicator.  The model will be normalized so
1335         * that all whitespace is removed,and will include the enclosing
1336         * parentheses.</p>
1337         *
1338         * @param name The element type name.
1339         * @param model The content model as a normalized string.
1340         * @exception SAXException The application may raise an exception.
1341         */
1342        public void elementDecl (String name, String model)
1343            throws SAXException
1344        {
1345                            if (null != m_resultDeclHandler)
1346                                    m_resultDeclHandler.elementDecl(name, model);
1347        }
1348    
1349    
1350        /**
1351         * Report an attribute type declaration.
1352         *
1353         * <p>Only the effective (first) declaration for an attribute will
1354         * be reported.  The type will be one of the strings "CDATA",
1355         * "ID", "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY",
1356         * "ENTITIES", or "NOTATION", or a parenthesized token group with 
1357         * the separator "|" and all whitespace removed.</p>
1358         *
1359         * @param eName The name of the associated element.
1360         * @param aName The name of the attribute.
1361         * @param type A string representing the attribute type.
1362         * @param valueDefault A string representing the attribute default
1363         *        ("#IMPLIED", "#REQUIRED", or "#FIXED") or null if
1364         *        none of these applies.
1365         * @param value A string representing the attribute's default value,
1366         *        or null if there is none.
1367         * @exception SAXException The application may raise an exception.
1368         */
1369        public void attributeDecl (String eName,
1370                                            String aName,
1371                                            String type,
1372                                            String valueDefault,
1373                                            String value)
1374            throws SAXException
1375        {
1376          if (null != m_resultDeclHandler)
1377                                    m_resultDeclHandler.attributeDecl(eName, aName, type, valueDefault, value);
1378        }
1379    
1380    
1381        /**
1382         * Report an internal entity declaration.
1383         *
1384         * <p>Only the effective (first) declaration for each entity
1385         * will be reported.</p>
1386         *
1387         * @param name The name of the entity.  If it is a parameter
1388         *        entity, the name will begin with '%'.
1389         * @param value The replacement text of the entity.
1390         * @exception SAXException The application may raise an exception.
1391         * @see #externalEntityDecl
1392         * @see org.xml.sax.DTDHandler#unparsedEntityDecl
1393         */
1394        public void internalEntityDecl (String name, String value)
1395            throws SAXException
1396        {
1397          if (null != m_resultDeclHandler)
1398                                    m_resultDeclHandler.internalEntityDecl(name, value); 
1399        }
1400    
1401    
1402        /**
1403         * Report a parsed external entity declaration.
1404         *
1405         * <p>Only the effective (first) declaration for each entity
1406         * will be reported.</p>
1407         *
1408         * @param name The name of the entity.  If it is a parameter
1409         *        entity, the name will begin with '%'.
1410         * @param publicId The declared public identifier of the entity, or
1411         *        null if none was declared.
1412         * @param systemId The declared system identifier of the entity.
1413         * @exception SAXException The application may raise an exception.
1414         * @see #internalEntityDecl
1415         * @see org.xml.sax.DTDHandler#unparsedEntityDecl
1416         */
1417        public void externalEntityDecl (String name, String publicId,
1418                                                 String systemId)
1419            throws SAXException
1420        {
1421          if (null != m_resultDeclHandler)
1422                                    m_resultDeclHandler.externalEntityDecl(name, publicId, systemId);
1423        }
1424      
1425      /**
1426       * This is null unless we own the stream.
1427       */
1428      private java.io.FileOutputStream m_outputStream = null;
1429    
1430      /** The content handler where result events will be sent. */
1431      private ContentHandler m_resultContentHandler;
1432    
1433      /** The lexical handler where result events will be sent. */
1434      private LexicalHandler m_resultLexicalHandler;
1435    
1436      /** The DTD handler where result events will be sent. */
1437      private DTDHandler m_resultDTDHandler;
1438      
1439      /** The Decl handler where result events will be sent. */
1440      private DeclHandler m_resultDeclHandler;
1441    
1442      /** The Serializer, which may or may not be null. */
1443      private Serializer m_serializer;
1444    
1445      /** The Result object. */
1446      private Result m_result;
1447    
1448      /**
1449       * The system ID, which is unused, but must be returned to fullfill the
1450       *  TransformerHandler interface.
1451       */
1452      private String m_systemID;
1453    
1454      /**
1455       * The parameters, which is unused, but must be returned to fullfill the
1456       *  Transformer interface.
1457       */
1458      private Hashtable m_params;
1459    
1460      /** The error listener for TrAX errors and warnings. */
1461      private ErrorListener m_errorListener =
1462        new org.apache.xml.utils.DefaultErrorHandler(false);
1463    
1464      /**
1465       * The URIResolver, which is unused, but must be returned to fullfill the
1466       *  TransformerHandler interface.
1467       */
1468      URIResolver m_URIResolver;
1469    
1470      /** The output properties. */
1471      private OutputProperties m_outputFormat;
1472    
1473      /** Flag to set if we've found the first element, so we can tell if we have 
1474       *  to check to see if we should create an HTML serializer.      */
1475      boolean m_foundFirstElement;
1476      
1477      /**
1478       * State of the secure processing feature.
1479       */
1480      private boolean m_isSecureProcessing = false;
1481    }