1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the  "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18/*
19 * $Id: TransformerIdentityImpl.java 575747 2007-09-14 16:28:37Z kcormier $
20 */
21package org.apache.xalan.transformer;
22
23import java.io.IOException;
24import java.util.Hashtable;
25import java.util.Properties;
26
27import javax.xml.XMLConstants;
28import javax.xml.parsers.DocumentBuilder;
29import javax.xml.parsers.DocumentBuilderFactory;
30import javax.xml.parsers.ParserConfigurationException;
31import javax.xml.transform.ErrorListener;
32import javax.xml.transform.OutputKeys;
33import javax.xml.transform.Result;
34import javax.xml.transform.Source;
35import javax.xml.transform.Transformer;
36import javax.xml.transform.TransformerException;
37import javax.xml.transform.URIResolver;
38import javax.xml.transform.dom.DOMResult;
39import javax.xml.transform.dom.DOMSource;
40import javax.xml.transform.sax.SAXResult;
41import javax.xml.transform.sax.SAXSource;
42import javax.xml.transform.sax.TransformerHandler;
43import javax.xml.transform.stream.StreamSource;
44import javax.xml.transform.stream.StreamResult;
45
46import org.apache.xalan.res.XSLMessages;
47import org.apache.xalan.res.XSLTErrorResources;
48import org.apache.xalan.templates.OutputProperties;
49import org.apache.xml.serializer.Serializer;
50import org.apache.xml.serializer.SerializerFactory;
51import org.apache.xml.serializer.Method;
52import org.apache.xml.utils.DOMBuilder;
53import org.apache.xml.utils.XMLReaderManager;
54
55import org.w3c.dom.Document;
56import org.w3c.dom.DocumentFragment;
57import org.w3c.dom.Node;
58
59import org.xml.sax.Attributes;
60import org.xml.sax.ContentHandler;
61import org.xml.sax.DTDHandler;
62import org.xml.sax.InputSource;
63import org.xml.sax.Locator;
64import org.xml.sax.SAXException;
65import org.xml.sax.XMLReader;
66import org.xml.sax.ext.DeclHandler;
67import org.xml.sax.ext.LexicalHandler;
68
69/**
70 * This class implements an identity transformer for
71 * {@link javax.xml.transform.sax.SAXTransformerFactory#newTransformerHandler()}
72 * and {@link javax.xml.transform.TransformerFactory#newTransformer()}.  It
73 * simply feeds SAX events directly to a serializer ContentHandler, if the
74 * result is a stream.  If the result is a DOM, it will send the events to
75 * {@link org.apache.xml.utils.DOMBuilder}.  If the result is another
76 * content handler, it will simply pass the events on.
77 */
78public class TransformerIdentityImpl extends Transformer
79        implements TransformerHandler, DeclHandler
80{
81
82  /**
83   * Constructor TransformerIdentityImpl creates an identity transform.
84   *
85   */
86  public TransformerIdentityImpl(boolean isSecureProcessing)
87  {
88    m_outputFormat = new OutputProperties(Method.XML);
89    m_isSecureProcessing = isSecureProcessing;
90  }
91
92  /**
93   * Constructor TransformerIdentityImpl creates an identity transform.
94   *
95   */
96  public TransformerIdentityImpl()
97  {
98    this(false);
99  }
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}
1482