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