1/*
2 * Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
3 */
4/*
5 * Licensed to the Apache Software Foundation (ASF) under one or more
6 * contributor license agreements.  See the NOTICE file distributed with
7 * this work for additional information regarding copyright ownership.
8 * The ASF licenses this file to You under the Apache License, Version 2.0
9 * (the "License"); you may not use this file except in compliance with
10 * the License.  You may obtain a copy of the License at
11 *
12 *     http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20
21package com.sun.org.apache.xml.internal.serializer;
22
23import java.io.IOException;
24import java.io.OutputStream;
25import java.io.Writer;
26import java.util.Properties;
27import java.util.ArrayList;
28import javax.xml.transform.SourceLocator;
29import javax.xml.transform.Transformer;
30import org.w3c.dom.Node;
31import org.xml.sax.Attributes;
32import org.xml.sax.ContentHandler;
33import org.xml.sax.Locator;
34import org.xml.sax.SAXException;
35
36/**
37 *This class wraps another SerializationHandler. The wrapped object will either
38 * handler XML or HTML, which is not known until a little later when the first XML
39 * tag is seen.  If the first tag is <html> then the wrapped object is an HTML
40 * handler, otherwise it is an XML handler.
41 *
42 * This class effectively caches the first few calls to it then passes them
43 * on to the wrapped handler (once it exists).  After that subsequent calls a
44 * simply passed directly to the wrapped handler.
45 *
46 * The user of this class doesn't know if the output is ultimatley XML or HTML.
47 *
48 * This class is not a public API, it is public because it is used within Xalan.
49 * @xsl.usage internal
50 */
51public final class ToUnknownStream extends SerializerBase
52{
53    /**
54     * The wrapped handler, initially XML but possibly switched to HTML
55     */
56    private SerializationHandler m_handler;
57
58    /**
59     * A String with no characters
60     */
61    private static final String EMPTYSTRING = "";
62
63    /**
64     * true if the underlying handler (XML or HTML) is fully initialized
65     */
66    private boolean m_wrapped_handler_not_initialized = false;
67
68    /**
69     * the prefix of the very first tag in the document
70     */
71    private String m_firstElementPrefix;
72
73    /**
74     * the element name (including any prefix) of the very first tag in the document
75     */
76    private String m_firstElementName;
77
78    /**
79     * the namespace URI associated with the first element
80     */
81    private String m_firstElementURI;
82
83    /**
84     * the local name (no prefix) associated with the first element
85     */
86    private String m_firstElementLocalName = null;
87
88    /**
89     * true if the first tag has been emitted to the wrapped handler
90     */
91    private boolean m_firstTagNotEmitted = true;
92
93    /**
94     * A collection of namespace URI's (only for first element).
95     * _namespacePrefix has the matching prefix for these URI's
96     */
97    private ArrayList<String> m_namespaceURI = null;
98
99    /**
100     * A collection of namespace Prefix (only for first element)
101     * _namespaceURI has the matching URIs for these prefix'
102     */
103    private ArrayList<String> m_namespacePrefix = null;
104
105    /**
106     * true if startDocument() was called before the underlying handler
107     * was initialized
108     */
109    private boolean m_needToCallStartDocument = false;
110
111    /**
112     * Default constructor.
113     * Initially this object wraps an XML Stream object, so _handler is never null.
114     * That may change later to an HTML Stream object.
115     */
116    public ToUnknownStream() {
117        m_handler = new ToXMLStream();
118    }
119
120    /**
121     * @see Serializer#asContentHandler()
122     * @return the wrapped XML or HTML handler
123     */
124    public ContentHandler asContentHandler() throws IOException {
125        /* don't return the real handler ( m_handler ) because
126         * that would expose the real handler to the outside.
127         * Keep m_handler private so it can be internally swapped
128         * to an HTML handler.
129         */
130        return this;
131    }
132
133    /**
134     * @see SerializationHandler#close()
135     */
136    public void close() {
137        m_handler.close();
138    }
139
140    /**
141     * @see Serializer#getOutputFormat()
142     * @return the properties of the underlying handler
143     */
144    public Properties getOutputFormat() {
145        return m_handler.getOutputFormat();
146    }
147
148    /**
149     * @see Serializer#getOutputStream()
150     * @return the OutputStream of the underlying XML or HTML handler
151     */
152    public OutputStream getOutputStream() {
153        return m_handler.getOutputStream();
154    }
155
156    /**
157     * @see Serializer#getWriter()
158     * @return the Writer of the underlying XML or HTML handler
159     */
160    public Writer getWriter() {
161        return m_handler.getWriter();
162    }
163
164    /**
165     * passes the call on to the underlying HTML or XML handler
166     * @see Serializer#reset()
167     * @return ???
168     */
169    public boolean reset() {
170        return m_handler.reset();
171    }
172
173    /**
174     * Converts the DOM node to output
175     * @param node the DOM node to transform to output
176     * @see DOMSerializer#serialize(Node)
177     *
178     */
179    public void serialize(Node node) throws IOException {
180        if (m_firstTagNotEmitted) {
181            flush();
182        }
183        m_handler.serialize(node);
184    }
185
186    /**
187     * @see SerializationHandler#setEscaping(boolean)
188     */
189    public boolean setEscaping(boolean escape) throws SAXException {
190        return m_handler.setEscaping(escape);
191    }
192
193    /**
194     * Set the properties of the handler
195     * @param format the output properties to set
196     * @see Serializer#setOutputFormat(Properties)
197     */
198    public void setOutputFormat(Properties format) {
199        m_handler.setOutputFormat(format);
200    }
201
202    /**
203     * Sets the output stream to write to
204     * @param output the OutputStream to write to
205     * @see Serializer#setOutputStream(OutputStream)
206     */
207    public void setOutputStream(OutputStream output) {
208        m_handler.setOutputStream(output);
209    }
210
211    /**
212     * Sets the writer to write to
213     * @param writer the writer to write to
214     * @see Serializer#setWriter(Writer)
215     */
216    public void setWriter(Writer writer) {
217        m_handler.setWriter(writer);
218    }
219
220    /**
221     * Adds an attribute to the currenly open tag
222     * @param uri the URI of a namespace
223     * @param localName the attribute name, without prefix
224     * @param rawName the attribute name, with prefix (if any)
225     * @param type the type of the attribute, typically "CDATA"
226     * @param value the value of the parameter
227     * @param XSLAttribute true if this attribute is coming from an xsl:attribute element
228     * @see ExtendedContentHandler#addAttribute(String, String, String, String, String)
229     */
230    public void addAttribute(String uri, String localName, String rawName,
231                             String type, String value)
232        throws SAXException
233    {
234        addAttribute(uri, localName, rawName, type, value, false);
235    }
236
237    /**
238     * Adds an attribute to the currenly open tag
239     * @param uri the URI of a namespace
240     * @param localName the attribute name, without prefix
241     * @param rawName the attribute name, with prefix (if any)
242     * @param type the type of the attribute, typically "CDATA"
243     * @param value the value of the parameter
244     * @param XSLAttribute true if this attribute is coming from an xsl:attribute element
245     * @see ExtendedContentHandler#addAttribute(String, String, String, String, String)
246     */
247    public void addAttribute(String uri, String localName, String rawName,
248                             String type, String value, boolean XSLAttribute)
249        throws SAXException
250    {
251        if (m_firstTagNotEmitted) {
252            flush();
253        }
254        m_handler.addAttribute(uri, localName, rawName, type, value, XSLAttribute);
255    }
256
257    /**
258     * Adds an attribute to the currenly open tag
259     * @param rawName the attribute name, with prefix (if any)
260     * @param value the value of the parameter
261     * @see ExtendedContentHandler#addAttribute(String, String)
262     */
263    public void addAttribute(String rawName, String value) {
264        if (m_firstTagNotEmitted) {
265            flush();
266        }
267        m_handler.addAttribute(rawName, value);
268    }
269
270    /**
271     * Adds a unique attribute to the currenly open tag
272     */
273    public void addUniqueAttribute(String rawName, String value, int flags)
274        throws SAXException
275    {
276        if (m_firstTagNotEmitted) {
277            flush();
278        }
279        m_handler.addUniqueAttribute(rawName, value, flags);
280    }
281
282    /**
283     * Converts the String to a character array and calls the SAX method
284     * characters(char[],int,int);
285     *
286     * @param chars The string of characters to process.
287     *
288     * @throws org.xml.sax.SAXException
289     *
290     * @see ExtendedContentHandler#characters(String)
291     */
292    public void characters(String chars) throws SAXException {
293        final int len = (chars == null) ? 0 : chars.length();
294        if (len > m_charsBuff.length) {
295            m_charsBuff = new char[len * 2 + 1];
296        }
297        if (len > 0) {
298            chars.getChars(0, len, m_charsBuff, 0);
299        }
300        this.characters(m_charsBuff, 0, len);
301    }
302
303    /**
304     * Pass the call on to the underlying handler
305     * @see ExtendedContentHandler#endElement(String)
306     */
307    public void endElement(String elementName) throws SAXException {
308        if (m_firstTagNotEmitted) {
309            flush();
310        }
311        m_handler.endElement(elementName);
312    }
313
314    /**
315     * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String)
316     * @param prefix The prefix that maps to the URI
317     * @param uri The URI for the namespace
318     */
319    public void startPrefixMapping(String prefix, String uri) throws SAXException {
320        this.startPrefixMapping(prefix,uri, true);
321    }
322
323    /**
324     * This method is used when a prefix/uri namespace mapping
325     * is indicated after the element was started with a
326     * startElement() and before and endElement().
327     * startPrefixMapping(prefix,uri) would be used before the
328     * startElement() call.
329     * @param uri the URI of the namespace
330     * @param prefix the prefix associated with the given URI.
331     *
332     * @see ExtendedContentHandler#namespaceAfterStartElement(String, String)
333     */
334    public void namespaceAfterStartElement(String prefix, String uri)
335        throws SAXException
336    {
337        // hack for XSLTC with finding URI for default namespace
338        if (m_firstTagNotEmitted &&
339            m_firstElementURI == null &&
340            m_firstElementName != null)
341        {
342            String prefix1 = getPrefixPart(m_firstElementName);
343            if (prefix1 == null && EMPTYSTRING.equals(prefix)) {
344                // the elements URI is not known yet, and it
345                // doesn't have a prefix, and we are currently
346                // setting the uri for prefix "", so we have
347                // the uri for the element... lets remember it
348                m_firstElementURI = uri;
349            }
350        }
351        startPrefixMapping(prefix, uri, false);
352    }
353
354    public boolean startPrefixMapping(String prefix, String uri, boolean shouldFlush)
355        throws SAXException
356    {
357        boolean pushed = false;
358        if (m_firstTagNotEmitted) {
359            if (m_firstElementName != null && shouldFlush) {
360                /* we've already seen a startElement, and this is a prefix mapping
361                 * for the up coming element, so flush the old element
362                 * then send this event on its way.
363                 */
364                flush();
365                pushed = m_handler.startPrefixMapping(prefix, uri, shouldFlush);
366            } else {
367                if (m_namespacePrefix == null) {
368                    m_namespacePrefix = new ArrayList<>();
369                    m_namespaceURI = new ArrayList<>();
370                }
371                m_namespacePrefix.add(prefix);
372                m_namespaceURI.add(uri);
373
374                if (m_firstElementURI == null) {
375                    if (prefix.equals(m_firstElementPrefix))
376                        m_firstElementURI = uri;
377                }
378            }
379        } else {
380            pushed = m_handler.startPrefixMapping(prefix, uri, shouldFlush);
381        }
382        return pushed;
383    }
384
385    /**
386      * This method cannot be cached because default is different in
387      * HTML and XML (we need more than a boolean).
388      */
389    public void setVersion(String version) {
390        m_handler.setVersion(version);
391    }
392
393    /**
394     * @see org.xml.sax.ContentHandler#startDocument()
395     */
396    public void startDocument() throws SAXException {
397        m_needToCallStartDocument = true;
398    }
399
400    public void startElement(String qName) throws SAXException {
401        this.startElement(null, null, qName, null);
402    }
403
404    public void startElement(String namespaceURI, String localName,
405                             String qName) throws SAXException {
406        this.startElement(namespaceURI, localName, qName, null);
407    }
408
409    public void startElement(String namespaceURI, String localName,
410                             String elementName, Attributes atts)
411        throws SAXException
412    {
413        if (m_needToCallSetDocumentInfo) {
414            super.setDocumentInfo();
415            m_needToCallSetDocumentInfo = false;
416        }
417
418        /* we are notified of the start of an element */
419        if (m_firstTagNotEmitted) {
420            /* we have not yet sent the first element on its way */
421            if (m_firstElementName != null) {
422                /* this is not the first element, but a later one.
423                 * But we have the old element pending, so flush it out,
424                 * then send this one on its way.
425                 */
426                flush();
427                m_handler.startElement(namespaceURI, localName, elementName,  atts);
428            }
429            else
430            {
431                /* this is the very first element that we have seen,
432                 * so save it for flushing later.  We may yet get to know its
433                 * URI due to added attributes.
434                 */
435
436                m_wrapped_handler_not_initialized = true;
437                m_firstElementName = elementName;
438
439                // null if not known
440                m_firstElementPrefix = getPrefixPartUnknown(elementName);
441
442                // null if not known
443                m_firstElementURI = namespaceURI;
444
445                // null if not known
446                m_firstElementLocalName = localName;
447
448                if (m_tracer != null)
449                    firePseudoElement(elementName);
450
451                /* we don't want to call our own addAttributes, which
452                 * merely delegates to the wrapped handler, but we want to
453                 * add these attributes to m_attributes. So me must call super.
454                 * addAttributes() In this case m_attributes is only used for the
455                 * first element, after that this class totally delegates to the
456                 * wrapped handler which is either XML or HTML.
457                 */
458                if (atts != null)
459                    super.addAttributes(atts);
460
461                // if there are attributes, then lets make the flush()
462                // call the startElement on the handler and send the
463                // attributes on their way.
464                if (atts != null)
465                    flush();
466
467            }
468        }
469        else
470        {
471            // this is not the first element, but a later one, so just
472            // send it on its way.
473            m_handler.startElement(namespaceURI, localName, elementName,  atts);
474        }
475    }
476
477    /**
478     * Pass the call on to the underlying handler
479     * @see ExtendedLexicalHandler#comment(String)
480     */
481    public void comment(String comment) throws SAXException
482    {
483        if (m_firstTagNotEmitted && m_firstElementName != null)
484        {
485            emitFirstTag();
486        }
487        else if (m_needToCallStartDocument)
488        {
489            m_handler.startDocument();
490            m_needToCallStartDocument = false;
491        }
492
493        m_handler.comment(comment);
494    }
495
496    /**
497     * Pass the call on to the underlying handler
498     * @see XSLOutputAttributes#getDoctypePublic()
499     */
500    public String getDoctypePublic()
501    {
502
503        return m_handler.getDoctypePublic();
504    }
505
506    /**
507     * Pass the call on to the underlying handler
508     * @see XSLOutputAttributes#getDoctypeSystem()
509     */
510    public String getDoctypeSystem()
511    {
512        return m_handler.getDoctypeSystem();
513    }
514
515    /**
516     * Pass the call on to the underlying handler
517     * @see XSLOutputAttributes#getEncoding()
518     */
519    public String getEncoding()
520    {
521        return m_handler.getEncoding();
522    }
523
524    /**
525     * Pass the call on to the underlying handler
526     * @see XSLOutputAttributes#getIndent()
527     */
528    public boolean getIndent()
529    {
530        return m_handler.getIndent();
531    }
532
533    /**
534     * Pass the call on to the underlying handler
535     * @see XSLOutputAttributes#getIndentAmount()
536     */
537    public int getIndentAmount()
538    {
539        return m_handler.getIndentAmount();
540    }
541
542    /**
543     * Pass the call on to the underlying handler
544     * @see XSLOutputAttributes#getMediaType()
545     */
546    public String getMediaType()
547    {
548        return m_handler.getMediaType();
549    }
550
551    /**
552     * Pass the call on to the underlying handler
553     * @see XSLOutputAttributes#getOmitXMLDeclaration()
554     */
555    public boolean getOmitXMLDeclaration()
556    {
557        return m_handler.getOmitXMLDeclaration();
558    }
559
560    /**
561     * Pass the call on to the underlying handler
562     * @see XSLOutputAttributes#getStandalone()
563     */
564    public String getStandalone()
565    {
566        return m_handler.getStandalone();
567    }
568
569    /**
570     * Pass the call on to the underlying handler
571     * @see XSLOutputAttributes#getVersion()
572     */
573    public String getVersion() {
574        return m_handler.getVersion();
575    }
576
577    /**
578     * @see XSLOutputAttributes#setDoctype(String, String)
579     */
580    public void setDoctype(String system, String pub) {
581        m_handler.setDoctypePublic(pub);
582        m_handler.setDoctypeSystem(system);
583    }
584
585    /**
586     * Set the doctype in the underlying XML handler. Remember that this method
587     * was called, just in case we need to transfer this doctype to an HTML handler
588     * @param doctype the public doctype to set
589     * @see XSLOutputAttributes#setDoctypePublic(String)
590     */
591    public void setDoctypePublic(String doctype) {
592        m_handler.setDoctypePublic(doctype);
593    }
594
595    /**
596     * Set the doctype in the underlying XML handler. Remember that this method
597     * was called, just in case we need to transfer this doctype to an HTML handler
598     * @param doctype the system doctype to set
599     * @see XSLOutputAttributes#setDoctypeSystem(String)
600     */
601    public void setDoctypeSystem(String doctype) {
602        m_handler.setDoctypeSystem(doctype);
603    }
604
605    /**
606     * Pass the call on to the underlying handler
607     * @see XSLOutputAttributes#setEncoding(String)
608     */
609    public void setEncoding(String encoding) {
610        m_handler.setEncoding(encoding);
611    }
612
613    /**
614     * Pass the call on to the underlying handler
615     * @see XSLOutputAttributes#setIndent(boolean)
616     */
617    public void setIndent(boolean indent) {
618        m_handler.setIndent(indent);
619    }
620
621    /**
622     * Pass the call on to the underlying handler
623     */
624    public void setIndentAmount(int value) {
625        m_handler.setIndentAmount(value);
626    }
627
628    /**
629     * @see XSLOutputAttributes#setMediaType(String)
630     */
631    public void setMediaType(String mediaType) {
632        m_handler.setMediaType(mediaType);
633    }
634
635    /**
636     * Pass the call on to the underlying handler
637     * @see XSLOutputAttributes#setOmitXMLDeclaration(boolean)
638     */
639    public void setOmitXMLDeclaration(boolean b) {
640        m_handler.setOmitXMLDeclaration(b);
641    }
642
643    /**
644     * Pass the call on to the underlying handler
645     * @see XSLOutputAttributes#setStandalone(String)
646     */
647    public void setStandalone(String standalone) {
648        m_handler.setStandalone(standalone);
649    }
650
651    /**
652     * Pass the call on to the underlying handler
653     * @see org.xml.sax.ext.DeclHandler#attributeDecl(String, String, String, String, String)
654     */
655    public void attributeDecl(String arg0, String arg1, String arg2,
656                              String arg3, String arg4) throws SAXException {
657        m_handler.attributeDecl(arg0, arg1, arg2, arg3, arg4);
658    }
659
660    /**
661     * Pass the call on to the underlying handler
662     * @see org.xml.sax.ext.DeclHandler#elementDecl(String, String)
663     */
664    public void elementDecl(String arg0, String arg1) throws SAXException
665    {
666        if (m_firstTagNotEmitted) {
667            emitFirstTag();
668        }
669        m_handler.elementDecl(arg0, arg1);
670    }
671
672    /**
673     * Pass the call on to the underlying handler
674     * @see org.xml.sax.ext.DeclHandler#externalEntityDecl(String, String, String)
675     */
676    public void externalEntityDecl(
677        String name,
678        String publicId,
679        String systemId)
680        throws SAXException
681    {
682        if (m_firstTagNotEmitted) {
683            flush();
684        }
685        m_handler.externalEntityDecl(name, publicId, systemId);
686    }
687
688    /**
689     * Pass the call on to the underlying handler
690     * @see org.xml.sax.ext.DeclHandler#internalEntityDecl(String, String)
691     */
692    public void internalEntityDecl(String arg0, String arg1)
693        throws SAXException
694    {
695        if (m_firstTagNotEmitted) {
696            flush();
697        }
698        m_handler.internalEntityDecl(arg0, arg1);
699    }
700
701    /**
702     * Pass the call on to the underlying handler
703     * @see org.xml.sax.ContentHandler#characters(char[], int, int)
704     */
705    public void characters(char[] characters, int offset, int length)
706        throws SAXException
707    {
708        if (m_firstTagNotEmitted) {
709            flush();
710        }
711        m_handler.characters(characters, offset, length);
712    }
713
714    /**
715     * Pass the call on to the underlying handler
716     * @see org.xml.sax.ContentHandler#endDocument()
717     */
718    public void endDocument() throws SAXException {
719        if (m_firstTagNotEmitted) {
720            flush();
721        }
722        m_handler.endDocument();
723    }
724
725    /**
726     * Pass the call on to the underlying handler
727     * @see org.xml.sax.ContentHandler#endElement(String, String, String)
728     */
729    public void endElement(String namespaceURI, String localName, String qName)
730        throws SAXException
731    {
732        if (m_firstTagNotEmitted) {
733            flush();
734            if (namespaceURI == null && m_firstElementURI != null)
735                namespaceURI = m_firstElementURI;
736
737            if (localName == null && m_firstElementLocalName != null)
738                localName = m_firstElementLocalName;
739        }
740        m_handler.endElement(namespaceURI, localName, qName);
741    }
742
743    /**
744     * Pass the call on to the underlying handler
745     * @see org.xml.sax.ContentHandler#endPrefixMapping(String)
746     */
747    public void endPrefixMapping(String prefix) throws SAXException {
748        m_handler.endPrefixMapping(prefix);
749    }
750
751    /**
752     * Pass the call on to the underlying handler
753     * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
754     */
755    public void ignorableWhitespace(char[] ch, int start, int length)
756        throws SAXException
757    {
758        if (m_firstTagNotEmitted)
759        {
760            flush();
761        }
762        m_handler.ignorableWhitespace(ch, start, length);
763    }
764
765    /**
766     * Pass the call on to the underlying handler
767     * @see org.xml.sax.ContentHandler#processingInstruction(String, String)
768     */
769    public void processingInstruction(String target, String data)
770        throws SAXException
771    {
772          if (m_firstTagNotEmitted)
773        {
774            flush();
775        }
776
777        m_handler.processingInstruction(target, data);
778    }
779
780    /**
781     * Pass the call on to the underlying handler
782     * @see org.xml.sax.ContentHandler#setDocumentLocator(Locator)
783     */
784    public void setDocumentLocator(Locator locator)
785    {
786        super.setDocumentLocator(locator);
787        m_handler.setDocumentLocator(locator);
788    }
789
790    /**
791     * Pass the call on to the underlying handler
792     * @see org.xml.sax.ContentHandler#skippedEntity(String)
793     */
794    public void skippedEntity(String name) throws SAXException
795    {
796        m_handler.skippedEntity(name);
797    }
798
799
800
801    /**
802     * Pass the call on to the underlying handler
803     * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int)
804     */
805    public void comment(char[] ch, int start, int length) throws SAXException
806    {
807        if (m_firstTagNotEmitted)
808        {
809            flush();
810        }
811
812        m_handler.comment(ch, start, length);
813    }
814
815    /**
816     * Pass the call on to the underlying handler
817     * @see org.xml.sax.ext.LexicalHandler#endCDATA()
818     */
819    public void endCDATA() throws SAXException
820    {
821
822        m_handler.endCDATA();
823    }
824
825    /**
826     * Pass the call on to the underlying handler
827     * @see org.xml.sax.ext.LexicalHandler#endDTD()
828     */
829    public void endDTD() throws SAXException
830    {
831
832        m_handler.endDTD();
833    }
834
835    /**
836     * Pass the call on to the underlying handler
837     * @see org.xml.sax.ext.LexicalHandler#endEntity(String)
838     */
839    public void endEntity(String name) throws SAXException
840    {
841        if (m_firstTagNotEmitted)
842        {
843            emitFirstTag();
844        }
845        m_handler.endEntity(name);
846    }
847
848    /**
849     * Pass the call on to the underlying handler
850     * @see org.xml.sax.ext.LexicalHandler#startCDATA()
851     */
852    public void startCDATA() throws SAXException
853    {
854        m_handler.startCDATA();
855    }
856
857    /**
858     * Pass the call on to the underlying handler
859     * @see org.xml.sax.ext.LexicalHandler#startDTD(String, String, String)
860     */
861    public void startDTD(String name, String publicId, String systemId)
862        throws SAXException
863    {
864        m_handler.startDTD(name, publicId, systemId);
865    }
866
867    /**
868     * Pass the call on to the underlying handler
869     * @see org.xml.sax.ext.LexicalHandler#startEntity(String)
870     */
871    public void startEntity(String name) throws SAXException
872    {
873        m_handler.startEntity(name);
874    }
875
876    /**
877     * Initialize the wrapped output stream (XML or HTML).
878     * If the stream handler should be HTML, then replace the XML handler with
879     * an HTML handler. After than send the starting method calls that were cached
880     * to the wrapped handler.
881     *
882     */
883    private void initStreamOutput() throws SAXException
884    {
885
886        // Try to rule out if this is an not to be an HTML document based on prefix
887        boolean firstElementIsHTML = isFirstElemHTML();
888
889        if (firstElementIsHTML)
890        {
891            // create an HTML output handler, and initialize it
892
893            // keep a reference to the old handler, ... it will soon be gone
894            SerializationHandler oldHandler = m_handler;
895
896            /* We have to make sure we get an output properties with the proper
897             * defaults for the HTML method.  The easiest way to do this is to
898             * have the OutputProperties class do it.
899             */
900
901            Properties htmlProperties =
902                OutputPropertiesFactory.getDefaultMethodProperties(Method.HTML);
903            Serializer serializer =
904                SerializerFactory.getSerializer(htmlProperties);
905
906            // The factory should be returning a ToStream
907            // Don't know what to do if it doesn't
908            // i.e. the user has over-ridden the content-handler property
909            // for html
910            m_handler = (SerializationHandler) serializer;
911            //m_handler = new ToHTMLStream();
912
913            Writer writer = oldHandler.getWriter();
914
915            if (null != writer)
916                m_handler.setWriter(writer);
917            else
918            {
919                OutputStream os = oldHandler.getOutputStream();
920
921                if (null != os)
922                    m_handler.setOutputStream(os);
923            }
924
925            // need to copy things from the old handler to the new one here
926
927            //            if (_setVersion_called)
928            //            {
929            m_handler.setVersion(oldHandler.getVersion());
930            //            }
931            //            if (_setDoctypeSystem_called)
932            //            {
933            m_handler.setDoctypeSystem(oldHandler.getDoctypeSystem());
934            //            }
935            //            if (_setDoctypePublic_called)
936            //            {
937            m_handler.setDoctypePublic(oldHandler.getDoctypePublic());
938            //            }
939            //            if (_setMediaType_called)
940            //            {
941            m_handler.setMediaType(oldHandler.getMediaType());
942            //            }
943
944            m_handler.setTransformer(oldHandler.getTransformer());
945        }
946
947        /* Now that we have a real wrapped handler (XML or HTML) lets
948         * pass any cached calls to it
949         */
950        // Call startDocument() if necessary
951        if (m_needToCallStartDocument)
952        {
953            m_handler.startDocument();
954            m_needToCallStartDocument = false;
955        }
956
957        // the wrapped handler is now fully initialized
958        m_wrapped_handler_not_initialized = false;
959    }
960
961    private void emitFirstTag() throws SAXException {
962        if (m_firstElementName != null) {
963            if (m_wrapped_handler_not_initialized) {
964                initStreamOutput();
965                m_wrapped_handler_not_initialized = false;
966            }
967            // Output first tag
968            m_handler.startElement(m_firstElementURI, null, m_firstElementName, m_attributes);
969            // don't need the collected attributes of the first element anymore.
970            m_attributes = null;
971
972            // Output namespaces of first tag
973            if (m_namespacePrefix != null) {
974                final int n = m_namespacePrefix.size();
975                for (int i = 0; i < n; i++) {
976                    final String prefix = m_namespacePrefix.get(i);
977                    final String uri = m_namespaceURI.get(i);
978                    m_handler.startPrefixMapping(prefix, uri, false);
979                }
980                m_namespacePrefix = null;
981                m_namespaceURI = null;
982            }
983            m_firstTagNotEmitted = false;
984        }
985    }
986
987    /**
988     * Utility function for calls to local-name().
989     *
990     * Don't want to override static function on SerializerBase
991     * So added Unknown suffix to method name.
992     */
993    private String getLocalNameUnknown(String value) {
994        int idx = value.lastIndexOf(':');
995        if (idx >= 0)
996            value = value.substring(idx + 1);
997        idx = value.lastIndexOf('@');
998        if (idx >= 0)
999            value = value.substring(idx + 1);
1000        return (value);
1001    }
1002
1003    /**
1004     * Utility function to return prefix
1005     *
1006     * Don't want to override static function on SerializerBase
1007     * So added Unknown suffix to method name.
1008     */
1009    private String getPrefixPartUnknown(String qname) {
1010        final int index = qname.indexOf(':');
1011        return (index > 0) ? qname.substring(0, index) : EMPTYSTRING;
1012    }
1013
1014    /**
1015     * Determine if the firts element in the document is <html> or <HTML>
1016     * This uses the cached first element name, first element prefix and the
1017     * cached namespaces from previous method calls
1018     *
1019     * @return true if the first element is an opening <html> tag
1020     */
1021    private boolean isFirstElemHTML() {
1022        boolean isHTML;
1023
1024        // is the first tag html, not considering the prefix ?
1025        isHTML =
1026            getLocalNameUnknown(m_firstElementName).equalsIgnoreCase("html");
1027
1028        // Try to rule out if this is not to be an HTML document based on URI
1029        if (isHTML &&
1030            m_firstElementURI != null &&
1031            !EMPTYSTRING.equals(m_firstElementURI))
1032        {
1033            // the <html> element has a non-trivial namespace
1034            isHTML = false;
1035        }
1036        // Try to rule out if this is an not to be an HTML document based on prefix
1037        if (isHTML && m_namespacePrefix != null) {
1038            /* the first element has a name of "html", but lets check the prefix.
1039             * If the prefix points to a namespace with a URL that is not ""
1040             * then the doecument doesn't start with an <html> tag, and isn't html
1041             */
1042            final int max = m_namespacePrefix.size();
1043            for (int i = 0; i < max; i++) {
1044                final String prefix = m_namespacePrefix.get(i);
1045                final String uri = m_namespaceURI.get(i);
1046
1047                if (m_firstElementPrefix != null &&
1048                    m_firstElementPrefix.equals(prefix) &&
1049                    !EMPTYSTRING.equals(uri))
1050                {
1051                    // The first element has a prefix, so it can't be <html>
1052                    isHTML = false;
1053                    break;
1054                }
1055            }
1056
1057        }
1058        return isHTML;
1059    }
1060
1061    /**
1062     * @see Serializer#asDOMSerializer()
1063     */
1064    public DOMSerializer asDOMSerializer() throws IOException {
1065        return m_handler.asDOMSerializer();
1066    }
1067
1068    /**
1069     * @param URI_and_localNames Vector a list of pairs of URI/localName
1070     * specified in the cdata-section-elements attribute.
1071     * @see SerializationHandler#setCdataSectionElements(java.util.Vector)
1072     */
1073    public void setCdataSectionElements(ArrayList<String> URI_and_localNames) {
1074        m_handler.setCdataSectionElements(URI_and_localNames);
1075    }
1076
1077    /**
1078     * @see ExtendedContentHandler#addAttributes(org.xml.sax.Attributes)
1079     */
1080    public void addAttributes(Attributes atts) throws SAXException {
1081        m_handler.addAttributes(atts);
1082    }
1083
1084    /**
1085     * Get the current namespace mappings.
1086     * Simply returns the mappings of the wrapped handler.
1087     * @see ExtendedContentHandler#getNamespaceMappings()
1088     */
1089    public NamespaceMappings getNamespaceMappings() {
1090        NamespaceMappings mappings = null;
1091        if (m_handler != null) {
1092            mappings = m_handler.getNamespaceMappings();
1093        }
1094        return mappings;
1095    }
1096
1097    /**
1098     * @see SerializationHandler#flushPending()
1099     */
1100    public void flushPending() throws SAXException {
1101        flush();
1102        m_handler.flushPending();
1103    }
1104
1105    private void flush() {
1106        try {
1107            if (m_firstTagNotEmitted) {
1108                emitFirstTag();
1109            }
1110            if (m_needToCallStartDocument) {
1111                m_handler.startDocument();
1112                m_needToCallStartDocument = false;
1113            }
1114        } catch(SAXException e) {
1115            throw new RuntimeException(e.toString());
1116        }
1117    }
1118
1119    /**
1120     * @see ExtendedContentHandler#getPrefix
1121     */
1122    public String getPrefix(String namespaceURI) {
1123        return m_handler.getPrefix(namespaceURI);
1124    }
1125
1126    /**
1127     * @see ExtendedContentHandler#entityReference(java.lang.String)
1128     */
1129    public void entityReference(String entityName) throws SAXException {
1130        m_handler.entityReference(entityName);
1131    }
1132
1133    /**
1134     * @see ExtendedContentHandler#getNamespaceURI(java.lang.String, boolean)
1135     */
1136    public String getNamespaceURI(String qname, boolean isElement) {
1137        return m_handler.getNamespaceURI(qname, isElement);
1138    }
1139
1140    public String getNamespaceURIFromPrefix(String prefix) {
1141        return m_handler.getNamespaceURIFromPrefix(prefix);
1142    }
1143
1144    public void setTransformer(Transformer t) {
1145        m_handler.setTransformer(t);
1146        if ((t instanceof SerializerTrace) &&
1147            (((SerializerTrace) t).hasTraceListeners()))
1148        {
1149            m_tracer = (SerializerTrace) t;
1150        } else {
1151            m_tracer = null;
1152        }
1153    }
1154
1155    public Transformer getTransformer() {
1156        return m_handler.getTransformer();
1157    }
1158
1159    /**
1160     * @see SerializationHandler#setContentHandler(org.xml.sax.ContentHandler)
1161     */
1162    public void setContentHandler(ContentHandler ch) {
1163        m_handler.setContentHandler(ch);
1164    }
1165
1166    /**
1167     * This method is used to set the source locator, which might be used to
1168     * generated an error message.
1169     * @param locator the source locator
1170     *
1171     * @see ExtendedContentHandler#setSourceLocator(javax.xml.transform.SourceLocator)
1172     */
1173    public void setSourceLocator(SourceLocator locator) {
1174        m_handler.setSourceLocator(locator);
1175    }
1176
1177    protected void firePseudoElement(String elementName) {
1178        if (m_tracer != null) {
1179            StringBuffer sb = new StringBuffer();
1180
1181            sb.append('<');
1182            sb.append(elementName);
1183
1184            // convert the StringBuffer to a char array and
1185            // emit the trace event that these characters "might"
1186            // be written
1187            char ch[] = sb.toString().toCharArray();
1188            m_tracer.fireGenerateEvent(
1189                SerializerTrace.EVENTTYPE_OUTPUT_PSEUDO_CHARACTERS,
1190                ch,
1191                0,
1192                ch.length);
1193        }
1194    }
1195
1196    /**
1197     * @see org.apache.xml.serializer.Serializer#asDOM3Serializer()
1198     */
1199    public Object asDOM3Serializer() throws IOException
1200    {
1201        return m_handler.asDOM3Serializer();
1202    }
1203}
1204