1 /*
2  * Copyright (c) 2006, 2021, 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 com.sun.org.apache.xml.internal.serializer.dom3.DOMConstants;
24 import com.sun.org.apache.xml.internal.serializer.utils.MsgKey;
25 import com.sun.org.apache.xml.internal.serializer.utils.Utils;
26 import com.sun.org.apache.xml.internal.serializer.utils.WrappedRuntimeException;
27 import java.io.IOException;
28 import java.io.OutputStream;
29 import java.io.OutputStreamWriter;
30 import java.io.UnsupportedEncodingException;
31 import java.io.Writer;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.EmptyStackException;
35 import java.util.Enumeration;
36 import java.util.Iterator;
37 import java.util.List;
38 import java.util.Properties;
39 import java.util.Set;
40 import java.util.StringTokenizer;
41 import javax.xml.transform.ErrorListener;
42 import javax.xml.transform.OutputKeys;
43 import javax.xml.transform.Transformer;
44 import javax.xml.transform.TransformerException;
45 import jdk.xml.internal.JdkXmlUtils;
46 import org.w3c.dom.Node;
47 import org.xml.sax.Attributes;
48 import org.xml.sax.ContentHandler;
49 import org.xml.sax.SAXException;
50 
51 /**
52  * This abstract class is a base class for other stream
53  * serializers (xml, html, text ...) that write output to a stream.
54  *
55  * @xsl.usage internal
56  * @LastModified: Apr 2021
57  */
58 abstract public class ToStream extends SerializerBase {
59 
60     private static final String COMMENT_BEGIN = "<!--";
61     private static final String COMMENT_END = "-->";
62 
63     /** Stack to keep track of disabling output escaping. */
64     protected BoolStack m_disableOutputEscapingStates = new BoolStack();
65 
66     /**
67      * The encoding information associated with this serializer.
68      * Although initially there is no encoding,
69      * there is a dummy EncodingInfo object that will say
70      * that every character is in the encoding. This is useful
71      * for a serializer that is in temporary output state and has
72      * no associated encoding. A serializer in final output state
73      * will have an encoding, and will worry about whether
74      * single chars or surrogate pairs of high/low chars form
75      * characters in the output encoding.
76      */
77     EncodingInfo m_encodingInfo = new EncodingInfo(null,null);
78 
79     /**
80      * Method reference to the sun.io.CharToByteConverter#canConvert method
81      * for this encoding.  Invalid if m_charToByteConverter is null.
82      */
83     java.lang.reflect.Method m_canConvertMeth;
84 
85     /**
86      * Boolean that tells if we already tried to get the converter.
87      */
88     boolean m_triedToGetConverter = false;
89 
90     /**
91      * Opaque reference to the sun.io.CharToByteConverter for this
92      * encoding.
93      */
94     Object m_charToByteConverter = null;
95 
96     /**
97      * Used to buffer the text nodes and the entity reference nodes if
98      * indentation is on.
99      */
100     protected CharacterBuffer m_charactersBuffer = new CharacterBuffer();
101 
102     /**
103      * Used to decide if a text node is pretty-printed with indentation.
104      * If m_childNodeNum > 1, the text node will be indented.
105      *
106      */
107     protected List<Integer> m_childNodeNumStack = new ArrayList<>();
108 
109     protected int m_childNodeNum = 0;
110 
111     /**
112      * Used to handle xml:space attribute
113      *
114      */
115     protected BoolStack m_preserveSpaces = new BoolStack();
116 
117     protected boolean m_ispreserveSpace = false;
118 
119 
120     /**
121      * State flag that tells if the previous node processed
122      * was text, so we can tell if we should preserve whitespace.
123      *
124      * Used in endDocument() and shouldIndent() but
125      * only if m_doIndent is true.
126      * If m_doIndent is false this flag has no impact.
127      */
128     protected boolean m_isprevtext = false;
129 
130     /**
131      * The maximum character size before we have to resort
132      * to escaping.
133      */
134     protected int m_maxCharacter = Encodings.getLastPrintable();
135 
136     /**
137      * The system line separator for writing out line breaks.
138      * The default value is from the system property,
139      * but this value can be set through the xsl:output
140      * extension attribute xalan:line-separator.
141      */
142     protected char[] m_lineSep = System.lineSeparator().toCharArray();
143 
144     /**
145      * True if the the system line separator is to be used.
146      */
147     protected boolean m_lineSepUse = true;
148 
149     /**
150      * The length of the line seperator, since the write is done
151      * one character at a time.
152      */
153     protected int m_lineSepLen = m_lineSep.length;
154 
155     /**
156      * Map that tells which characters should have special treatment, and it
157      *  provides character to entity name lookup.
158      */
159     protected CharInfo m_charInfo;
160 
161     /** True if we control the buffer, and we should flush the output on endDocument. */
162     boolean m_shouldFlush = true;
163 
164     /**
165      * Add space before '/>' for XHTML.
166      */
167     protected boolean m_spaceBeforeClose = false;
168 
169     /**
170      * Flag to signal that a newline should be added.
171      *
172      * Used only in indent() which is called only if m_doIndent is true.
173      * If m_doIndent is false this flag has no impact.
174      */
175     boolean m_startNewLine;
176 
177     /**
178      * Tells if we're in an internal document type subset.
179      */
180     protected boolean m_inDoctype = false;
181 
182     /**
183      * Flag to quickly tell if the encoding is UTF8.
184      */
185     boolean m_isUTF8 = false;
186 
187     /**
188      * remembers if we are in between the startCDATA() and endCDATA() callbacks
189      */
190     protected boolean m_cdataStartCalled = false;
191 
192     /**
193      * If this flag is true DTD entity references are not left as-is,
194      * which is exiting older behavior.
195      */
196     private boolean m_expandDTDEntities = true;
197 
198     private char m_highSurrogate = 0;
199 
200     /**
201      * Default constructor
202      */
ToStream()203     public ToStream() { }
204 
205     /**
206      * This helper method to writes out "]]>" when closing a CDATA section.
207      *
208      * @throws org.xml.sax.SAXException
209      */
closeCDATA()210     protected void closeCDATA() throws org.xml.sax.SAXException {
211         try {
212             m_writer.write(CDATA_DELIMITER_CLOSE);
213             // write out a CDATA section closing "]]>"
214             m_cdataTagOpen = false; // Remember that we have done so.
215         }
216         catch (IOException e) {
217             throw new SAXException(e);
218         }
219     }
220 
221     /**
222      * Serializes the DOM node. Throws an exception only if an I/O
223      * exception occured while serializing.
224      *
225      * @param node Node to serialize.
226      * @throws IOException An I/O exception occured while serializing
227      */
serialize(Node node)228     public void serialize(Node node) throws IOException {
229         try {
230             TreeWalker walker = new TreeWalker(this);
231             walker.traverse(node);
232         } catch (org.xml.sax.SAXException se) {
233             throw new WrappedRuntimeException(se);
234         }
235     }
236 
237     /**
238      * Return true if the character is the high member of a surrogate pair.
239      *
240      * NEEDSDOC @param c
241      *
242      * NEEDSDOC ($objectName$) @return
243      */
isUTF16Surrogate(char c)244     static final boolean isUTF16Surrogate(char c) {
245         return (c & 0xFC00) == 0xD800;
246     }
247 
248     /**
249      * Taken from XSLTC
250      */
251     private boolean m_escaping = true;
252 
253     /**
254      * Flush the formatter's result stream.
255      *
256      * @throws org.xml.sax.SAXException
257      */
flushWriter()258     protected final void flushWriter() throws org.xml.sax.SAXException {
259         final Writer writer = m_writer;
260         if (null != writer) {
261             try {
262                 if (writer instanceof WriterToUTF8Buffered) {
263                     if (m_shouldFlush)
264                         ((WriterToUTF8Buffered)writer).flush();
265                     else
266                         ((WriterToUTF8Buffered)writer).flushBuffer();
267                 }
268                 if (writer instanceof WriterToASCI) {
269                     if (m_shouldFlush)
270                         writer.flush();
271                 } else {
272                     // Flush always.
273                     // Not a great thing if the writer was created
274                     // by this class, but don't have a choice.
275                     writer.flush();
276                 }
277             } catch (IOException ioe) {
278                 throw new org.xml.sax.SAXException(ioe);
279             }
280         }
281     }
282 
283     OutputStream m_outputStream;
284 
285     /**
286      * Get the output stream where the events will be serialized to.
287      *
288      * @return reference to the result stream, or null of only a writer was
289      * set.
290      */
getOutputStream()291     public OutputStream getOutputStream() {
292         return m_outputStream;
293     }
294 
295     // Implement DeclHandler
296 
297     /**
298      *   Report an element type declaration.
299      *
300      *   <p>The content model will consist of the string "EMPTY", the
301      *   string "ANY", or a parenthesised group, optionally followed
302      *   by an occurrence indicator.  The model will be normalized so
303      *   that all whitespace is removed,and will include the enclosing
304      *   parentheses.</p>
305      *
306      *   @param name The element type name.
307      *   @param model The content model as a normalized string.
308      *   @exception SAXException The application may raise an exception.
309      */
elementDecl(String name, String model)310     public void elementDecl(String name, String model) throws SAXException
311     {
312         // Do not inline external DTD
313         if (m_inExternalDTD)
314             return;
315         try {
316             final Writer writer = m_writer;
317             DTDprolog();
318 
319             writer.write("<!ELEMENT ");
320             writer.write(name);
321             writer.write(' ');
322             writer.write(model);
323             writer.write('>');
324             writer.write(m_lineSep, 0, m_lineSepLen);
325         }
326         catch (IOException e)
327         {
328             throw new SAXException(e);
329         }
330 
331     }
332 
333     /**
334      * Report an internal entity declaration.
335      *
336      * <p>Only the effective (first) declaration for each entity
337      * will be reported.</p>
338      *
339      * @param name The name of the entity.  If it is a parameter
340      *        entity, the name will begin with '%'.
341      * @param value The replacement text of the entity.
342      * @exception SAXException The application may raise an exception.
343      * @see #externalEntityDecl
344      * @see org.xml.sax.DTDHandler#unparsedEntityDecl
345      */
internalEntityDecl(String name, String value)346     public void internalEntityDecl(String name, String value)
347         throws SAXException
348     {
349         // Do not inline external DTD
350         if (m_inExternalDTD)
351             return;
352         try {
353             DTDprolog();
354             outputEntityDecl(name, value);
355         } catch (IOException e) {
356             throw new SAXException(e);
357         }
358 
359     }
360 
361     /**
362      * Output the doc type declaration.
363      *
364      * @param name non-null reference to document type name.
365      * NEEDSDOC @param value
366      *
367      * @throws org.xml.sax.SAXException
368      */
outputEntityDecl(String name, String value)369     void outputEntityDecl(String name, String value) throws IOException
370     {
371         final Writer writer = m_writer;
372         writer.write("<!ENTITY ");
373         writer.write(name);
374         writer.write(" \"");
375         writer.write(value);
376         writer.write("\">");
377         writer.write(m_lineSep, 0, m_lineSepLen);
378     }
379 
380     /**
381      * Output a system-dependent line break.
382      *
383      * @throws org.xml.sax.SAXException
384      */
outputLineSep()385     protected final void outputLineSep() throws IOException {
386         m_writer.write(m_lineSep, 0, m_lineSepLen);
387     }
388 
setProp(String name, String val, boolean defaultVal)389     void setProp(String name, String val, boolean defaultVal) {
390         if (val != null) {
391 
392             char first = getFirstCharLocName(name);
393             switch (first) {
394             case 'c':
395                 if (OutputKeys.CDATA_SECTION_ELEMENTS.equals(name)) {
396                     addCdataSectionElements(val); // val is cdataSectionNames
397                 }
398                 break;
399             case 'd':
400                 if (OutputKeys.DOCTYPE_SYSTEM.equals(name)) {
401                     this.m_doctypeSystem = val;
402                 } else if (OutputKeys.DOCTYPE_PUBLIC.equals(name)) {
403                     this.m_doctypePublic = val;
404                     if (val.startsWith("-//W3C//DTD XHTML"))
405                         m_spaceBeforeClose = true;
406                 }
407                 break;
408             case 'e':
409                 String newEncoding = val;
410                 if (OutputKeys.ENCODING.equals(name)) {
411                     String possible_encoding = Encodings.getMimeEncoding(val);
412                     if (possible_encoding != null) {
413                         // if the encoding is being set, try to get the
414                         // preferred
415                         // mime-name and set it too.
416                         super.setProp("mime-name", possible_encoding,
417                                 defaultVal);
418                     }
419                     final String oldExplicitEncoding = getOutputPropertyNonDefault(OutputKeys.ENCODING);
420                     final String oldDefaultEncoding  = getOutputPropertyDefault(OutputKeys.ENCODING);
421                     if ( (defaultVal && ( oldDefaultEncoding == null || !oldDefaultEncoding.equalsIgnoreCase(newEncoding)))
422                             || ( !defaultVal && (oldExplicitEncoding == null || !oldExplicitEncoding.equalsIgnoreCase(newEncoding) ))) {
423                        // We are trying to change the default or the non-default setting of the encoding to a different value
424                        // from what it was
425 
426                        EncodingInfo encodingInfo = Encodings.getEncodingInfo(newEncoding);
427                        if (newEncoding != null && encodingInfo.name == null) {
428                         // We tried to get an EncodingInfo for Object for the given
429                         // encoding, but it came back with an internall null name
430                         // so the encoding is not supported by the JDK, issue a message.
431                         final String msg = Utils.messages.createMessage(
432                                 MsgKey.ER_ENCODING_NOT_SUPPORTED,new Object[]{ newEncoding });
433 
434                         final String msg2 =
435                             "Warning: encoding \"" + newEncoding + "\" not supported, using "
436                                    + Encodings.DEFAULT_MIME_ENCODING;
437                         try {
438                                 // Prepare to issue the warning message
439                                 final Transformer tran = super.getTransformer();
440                                 if (tran != null) {
441                                     final ErrorListener errHandler = tran
442                                             .getErrorListener();
443                                     // Issue the warning message
444                                     if (null != errHandler
445                                             && m_sourceLocator != null) {
446                                         errHandler
447                                                 .warning(new TransformerException(
448                                                         msg, m_sourceLocator));
449                                         errHandler
450                                                 .warning(new TransformerException(
451                                                         msg2, m_sourceLocator));
452                                     } else {
453                                         System.out.println(msg);
454                                         System.out.println(msg2);
455                                     }
456                                 } else {
457                                     System.out.println(msg);
458                                     System.out.println(msg2);
459                                 }
460                             } catch (Exception e) {
461                             }
462 
463                             // We said we are using UTF-8, so use it
464                             newEncoding = Encodings.DEFAULT_MIME_ENCODING;
465                             val = Encodings.DEFAULT_MIME_ENCODING; // to store the modified value into the properties a little later
466                             encodingInfo = Encodings.getEncodingInfo(newEncoding);
467                         }
468                        // The encoding was good, or was forced to UTF-8 above
469 
470 
471                        // If there is already a non-default set encoding and we
472                        // are trying to set the default encoding, skip the this block
473                        // as the non-default value is already the one to use.
474                        if (defaultVal == false || oldExplicitEncoding == null) {
475                            m_encodingInfo = encodingInfo;
476                            if (newEncoding != null)
477                                m_isUTF8 = newEncoding.equals(Encodings.DEFAULT_MIME_ENCODING);
478 
479                            // if there was a previously set OutputStream
480                            OutputStream os = getOutputStream();
481                            if (os != null) {
482                                Writer w = getWriter();
483 
484                                // If the writer was previously set, but
485                                // set by the user, or if the new encoding is the same
486                                // as the old encoding, skip this block
487                                String oldEncoding = getOutputProperty(OutputKeys.ENCODING);
488                                if ((w == null || !m_writer_set_by_user)
489                                        && !newEncoding.equalsIgnoreCase(oldEncoding)) {
490                                    // Make the change of encoding in our internal
491                                    // table, then call setOutputStreamInternal
492                                    // which will stomp on the old Writer (if any)
493                                    // with a new Writer with the new encoding.
494                                    super.setProp(name, val, defaultVal);
495                                    setOutputStreamInternal(os,false);
496                                }
497                            }
498                        }
499                     }
500                 }
501                 break;
502             case 'i':
503                 if (OutputPropertiesFactory.S_KEY_INDENT_AMOUNT.equals(name)) {
504                     setIndentAmount(Integer.parseInt(val));
505                 } else if (OutputKeys.INDENT.equals(name)) {
506                     m_doIndent = val.endsWith("yes");
507                 } else if ((DOMConstants.S_JDK_PROPERTIES_NS + DOMConstants.S_IS_STANDALONE)
508                         .equals(name)) {
509                     m_isStandalone = val.endsWith("yes");
510                 }
511 
512                 break;
513             case 'l':
514                 if (OutputPropertiesFactory.S_KEY_LINE_SEPARATOR.equals(name)) {
515                     m_lineSep = val.toCharArray();
516                     m_lineSepLen = m_lineSep.length;
517                 }
518 
519                 break;
520             case 'm':
521                 if (OutputKeys.MEDIA_TYPE.equals(name)) {
522                     m_mediatype = val;
523                 }
524                 break;
525             case 'o':
526                 if (OutputKeys.OMIT_XML_DECLARATION.equals(name)) {
527                     boolean b = val.endsWith("yes") ? true : false;
528                     this.m_shouldNotWriteXMLHeader = b;
529                 }
530                 break;
531             case 's':
532                 // if standalone was explicitly specified
533                 if (OutputKeys.STANDALONE.equals(name)) {
534                     if (defaultVal) {
535                         setStandaloneInternal(val);
536                     } else {
537                         m_standaloneWasSpecified = true;
538                         setStandaloneInternal(val);
539                     }
540                 }
541 
542                 break;
543             case 'v':
544                 if (OutputKeys.VERSION.equals(name)) {
545                     m_version = val;
546                 }
547                 break;
548             default:
549                 break;
550 
551             }
552             super.setProp(name, val, defaultVal);
553         }
554     }
555 
556     /**
557      * Specifies an output format for this serializer. It the
558      * serializer has already been associated with an output format,
559      * it will switch to the new format. This method should not be
560      * called while the serializer is in the process of serializing
561      * a document.
562      *
563      * @param format The output format to use
564      */
setOutputFormat(Properties format)565     public void setOutputFormat(Properties format) {
566         boolean shouldFlush = m_shouldFlush;
567 
568         if (format != null) {
569             // Set the default values first,
570             // and the non-default values after that,
571             // just in case there is some unexpected
572             // residual values left over from over-ridden default values
573             Enumeration<?> propNames;
574             propNames = format.propertyNames();
575             while (propNames.hasMoreElements()) {
576                 String key = (String) propNames.nextElement();
577                 // Get the value, possibly a default value
578                 String value = format.getProperty(key);
579                 // Get the non-default value (if any).
580                 String explicitValue = (String) format.get(key);
581                 if (explicitValue == null && value != null) {
582                     // This is a default value
583                     this.setOutputPropertyDefault(key,value);
584                 }
585                 if (explicitValue != null) {
586                     // This is an explicit non-default value
587                     this.setOutputProperty(key,explicitValue);
588                 }
589             }
590         }
591 
592         // Access this only from the Hashtable level... we don't want to
593         // get default properties.
594         String entitiesFileName =
595             (String) format.get(OutputPropertiesFactory.S_KEY_ENTITIES);
596 
597         if (null != entitiesFileName) {
598             String method = (String) format.get(OutputKeys.METHOD);
599             m_charInfo = CharInfo.getCharInfo(entitiesFileName, method);
600         }
601 
602         m_shouldFlush = shouldFlush;
603     }
604 
605     /**
606      * Returns the output format for this serializer.
607      *
608      * @return The output format in use
609      */
getOutputFormat()610     public Properties getOutputFormat() {
611         Properties def = new Properties();
612         {
613             Set<String> s = getOutputPropDefaultKeys();
614             for (String key : s) {
615                 String val = getOutputPropertyDefault(key);
616                 def.put(key, val);
617             }
618         }
619 
620         Properties props = new Properties(def);
621         {
622             Set<String> s = getOutputPropKeys();
623             for (String key : s) {
624                 String val = getOutputPropertyNonDefault(key);
625                 if (val != null)
626                     props.put(key, val);
627             }
628         }
629         return props;
630     }
631 
632     /**
633      * Specifies a writer to which the document should be serialized.
634      * This method should not be called while the serializer is in
635      * the process of serializing a document.
636      *
637      * @param writer The output writer stream
638      */
setWriter(Writer writer)639     public void setWriter(Writer writer) {
640         setWriterInternal(writer, true);
641     }
642 
643     private boolean m_writer_set_by_user;
setWriterInternal(Writer writer, boolean setByUser)644     private void setWriterInternal(Writer writer, boolean setByUser) {
645         m_writer_set_by_user = setByUser;
646         m_writer = writer;
647         // if we are tracing events we need to trace what
648         // characters are written to the output writer.
649         if (m_tracer != null) {
650             boolean noTracerYet = true;
651             Writer w2 = m_writer;
652             while (w2 instanceof WriterChain) {
653                 if (w2 instanceof SerializerTraceWriter) {
654                     noTracerYet = false;
655                     break;
656                 }
657                 w2 = ((WriterChain)w2).getWriter();
658             }
659             if (noTracerYet)
660                 m_writer = new SerializerTraceWriter(m_writer, m_tracer);
661         }
662     }
663 
664     /**
665      * Set if the operating systems end-of-line line separator should
666      * be used when serializing.  If set false NL character
667      * (decimal 10) is left alone, otherwise the new-line will be replaced on
668      * output with the systems line separator. For example on UNIX this is
669      * NL, while on Windows it is two characters, CR NL, where CR is the
670      * carriage-return (decimal 13).
671      *
672      * @param use_sytem_line_break True if an input NL is replaced with the
673      * operating systems end-of-line separator.
674      * @return The previously set value of the serializer.
675      */
setLineSepUse(boolean use_sytem_line_break)676     public boolean setLineSepUse(boolean use_sytem_line_break) {
677         boolean oldValue = m_lineSepUse;
678         m_lineSepUse = use_sytem_line_break;
679         return oldValue;
680     }
681 
682     /**
683      * Specifies an output stream to which the document should be
684      * serialized. This method should not be called while the
685      * serializer is in the process of serializing a document.
686      * <p>
687      * The encoding specified in the output properties is used, or
688      * if no encoding was specified, the default for the selected
689      * output method.
690      *
691      * @param output The output stream
692      */
setOutputStream(OutputStream output)693     public void setOutputStream(OutputStream output) {
694         setOutputStreamInternal(output, true);
695     }
696 
setOutputStreamInternal(OutputStream output, boolean setByUser)697     private void setOutputStreamInternal(OutputStream output, boolean setByUser)
698     {
699         m_outputStream = output;
700         String encoding = getOutputProperty(OutputKeys.ENCODING);
701         if (Encodings.DEFAULT_MIME_ENCODING.equalsIgnoreCase(encoding))
702         {
703             // We wrap the OutputStream with a writer, but
704             // not one set by the user
705             try {
706                 setWriterInternal(new WriterToUTF8Buffered(output), false);
707             } catch (UnsupportedEncodingException e) {
708                 e.printStackTrace();
709             }
710         } else if (
711                 "WINDOWS-1250".equals(encoding)
712                 || "US-ASCII".equals(encoding)
713                 || "ASCII".equals(encoding))
714         {
715             setWriterInternal(new WriterToASCI(output), false);
716         } else if (encoding != null) {
717             Writer osw = null;
718                 try
719                 {
720                     osw = Encodings.getWriter(output, encoding);
721                 }
722                 catch (UnsupportedEncodingException uee)
723                 {
724                     osw = null;
725                 }
726 
727 
728             if (osw == null) {
729                 System.out.println(
730                     "Warning: encoding \""
731                         + encoding
732                         + "\" not supported"
733                         + ", using "
734                         + Encodings.DEFAULT_MIME_ENCODING);
735 
736                 encoding = Encodings.DEFAULT_MIME_ENCODING;
737                 setEncoding(encoding);
738                 try {
739                     osw = Encodings.getWriter(output, encoding);
740                 } catch (UnsupportedEncodingException e) {
741                     // We can't really get here, UTF-8 is always supported
742                     // This try-catch exists to make the compiler happy
743                     e.printStackTrace();
744                 }
745             }
746             setWriterInternal(osw,false);
747         }
748         else {
749             // don't have any encoding, but we have an OutputStream
750             Writer osw = new OutputStreamWriter(output);
751             setWriterInternal(osw,false);
752         }
753     }
754 
755 
756     /**
757      * @see SerializationHandler#setEscaping(boolean)
758      */
setEscaping(boolean escape)759     public boolean setEscaping(boolean escape)
760     {
761         final boolean temp = m_escaping;
762         m_escaping = escape;
763         return temp;
764 
765     }
766 
767 
768     /**
769      * Might print a newline character and the indentation amount
770      * of the given depth.
771      *
772      * @param depth the indentation depth (element nesting depth)
773      *
774      * @throws org.xml.sax.SAXException if an error occurs during writing.
775      */
indent(int depth)776     protected void indent(int depth) throws IOException
777     {
778 
779         if (m_startNewLine)
780             outputLineSep();
781         /*
782          * Default value is 4, so printSpace directly.
783          */
784         printSpace(depth * m_indentAmount);
785 
786     }
787 
788     /**
789      * Indent at the current element nesting depth.
790      * @throws IOException
791      */
indent()792     protected void indent() throws IOException
793     {
794         indent(m_elemContext.m_currentElemDepth);
795     }
796     /**
797      * Prints <var>n</var> spaces.
798      * @param n         Number of spaces to print.
799      *
800      * @throws org.xml.sax.SAXException if an error occurs when writing.
801      */
printSpace(int n)802     private void printSpace(int n) throws IOException
803     {
804         final Writer writer = m_writer;
805         for (int i = 0; i < n; i++)
806         {
807             writer.write(' ');
808         }
809 
810     }
811 
812     /**
813      * Report an attribute type declaration.
814      *
815      * <p>Only the effective (first) declaration for an attribute will
816      * be reported.  The type will be one of the strings "CDATA",
817      * "ID", "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY",
818      * "ENTITIES", or "NOTATION", or a parenthesized token group with
819      * the separator "|" and all whitespace removed.</p>
820      *
821      * @param eName The name of the associated element.
822      * @param aName The name of the attribute.
823      * @param type A string representing the attribute type.
824      * @param valueDefault A string representing the attribute default
825      *        ("#IMPLIED", "#REQUIRED", or "#FIXED") or null if
826      *        none of these applies.
827      * @param value A string representing the attribute's default value,
828      *        or null if there is none.
829      * @exception SAXException The application may raise an exception.
830      */
attributeDecl( String eName, String aName, String type, String valueDefault, String value)831     public void attributeDecl(
832         String eName,
833         String aName,
834         String type,
835         String valueDefault,
836         String value)
837         throws SAXException
838     {
839         // Do not inline external DTD
840         if (m_inExternalDTD)
841             return;
842         try
843         {
844             final Writer writer = m_writer;
845             DTDprolog();
846 
847             writer.write("<!ATTLIST ");
848             writer.write(eName);
849             writer.write(' ');
850 
851             writer.write(aName);
852             writer.write(' ');
853             writer.write(type);
854             if (valueDefault != null)
855             {
856                 writer.write(' ');
857                 writer.write(valueDefault);
858             }
859 
860             //writer.write(" ");
861             //writer.write(value);
862             writer.write('>');
863             writer.write(m_lineSep, 0, m_lineSepLen);
864         }
865         catch (IOException e)
866         {
867             throw new SAXException(e);
868         }
869     }
870 
871     /**
872      * Get the character stream where the events will be serialized to.
873      *
874      * @return Reference to the result Writer, or null.
875      */
getWriter()876     public Writer getWriter()
877     {
878         return m_writer;
879     }
880 
881     /**
882      * Report a parsed external entity declaration.
883      *
884      * <p>Only the effective (first) declaration for each entity
885      * will be reported.</p>
886      *
887      * @param name The name of the entity.  If it is a parameter
888      *        entity, the name will begin with '%'.
889      * @param publicId The declared public identifier of the entity, or
890      *        null if none was declared.
891      * @param systemId The declared system identifier of the entity.
892      * @exception SAXException The application may raise an exception.
893      * @see #internalEntityDecl
894      * @see org.xml.sax.DTDHandler#unparsedEntityDecl
895      */
externalEntityDecl( String name, String publicId, String systemId)896     public void externalEntityDecl(
897         String name,
898         String publicId,
899         String systemId)
900         throws SAXException
901     {
902         try {
903             DTDprolog();
904 
905             m_writer.write("<!ENTITY ");
906             m_writer.write(name);
907             if (publicId != null) {
908                 m_writer.write(" PUBLIC \"");
909                 m_writer.write(publicId);
910 
911             }
912             else {
913                 m_writer.write(" SYSTEM \"");
914                 m_writer.write(systemId);
915             }
916             m_writer.write("\" >");
917             m_writer.write(m_lineSep, 0, m_lineSepLen);
918         } catch (IOException e) {
919             // TODO Auto-generated catch block
920             e.printStackTrace();
921         }
922 
923     }
924 
925     /**
926      * Tell if this character can be written without escaping.
927      */
escapingNotNeeded(char ch)928     protected boolean escapingNotNeeded(char ch)
929     {
930         final boolean ret;
931         if (ch < 127)
932         {
933             // This is the old/fast code here, but is this
934             // correct for all encodings?
935             if (ch >= 0x20 || (0x0A == ch || 0x0D == ch || 0x09 == ch))
936                 ret= true;
937             else
938                 ret = false;
939         }
940         else {
941             ret = m_encodingInfo.isInEncoding(ch);
942         }
943         return ret;
944     }
945 
946     /**
947      * Once a surrogate has been detected, write out the pair of
948      * characters if it is in the encoding, or if there is no
949      * encoding, otherwise write out an entity reference
950      * of the value of the unicode code point of the character
951      * represented by the high/low surrogate pair.
952      * <p>
953      * An exception is thrown if there is no low surrogate in the pair,
954      * because the array ends unexpectely, or if the low char is there
955      * but its value is such that it is not a low surrogate.
956      *
957      * @param c the first (high) part of the surrogate, which
958      * must be confirmed before calling this method.
959      * @param ch Character array.
960      * @param i position Where the surrogate was detected.
961      * @param end The end index of the significant characters.
962      * @return the status of writing a surrogate pair.
963      *        -1 -- nothing is written
964      *         0 -- the pair is written as-is
965      *         code point -- the pair is written as an entity reference
966      *
967      * @throws IOException
968      * @throws org.xml.sax.SAXException if invalid UTF-16 surrogate detected.
969      */
writeUTF16Surrogate(char c, char ch[], int i, int end)970     protected int writeUTF16Surrogate(char c, char ch[], int i, int end)
971         throws IOException, SAXException
972     {
973         int status = -1;
974         if (i + 1 >= end)
975         {
976             m_highSurrogate = c;
977             return status;
978         }
979 
980         char high, low;
981         if (m_highSurrogate == 0) {
982             high = c;
983             low = ch[i+1];
984             status = 0;
985         } else {
986             high = m_highSurrogate;
987             low = c;
988             m_highSurrogate = 0;
989         }
990 
991         if (!Encodings.isLowUTF16Surrogate(low)) {
992             throwIOE(high, low);
993         }
994 
995         final Writer writer = m_writer;
996 
997         // If we make it to here we have a valid high, low surrogate pair
998         if (m_encodingInfo.isInEncoding(high,low)) {
999             // If the character formed by the surrogate pair
1000             // is in the encoding, so just write it out
1001             writer.write(new char[]{high, low}, 0, 2);
1002         }
1003         else {
1004             // Don't know what to do with this char, it is
1005             // not in the encoding and not a high char in
1006             // a surrogate pair, so write out as an entity ref
1007             final String encoding = getEncoding();
1008             if (encoding != null) {
1009                 status = writeCharRef(writer, high, low);
1010             } else {
1011                 /* The output encoding is not known,
1012                  * so just write it out as-is.
1013                  */
1014                 writer.write(new char[]{high, low}, 0, 2);
1015             }
1016         }
1017         // non-zero only if character reference was written out.
1018         return status;
1019     }
1020 
1021     /**
1022      * Handle one of the default entities, return false if it
1023      * is not a default entity.
1024      *
1025      * @param ch character to be escaped.
1026      * @param i index into character array.
1027      * @param chars non-null reference to character array.
1028      * @param len length of chars.
1029      * @param fromTextNode true if the characters being processed
1030      * are from a text node, false if they are from an attribute value
1031      * @param escLF true if the linefeed should be escaped.
1032      *
1033      * @return i+1 if the character was written, else i.
1034      *
1035      * @throws java.io.IOException
1036      */
accumDefaultEntity( Writer writer, char ch, int i, char[] chars, int len, boolean fromTextNode, boolean escLF)1037     protected int accumDefaultEntity(
1038         Writer writer,
1039         char ch,
1040         int i,
1041         char[] chars,
1042         int len,
1043         boolean fromTextNode,
1044         boolean escLF)
1045         throws IOException
1046     {
1047 
1048         if (!escLF && CharInfo.S_LINEFEED == ch)
1049         {
1050             writer.write(m_lineSep, 0, m_lineSepLen);
1051         }
1052         else
1053         {
1054             // if this is text node character and a special one of those,
1055             // or if this is a character from attribute value and a special one of those
1056             if ((fromTextNode && m_charInfo.isSpecialTextChar(ch)) || (!fromTextNode && m_charInfo.isSpecialAttrChar(ch)))
1057             {
1058                 String outputStringForChar = m_charInfo.getOutputStringForChar(ch);
1059 
1060                 if (null != outputStringForChar)
1061                 {
1062                     writer.write(outputStringForChar);
1063                 }
1064                 else
1065                     return i;
1066             }
1067             else
1068                 return i;
1069         }
1070 
1071         return i + 1;
1072 
1073     }
1074     /**
1075      * Normalize the characters, but don't escape.
1076      *
1077      * @param ch The characters from the XML document.
1078      * @param start The start position in the array.
1079      * @param length The number of characters to read from the array.
1080      * @param isCData true if a CDATA block should be built around the characters.
1081      * @param useSystemLineSeparator true if the operating systems
1082      * end-of-line separator should be output rather than a new-line character.
1083      *
1084      * @throws IOException
1085      * @throws org.xml.sax.SAXException
1086      */
writeNormalizedChars( char ch[], int start, int length, boolean isCData, boolean useSystemLineSeparator)1087     void writeNormalizedChars(
1088         char ch[],
1089         int start,
1090         int length,
1091         boolean isCData,
1092         boolean useSystemLineSeparator)
1093         throws IOException, org.xml.sax.SAXException
1094     {
1095         final Writer writer = m_writer;
1096         int end = start + length;
1097 
1098         for (int i = start; i < end; i++)
1099         {
1100             char c = ch[i];
1101 
1102             if (CharInfo.S_LINEFEED == c && useSystemLineSeparator)
1103             {
1104                 writer.write(m_lineSep, 0, m_lineSepLen);
1105             }
1106             else if (isCData && (!escapingNotNeeded(c)))
1107             {
1108                 i = handleEscaping(writer, c, ch, i, end);
1109             }
1110             else if (
1111                 isCData
1112                     && ((i < (end - 2))
1113                         && (']' == c)
1114                         && (']' == ch[i + 1])
1115                         && ('>' == ch[i + 2])))
1116             {
1117                 writer.write(CDATA_CONTINUE);
1118 
1119                 i += 2;
1120             }
1121             else
1122             {
1123                 if (escapingNotNeeded(c))
1124                 {
1125                     if (isCData && !m_cdataTagOpen)
1126                     {
1127                         writer.write(CDATA_DELIMITER_OPEN);
1128                         m_cdataTagOpen = true;
1129                     }
1130                     writer.write(c);
1131                 }
1132                 else {
1133                     i = handleEscaping(writer, c, ch, i, end);
1134                 }
1135             }
1136         }
1137 
1138     }
1139 
1140     /**
1141      * Handles escaping, writes either with a surrogate pair or a character
1142      * reference.
1143      *
1144      * @param c the current char
1145      * @param ch the character array
1146      * @param i the current position
1147      * @param end the end index of the array
1148      * @return the next index
1149      *
1150      * @throws IOException
1151      * @throws org.xml.sax.SAXException if invalid UTF-16 surrogate detected.
1152      */
handleEscaping(Writer writer, char c, char ch[], int i, int end)1153     private int handleEscaping(Writer writer, char c, char ch[], int i, int end)
1154             throws IOException, SAXException {
1155         if (Encodings.isHighUTF16Surrogate(c) || Encodings.isLowUTF16Surrogate(c))
1156         {
1157             if (writeUTF16Surrogate(c, ch, i, end) >= 0) {
1158                 // move the index if the low surrogate is consumed
1159                 // as writeUTF16Surrogate has written the pair
1160                 if (Encodings.isHighUTF16Surrogate(c)) {
1161                     i++ ;
1162                 }
1163             }
1164         }
1165         else
1166         {
1167             writeCharRef(writer, c);
1168         }
1169         return i;
1170     }
1171 
1172     /**
1173      * Ends an un-escaping section.
1174      *
1175      * @see #startNonEscaping
1176      *
1177      * @throws org.xml.sax.SAXException
1178      */
endNonEscaping()1179     public void endNonEscaping() throws org.xml.sax.SAXException
1180     {
1181         m_disableOutputEscapingStates.pop();
1182     }
1183 
1184     /**
1185      * Starts an un-escaping section. All characters printed within an un-
1186      * escaping section are printed as is, without escaping special characters
1187      * into entity references. Only XML and HTML serializers need to support
1188      * this method.
1189      * <p> The contents of the un-escaping section will be delivered through the
1190      * regular <tt>characters</tt> event.
1191      *
1192      * @throws org.xml.sax.SAXException
1193      */
startNonEscaping()1194     public void startNonEscaping() throws org.xml.sax.SAXException
1195     {
1196         m_disableOutputEscapingStates.push(true);
1197     }
1198 
1199     /**
1200      * Receive notification of cdata.
1201      *
1202      * <p>The Parser will call this method to report each chunk of
1203      * character data.  SAX parsers may return all contiguous character
1204      * data in a single chunk, or they may split it into several
1205      * chunks; however, all of the characters in any single event
1206      * must come from the same external entity, so that the Locator
1207      * provides useful information.</p>
1208      *
1209      * <p>The application must not attempt to read from the array
1210      * outside of the specified range.</p>
1211      *
1212      * <p>Note that some parsers will report whitespace using the
1213      * ignorableWhitespace() method rather than this one (validating
1214      * parsers must do so).</p>
1215      *
1216      * @param ch The characters from the XML document.
1217      * @param start The start position in the array.
1218      * @param length The number of characters to read from the array.
1219      * @throws org.xml.sax.SAXException Any SAX exception, possibly
1220      *            wrapping another exception.
1221      * @see #ignorableWhitespace
1222      * @see org.xml.sax.Locator
1223      *
1224      * @throws org.xml.sax.SAXException
1225      */
cdata(char ch[], int start, final int length)1226     protected void cdata(char ch[], int start, final int length)
1227         throws org.xml.sax.SAXException
1228     {
1229         try
1230         {
1231             final int old_start = start;
1232             if (m_elemContext.m_startTagOpen)
1233             {
1234                 closeStartTag();
1235                 m_elemContext.m_startTagOpen = false;
1236             }
1237 
1238             if (!m_cdataTagOpen && shouldIndent())
1239                 indent();
1240 
1241             boolean writeCDataBrackets =
1242                 (((length >= 1) && escapingNotNeeded(ch[start])));
1243 
1244             /* Write out the CDATA opening delimiter only if
1245              * we are supposed to, and if we are not already in
1246              * the middle of a CDATA section
1247              */
1248             if (writeCDataBrackets && !m_cdataTagOpen)
1249             {
1250                 m_writer.write(CDATA_DELIMITER_OPEN);
1251                 m_cdataTagOpen = true;
1252             }
1253 
1254             // writer.write(ch, start, length);
1255             if (isEscapingDisabled())
1256             {
1257                 charactersRaw(ch, start, length);
1258             }
1259             else
1260                 writeNormalizedChars(ch, start, length, true, m_lineSepUse);
1261 
1262             /* used to always write out CDATA closing delimiter here,
1263              * but now we delay, so that we can merge CDATA sections on output.
1264              * need to write closing delimiter later
1265              */
1266             if (writeCDataBrackets)
1267             {
1268                 /* if the CDATA section ends with ] don't leave it open
1269                  * as there is a chance that an adjacent CDATA sections
1270                  * starts with ]>.
1271                  * We don't want to merge ]] with > , or ] with ]>
1272                  */
1273                 if (ch[start + length - 1] == ']')
1274                     closeCDATA();
1275             }
1276 
1277             // time to fire off CDATA event
1278             if (m_tracer != null)
1279                 super.fireCDATAEvent(ch, old_start, length);
1280         }
1281         catch (IOException ioe)
1282         {
1283             throw new org.xml.sax.SAXException(
1284                 Utils.messages.createMessage(
1285                     MsgKey.ER_OIERROR,
1286                     null),
1287                 ioe);
1288             //"IO error", ioe);
1289         }
1290     }
1291 
1292     /**
1293      * Tell if the character escaping should be disabled for the current state.
1294      *
1295      * @return true if the character escaping should be disabled.
1296      */
isEscapingDisabled()1297     private boolean isEscapingDisabled()
1298     {
1299         return m_disableOutputEscapingStates.peekOrFalse();
1300     }
1301 
1302     /**
1303      * If available, when the disable-output-escaping attribute is used,
1304      * output raw text without escaping.
1305      *
1306      * @param ch The characters from the XML document.
1307      * @param start The start position in the array.
1308      * @param length The number of characters to read from the array.
1309      *
1310      * @throws org.xml.sax.SAXException
1311      */
charactersRaw(char ch[], int start, int length)1312     protected void charactersRaw(char ch[], int start, int length)
1313         throws org.xml.sax.SAXException
1314     {
1315 
1316         if (isInEntityRef())
1317             return;
1318         try
1319         {
1320             if (m_elemContext.m_startTagOpen)
1321             {
1322                 closeStartTag();
1323                 m_elemContext.m_startTagOpen = false;
1324             }
1325 
1326             m_writer.write(ch, start, length);
1327         }
1328         catch (IOException e)
1329         {
1330             throw new SAXException(e);
1331         }
1332 
1333     }
1334 
1335     /**
1336      * Receive notification of character data.
1337      *
1338      * <p>The Parser will call this method to report each chunk of
1339      * character data.  SAX parsers may return all contiguous character
1340      * data in a single chunk, or they may split it into several
1341      * chunks; however, all of the characters in any single event
1342      * must come from the same external entity, so that the Locator
1343      * provides useful information.</p>
1344      *
1345      * <p>The application must not attempt to read from the array
1346      * outside of the specified range.</p>
1347      *
1348      * <p>Note that some parsers will report whitespace using the
1349      * ignorableWhitespace() method rather than this one (validating
1350      * parsers must do so).</p>
1351      *
1352      * @param chars The characters from the XML document.
1353      * @param start The start position in the array.
1354      * @param length The number of characters to read from the array.
1355      * @throws org.xml.sax.SAXException Any SAX exception, possibly
1356      *            wrapping another exception.
1357      * @see #ignorableWhitespace
1358      * @see org.xml.sax.Locator
1359      *
1360      * @throws org.xml.sax.SAXException
1361      */
characters(final char chars[], final int start, final int length)1362     public void characters(final char chars[], final int start, final int length)
1363         throws org.xml.sax.SAXException
1364     {
1365         // It does not make sense to continue with rest of the method if the number of
1366         // characters to read from array is 0.
1367         // Section 7.6.1 of XSLT 1.0 (http://www.w3.org/TR/xslt#value-of) suggest no text node
1368         // is created if string is empty.
1369         if (length == 0 || (isInEntityRef()))
1370             return;
1371 
1372         final boolean shouldNotFormat = !shouldFormatOutput();
1373         if (m_elemContext.m_startTagOpen)
1374         {
1375             closeStartTag();
1376             m_elemContext.m_startTagOpen = false;
1377         }
1378         else if (m_needToCallStartDocument)
1379         {
1380             startDocumentInternal();
1381         }
1382 
1383         if (m_cdataStartCalled || m_elemContext.m_isCdataSection)
1384         {
1385             /* either due to startCDATA() being called or due to
1386              * cdata-section-elements atribute, we need this as cdata
1387              */
1388             cdata(chars, start, length);
1389 
1390             return;
1391         }
1392 
1393         if (m_cdataTagOpen)
1394             closeCDATA();
1395         // the check with _escaping is a bit of a hack for XLSTC
1396 
1397         if (m_disableOutputEscapingStates.peekOrFalse() || (!m_escaping))
1398         {
1399             if (shouldNotFormat) {
1400                 charactersRaw(chars, start, length);
1401                 m_isprevtext = true;
1402             } else {
1403                 m_charactersBuffer.addRawText(chars, start, length);
1404             }
1405             // time to fire off characters generation event
1406             if (m_tracer != null)
1407                 super.fireCharEvent(chars, start, length);
1408 
1409             return;
1410         }
1411 
1412         if (m_elemContext.m_startTagOpen)
1413         {
1414             closeStartTag();
1415             m_elemContext.m_startTagOpen = false;
1416         }
1417 
1418         if (shouldNotFormat) {
1419             outputCharacters(chars, start, length);
1420         } else {
1421             m_charactersBuffer.addText(chars, start, length);
1422         }
1423 
1424         // time to fire off characters generation event
1425         if (m_tracer != null)
1426             super.fireCharEvent(chars, start, length);
1427     }
1428 
1429 
1430     /**
1431      * This method checks if the content in current element should be formatted.
1432      *
1433      * @return True if the content should be formatted.
1434      */
shouldFormatOutput()1435     protected boolean shouldFormatOutput() {
1436         return m_doIndent && !m_ispreserveSpace;
1437     }
1438 
1439     /**
1440      * @return True if the content in current element should be formatted.
1441      */
getIndent()1442     public boolean getIndent() {
1443         return shouldFormatOutput();
1444     }
1445 
1446     /**
1447      * Write out the characters.
1448      *
1449      * @param chars The characters of the text.
1450      * @param start The start position in the char array.
1451      * @param length The number of characters from the char array.
1452      */
outputCharacters(final char chars[], final int start, final int length)1453     private void outputCharacters(final char chars[], final int start, final int length) throws SAXException {
1454         try
1455         {
1456             int i;
1457             char ch1;
1458             int startClean;
1459 
1460             // skip any leading whitspace
1461             // don't go off the end and use a hand inlined version
1462             // of isWhitespace(ch)
1463             final int end = start + length;
1464             int lastDirty = start - 1; // last character that needed processing
1465             for (i = start;
1466                 ((i < end)
1467                     && ((ch1 = chars[i]) == 0x20
1468                         || (ch1 == 0xA && m_lineSepUse)
1469                         || ch1 == 0xD
1470                         || ch1 == 0x09));
1471                 i++)
1472             {
1473                 /*
1474                  * We are processing leading whitespace, but are doing the same
1475                  * processing for dirty characters here as for non-whitespace.
1476                  *
1477                  */
1478                 if (!m_charInfo.isTextASCIIClean(ch1))
1479                 {
1480                     lastDirty = processDirty(chars,end, i,ch1, lastDirty, true);
1481                     i = lastDirty;
1482                 }
1483             }
1484 
1485 //          int lengthClean;    // number of clean characters in a row
1486 //          final boolean[] isAsciiClean = m_charInfo.getASCIIClean();
1487 
1488             final boolean isXML10 = XMLVERSION10.equals(getVersion());
1489             // we've skipped the leading whitespace, now deal with the rest
1490             for (; i < end; i++)
1491             {
1492                 {
1493                     // A tight loop to skip over common clean chars
1494                     // This tight loop makes it easier for the JIT
1495                     // to optimize.
1496                     char ch2;
1497                     while (i<end
1498                             && ((ch2 = chars[i])<127)
1499                             && m_charInfo.isTextASCIIClean(ch2))
1500                             i++;
1501                     if (i == end)
1502                         break;
1503                 }
1504 
1505                 final char ch = chars[i];
1506                 /*  The check for isCharacterInC0orC1Ranger and
1507                  *  isNELorLSEPCharacter has been added
1508                  *  to support Control Characters in XML 1.1
1509                  */
1510                 if (!isCharacterInC0orC1Range(ch) &&
1511                     (isXML10 || !isNELorLSEPCharacter(ch)) &&
1512                     (escapingNotNeeded(ch) && (!m_charInfo.isSpecialTextChar(ch)))
1513                         || ('"' == ch))
1514                 {
1515                     ; // a character needing no special processing
1516                 }
1517                 else
1518                 {
1519                     lastDirty = processDirty(chars,end, i, ch, lastDirty, true);
1520                     i = lastDirty;
1521                 }
1522             }
1523 
1524             // we've reached the end. Any clean characters at the
1525             // end of the array than need to be written out?
1526             startClean = lastDirty + 1;
1527             if (i > startClean)
1528             {
1529                 int lengthClean = i - startClean;
1530                 m_writer.write(chars, startClean, lengthClean);
1531             }
1532 
1533             // For indentation purposes, mark that we've just writen text out
1534             m_isprevtext = true;
1535         }
1536         catch (IOException e)
1537         {
1538             throw new SAXException(e);
1539         }
1540     }
1541 
1542     /**
1543      * Used to flush the buffered characters when indentation is on, this method
1544      * will be called when the next node is traversed.
1545      *
1546      */
flushCharactersBuffer()1547     final protected void flushCharactersBuffer() throws SAXException {
1548         try {
1549             if (shouldFormatOutput() && m_charactersBuffer.isAnyCharactersBuffered()) {
1550                 if (m_elemContext.m_isCdataSection) {
1551                     /*
1552                      * due to cdata-section-elements atribute, we need this as
1553                      * cdata
1554                      */
1555                     char[] chars = m_charactersBuffer.toChars();
1556                     cdata(chars, 0, chars.length);
1557                     return;
1558                 }
1559 
1560                 m_childNodeNum++;
1561                 boolean skipBeginningNewlines = false;
1562                 if (shouldIndentForText()) {
1563                     indent();
1564                     m_startNewLine = true;
1565                     // newline has always been added here because if this is the
1566                     // text before the first element, shouldIndent() won't
1567                     // return true.
1568                     skipBeginningNewlines = true;
1569                 }
1570                 m_charactersBuffer.flush(skipBeginningNewlines);
1571             }
1572         } catch (IOException e) {
1573             throw new SAXException(e);
1574         } finally {
1575             m_charactersBuffer.clear();
1576         }
1577     }
1578 
1579     /**
1580      * True if should indent in flushCharactersBuffer method.
1581      * This method may be overridden in sub-class.
1582      *
1583      */
shouldIndentForText()1584     protected boolean shouldIndentForText() {
1585         return (shouldIndent() && m_childNodeNum > 1);
1586     }
1587 
1588     /**
1589      * This method checks if a given character is between C0 or C1 range
1590      * of Control characters.
1591      * This method is added to support Control Characters for XML 1.1
1592      * If a given character is TAB (0x09), LF (0x0A) or CR (0x0D), this method
1593      * return false. Since they are whitespace characters, no special processing is needed.
1594      *
1595      * @param ch
1596      * @return boolean
1597      */
isCharacterInC0orC1Range(char ch)1598     private static boolean isCharacterInC0orC1Range(char ch)
1599     {
1600         if(ch == 0x09 || ch == 0x0A || ch == 0x0D)
1601                 return false;
1602         else
1603                 return (ch >= 0x7F && ch <= 0x9F)|| (ch >= 0x01 && ch <= 0x1F);
1604     }
1605     /**
1606      * This method checks if a given character either NEL (0x85) or LSEP (0x2028)
1607      * These are new end of line charcters added in XML 1.1.  These characters must be
1608      * written as Numeric Character References (NCR) in XML 1.1 output document.
1609      *
1610      * @param ch
1611      * @return boolean
1612      */
isNELorLSEPCharacter(char ch)1613     private static boolean isNELorLSEPCharacter(char ch)
1614     {
1615         return (ch == 0x85 || ch == 0x2028);
1616     }
1617     /**
1618      * Process a dirty character and any preeceding clean characters
1619      * that were not yet processed.
1620      * @param chars array of characters being processed
1621      * @param end one (1) beyond the last character
1622      * in chars to be processed
1623      * @param i the index of the dirty character
1624      * @param ch the character in chars[i]
1625      * @param lastDirty the last dirty character previous to i
1626      * @param fromTextNode true if the characters being processed are
1627      * from a text node, false if they are from an attribute value.
1628      * @return the index of the last character processed
1629      */
processDirty( char[] chars, int end, int i, char ch, int lastDirty, boolean fromTextNode)1630     private int processDirty(
1631         char[] chars,
1632         int end,
1633         int i,
1634         char ch,
1635         int lastDirty,
1636         boolean fromTextNode) throws IOException, SAXException
1637     {
1638         int startClean = lastDirty + 1;
1639         // if we have some clean characters accumulated
1640         // process them before the dirty one.
1641         if (i > startClean)
1642         {
1643             int lengthClean = i - startClean;
1644             m_writer.write(chars, startClean, lengthClean);
1645         }
1646 
1647         // process the "dirty" character
1648         if (CharInfo.S_LINEFEED == ch && fromTextNode)
1649         {
1650             m_writer.write(m_lineSep, 0, m_lineSepLen);
1651         }
1652         else
1653         {
1654             startClean =
1655                 accumDefaultEscape(
1656                     m_writer,
1657                     ch,
1658                     i,
1659                     chars,
1660                     end,
1661                     fromTextNode,
1662                     false);
1663             i = startClean - 1;
1664         }
1665         // Return the index of the last character that we just processed
1666         // which is a dirty character.
1667         return i;
1668     }
1669 
1670     /**
1671      * Receive notification of character data.
1672      *
1673      * @param s The string of characters to process.
1674      *
1675      * @throws org.xml.sax.SAXException
1676      */
characters(String s)1677     public void characters(String s) throws org.xml.sax.SAXException
1678     {
1679         if (isInEntityRef())
1680             return;
1681         final int length = s.length();
1682         if (length > m_charsBuff.length)
1683         {
1684             m_charsBuff = new char[length * 2 + 1];
1685         }
1686         s.getChars(0, length, m_charsBuff, 0);
1687         characters(m_charsBuff, 0, length);
1688     }
1689 
1690     /**
1691      * Escape and writer.write a character.
1692      *
1693      * @param ch character to be escaped.
1694      * @param i index into character array.
1695      * @param chars non-null reference to character array.
1696      * @param len length of chars.
1697      * @param fromTextNode true if the characters being processed are
1698      * from a text node, false if the characters being processed are from
1699      * an attribute value.
1700      * @param escLF true if the linefeed should be escaped.
1701      *
1702      * @return i+1 if a character was written, i+2 if two characters
1703      * were written out, else return i.
1704      *
1705      * @throws org.xml.sax.SAXException
1706      */
accumDefaultEscape( Writer writer, char ch, int i, char[] chars, int len, boolean fromTextNode, boolean escLF)1707     protected int accumDefaultEscape(
1708         Writer writer,
1709         char ch,
1710         int i,
1711         char[] chars,
1712         int len,
1713         boolean fromTextNode,
1714         boolean escLF)
1715         throws IOException, SAXException
1716     {
1717 
1718         int pos = accumDefaultEntity(writer, ch, i, chars, len, fromTextNode, escLF);
1719 
1720         if (i == pos)
1721         {
1722             if (m_highSurrogate != 0) {
1723                 if (!(Encodings.isLowUTF16Surrogate(ch))) {
1724                     throwIOE(m_highSurrogate, ch);
1725                 }
1726                 writeCharRef(writer, m_highSurrogate, ch);
1727                 m_highSurrogate = 0;
1728                 return ++pos;
1729             }
1730 
1731             if (Encodings.isHighUTF16Surrogate(ch))
1732             {
1733                 if (i + 1 >= len)
1734                 {
1735                     // save for the next read
1736                     m_highSurrogate = ch;
1737                     pos++;
1738                 }
1739                 else
1740                 {
1741                     // the next should be the UTF-16 low surrogate of the hig/low pair.
1742                     char next = chars[++i];
1743                     if (!(Encodings.isLowUTF16Surrogate(next)))
1744                         throwIOE(ch, next);
1745 
1746                     writeCharRef(writer, ch, next);
1747                     pos += 2; // count the two characters that went into writing out this entity
1748                 }
1749             }
1750             else
1751             {
1752                 /*  This if check is added to support control characters in XML 1.1.
1753                  *  If a character is a Control Character within C0 and C1 range, it is desirable
1754                  *  to write it out as Numeric Character Reference(NCR) regardless of XML Version
1755                  *  being used for output document.
1756                  */
1757                 if (isCharacterInC0orC1Range(ch) ||
1758                         (XMLVERSION11.equals(getVersion()) && isNELorLSEPCharacter(ch)))
1759                 {
1760                     writeCharRef(writer, ch);
1761                 }
1762                 else if ((!escapingNotNeeded(ch) ||
1763                     (  (fromTextNode && m_charInfo.isSpecialTextChar(ch))
1764                      || (!fromTextNode && m_charInfo.isSpecialAttrChar(ch))))
1765                      && m_elemContext.m_currentElemDepth > 0)
1766                 {
1767                     writeCharRef(writer, ch);
1768                 }
1769                 else
1770                 {
1771                     writer.write(ch);
1772                 }
1773                 pos++;  // count the single character that was processed
1774             }
1775 
1776         }
1777         return pos;
1778     }
1779 
1780     /**
1781      * Writes out a character reference.
1782      * @param writer the writer
1783      * @param c the character
1784      * @throws IOException
1785      */
writeCharRef(Writer writer, char c)1786     private void writeCharRef(Writer writer, char c) throws IOException, SAXException {
1787         if (m_cdataTagOpen)
1788             closeCDATA();
1789         writer.write("&#");
1790         writer.write(Integer.toString(c));
1791         writer.write(';');
1792     }
1793 
1794     /**
1795      * Writes out a pair of surrogates as a character reference
1796      * @param writer the writer
1797      * @param high the high surrogate
1798      * @param low the low surrogate
1799      * @throws IOException
1800      */
writeCharRef(Writer writer, char high, char low)1801     private int writeCharRef(Writer writer, char high, char low) throws IOException, SAXException {
1802         if (m_cdataTagOpen)
1803             closeCDATA();
1804         // Unicode code point formed from the high/low pair.
1805         int codePoint = Encodings.toCodePoint(high, low);
1806         writer.write("&#");
1807         writer.write(Integer.toString(codePoint));
1808         writer.write(';');
1809         return codePoint;
1810     }
1811 
throwIOE(char ch, char next)1812     private void throwIOE(char ch, char next) throws IOException {
1813         throw new IOException(Utils.messages.createMessage(
1814                 MsgKey.ER_INVALID_UTF16_SURROGATE,
1815                 new Object[] {Integer.toHexString(ch) + " "
1816                         + Integer.toHexString(next)}));
1817     }
1818 
1819     /**
1820      * Receive notification of the beginning of an element, although this is a
1821      * SAX method additional namespace or attribute information can occur before
1822      * or after this call, that is associated with this element.
1823      *
1824      *
1825      * @param namespaceURI The Namespace URI, or the empty string if the
1826      *        element has no Namespace URI or if Namespace
1827      *        processing is not being performed.
1828      * @param localName The local name (without prefix), or the
1829      *        empty string if Namespace processing is not being
1830      *        performed.
1831      * @param name The element type name.
1832      * @param atts The attributes attached to the element, if any.
1833      * @throws org.xml.sax.SAXException Any SAX exception, possibly
1834      *            wrapping another exception.
1835      * @see org.xml.sax.ContentHandler#startElement
1836      * @see org.xml.sax.ContentHandler#endElement
1837      * @see org.xml.sax.AttributeList
1838      *
1839      * @throws org.xml.sax.SAXException
1840      */
startElement( String namespaceURI, String localName, String name, Attributes atts)1841     public void startElement(
1842         String namespaceURI,
1843         String localName,
1844         String name,
1845         Attributes atts)
1846         throws org.xml.sax.SAXException
1847     {
1848         if (isInEntityRef())
1849             return;
1850 
1851         if (m_doIndent) {
1852             m_childNodeNum++;
1853             flushCharactersBuffer();
1854         }
1855 
1856         if (m_needToCallStartDocument)
1857         {
1858             startDocumentInternal();
1859             m_needToCallStartDocument = false;
1860         }
1861         else if (m_cdataTagOpen)
1862             closeCDATA();
1863         try
1864         {
1865             if ((true == m_needToOutputDocTypeDecl)
1866                 && (null != getDoctypeSystem()))
1867             {
1868                 outputDocTypeDecl(name, true);
1869             }
1870 
1871             m_needToOutputDocTypeDecl = false;
1872 
1873             /* before we over-write the current elementLocalName etc.
1874              * lets close out the old one (if we still need to)
1875              */
1876             if (m_elemContext.m_startTagOpen)
1877             {
1878                 closeStartTag();
1879                 m_elemContext.m_startTagOpen = false;
1880             }
1881 
1882             if (namespaceURI != null)
1883                 ensurePrefixIsDeclared(namespaceURI, name);
1884 
1885             if (shouldIndent() && m_startNewLine)
1886             {
1887                 indent();
1888             }
1889 
1890             m_startNewLine = true;
1891 
1892             final Writer writer = m_writer;
1893             writer.write('<');
1894             writer.write(name);
1895         }
1896         catch (IOException e)
1897         {
1898             throw new SAXException(e);
1899         }
1900 
1901         // process the attributes now, because after this SAX call they might be gone
1902         if (atts != null)
1903             addAttributes(atts);
1904 
1905         if (m_doIndent) {
1906             m_ispreserveSpace = m_preserveSpaces.peekOrFalse();
1907             m_preserveSpaces.push(m_ispreserveSpace);
1908 
1909             m_childNodeNumStack.add(m_childNodeNum);
1910             m_childNodeNum = 0;
1911         }
1912 
1913         m_elemContext = m_elemContext.push(namespaceURI,localName,name);
1914         m_isprevtext = false;
1915 
1916         if (m_tracer != null){
1917             firePseudoAttributes();
1918         }
1919 
1920     }
1921 
1922     /**
1923       * Receive notification of the beginning of an element, additional
1924       * namespace or attribute information can occur before or after this call,
1925       * that is associated with this element.
1926       *
1927       *
1928       * @param elementNamespaceURI The Namespace URI, or the empty string if the
1929       *        element has no Namespace URI or if Namespace
1930       *        processing is not being performed.
1931       * @param elementLocalName The local name (without prefix), or the
1932       *        empty string if Namespace processing is not being
1933       *        performed.
1934       * @param elementName The element type name.
1935       * @throws org.xml.sax.SAXException Any SAX exception, possibly
1936       *            wrapping another exception.
1937       * @see org.xml.sax.ContentHandler#startElement
1938       * @see org.xml.sax.ContentHandler#endElement
1939       * @see org.xml.sax.AttributeList
1940       *
1941       * @throws org.xml.sax.SAXException
1942       */
startElement( String elementNamespaceURI, String elementLocalName, String elementName)1943     public void startElement(
1944         String elementNamespaceURI,
1945         String elementLocalName,
1946         String elementName)
1947         throws SAXException
1948     {
1949         startElement(elementNamespaceURI, elementLocalName, elementName, null);
1950     }
1951 
startElement(String elementName)1952     public void startElement(String elementName) throws SAXException
1953     {
1954         startElement(null, null, elementName, null);
1955     }
1956 
1957     /**
1958      * Output the doc type declaration.
1959      *
1960      * @param name non-null reference to document type name.
1961      * NEEDSDOC @param closeDecl
1962      *
1963      * @throws java.io.IOException
1964      */
outputDocTypeDecl(String name, boolean closeDecl)1965     void outputDocTypeDecl(String name, boolean closeDecl) throws SAXException
1966     {
1967         if (m_cdataTagOpen)
1968             closeCDATA();
1969         try
1970         {
1971             final Writer writer = m_writer;
1972             writer.write("<!DOCTYPE ");
1973             writer.write(name);
1974 
1975             String doctypePublic = getDoctypePublic();
1976             if (null != doctypePublic)
1977             {
1978                 writer.write(" PUBLIC \"");
1979                 writer.write(doctypePublic);
1980                 writer.write('\"');
1981             }
1982 
1983             String doctypeSystem = getDoctypeSystem();
1984             if (null != doctypeSystem)
1985             {
1986                 char quote = JdkXmlUtils.getQuoteChar(doctypeSystem);
1987                 if (null == doctypePublic) {
1988                     writer.write(" SYSTEM");
1989                 }
1990                 writer.write(" ");
1991                 writer.write(quote);
1992 
1993                 writer.write(doctypeSystem);
1994                 writer.write(quote);
1995                 if (closeDecl)
1996                 {
1997                     writer.write(">");
1998                     writer.write(m_lineSep, 0, m_lineSepLen);
1999                     closeDecl = false; // done closing
2000                 }
2001             }
2002             boolean dothis = false;
2003             if (dothis)
2004             {
2005                 // at one point this code seemed right,
2006                 // but not anymore - Brian M.
2007                 if (closeDecl)
2008                 {
2009                     writer.write('>');
2010                     writer.write(m_lineSep, 0, m_lineSepLen);
2011                 }
2012             }
2013         }
2014         catch (IOException e)
2015         {
2016             throw new SAXException(e);
2017         }
2018     }
2019 
2020     /**
2021      * Process the attributes, which means to write out the currently
2022      * collected attributes to the writer. The attributes are not
2023      * cleared by this method
2024      *
2025      * @param writer the writer to write processed attributes to.
2026      * @param nAttrs the number of attributes in m_attributes
2027      * to be processed
2028      *
2029      * @throws java.io.IOException
2030      * @throws org.xml.sax.SAXException
2031      */
processAttributes(Writer writer, int nAttrs)2032     public void processAttributes(Writer writer, int nAttrs) throws IOException, SAXException
2033     {
2034             /* real SAX attributes are not passed in, so process the
2035              * attributes that were collected after the startElement call.
2036              * _attribVector is a "cheap" list for Stream serializer output
2037              * accumulated over a series of calls to attribute(name,value)
2038              */
2039             String encoding = getEncoding();
2040             for (int i = 0; i < nAttrs; i++)
2041             {
2042                 // elementAt is JDK 1.1.8
2043                 final String name = m_attributes.getQName(i);
2044                 final String value = m_attributes.getValue(i);
2045                 writer.write(' ');
2046                 writer.write(name);
2047                 writer.write("=\"");
2048                 writeAttrString(writer, value, encoding);
2049                 writer.write('\"');
2050             }
2051     }
2052 
2053     /**
2054      * Returns the specified <var>string</var> after substituting <VAR>specials</VAR>,
2055      * and UTF-16 surrogates for chracter references <CODE>&amp;#xnn</CODE>.
2056      *
2057      * @param   string      String to convert to XML format.
2058      * @param   encoding    CURRENTLY NOT IMPLEMENTED.
2059      *
2060      * @throws java.io.IOException
2061      */
writeAttrString( Writer writer, String string, String encoding)2062     public void writeAttrString(
2063         Writer writer,
2064         String string,
2065         String encoding)
2066         throws IOException, SAXException
2067     {
2068         final int len = string.length();
2069         if (len > m_attrBuff.length)
2070         {
2071            m_attrBuff = new char[len*2 + 1];
2072         }
2073         string.getChars(0,len, m_attrBuff, 0);
2074         final char[] stringChars = m_attrBuff;
2075 
2076         for (int i = 0; i < len; )
2077         {
2078             char ch = stringChars[i];
2079             if (escapingNotNeeded(ch) && (!m_charInfo.isSpecialAttrChar(ch)))
2080             {
2081                 writer.write(ch);
2082                 i++;
2083             }
2084             else
2085             { // I guess the parser doesn't normalize cr/lf in attributes. -sb
2086 //                if ((CharInfo.S_CARRIAGERETURN == ch)
2087 //                    && ((i + 1) < len)
2088 //                    && (CharInfo.S_LINEFEED == stringChars[i + 1]))
2089 //                {
2090 //                    i++;
2091 //                    ch = CharInfo.S_LINEFEED;
2092 //                }
2093 
2094                 i = accumDefaultEscape(writer, ch, i, stringChars, len, false, true);
2095             }
2096         }
2097 
2098     }
2099 
2100     /**
2101      * Receive notification of the end of an element.
2102      *
2103      *
2104      * @param namespaceURI The Namespace URI, or the empty string if the
2105      *        element has no Namespace URI or if Namespace
2106      *        processing is not being performed.
2107      * @param localName The local name (without prefix), or the
2108      *        empty string if Namespace processing is not being
2109      *        performed.
2110      * @param name The element type name
2111      * @throws org.xml.sax.SAXException Any SAX exception, possibly
2112      *            wrapping another exception.
2113      *
2114      * @throws org.xml.sax.SAXException
2115      */
endElement(String namespaceURI, String localName, String name)2116     public void endElement(String namespaceURI, String localName, String name)
2117         throws org.xml.sax.SAXException
2118     {
2119 
2120         if (isInEntityRef())
2121             return;
2122 
2123         if (m_doIndent) {
2124             flushCharactersBuffer();
2125         }
2126         // namespaces declared at the current depth are no longer valid
2127         // so get rid of them
2128         m_prefixMap.popNamespaces(m_elemContext.m_currentElemDepth, null);
2129 
2130         try
2131         {
2132             final Writer writer = m_writer;
2133             if (m_elemContext.m_startTagOpen)
2134             {
2135                 if (m_tracer != null)
2136                     super.fireStartElem(m_elemContext.m_elementName);
2137                 int nAttrs = m_attributes.getLength();
2138                 if (nAttrs > 0)
2139                 {
2140                     processAttributes(m_writer, nAttrs);
2141                     // clear attributes object for re-use with next element
2142                     m_attributes.clear();
2143                 }
2144                 if (m_spaceBeforeClose)
2145                     writer.write(" />");
2146                 else
2147                     writer.write("/>");
2148                 /* don't need to pop cdataSectionState because
2149                  * this element ended so quickly that we didn't get
2150                  * to push the state.
2151                  */
2152 
2153             }
2154             else
2155             {
2156                 if (m_cdataTagOpen)
2157                     closeCDATA();
2158 
2159                 if (shouldIndent() && (m_childNodeNum > 1 || !m_isprevtext))
2160                     indent(m_elemContext.m_currentElemDepth - 1);
2161                 writer.write('<');
2162                 writer.write('/');
2163                 writer.write(name);
2164                 writer.write('>');
2165             }
2166         }
2167         catch (IOException e)
2168         {
2169             throw new SAXException(e);
2170         }
2171 
2172         if (m_doIndent) {
2173             m_ispreserveSpace = m_preserveSpaces.popAndTop();
2174             m_childNodeNum = m_childNodeNumStack.remove(m_childNodeNumStack.size() - 1);
2175 
2176             m_isprevtext = false;
2177         }
2178 
2179         // fire off the end element event
2180         if (m_tracer != null)
2181             super.fireEndElem(name);
2182         m_elemContext = m_elemContext.m_prev;
2183     }
2184 
2185     /**
2186      * Receive notification of the end of an element.
2187      * @param name The element type name
2188      * @throws org.xml.sax.SAXException Any SAX exception, possibly
2189      *     wrapping another exception.
2190      */
endElement(String name)2191     public void endElement(String name) throws org.xml.sax.SAXException
2192     {
2193         endElement(null, null, name);
2194     }
2195 
2196     /**
2197      * Begin the scope of a prefix-URI Namespace mapping
2198      * just before another element is about to start.
2199      * This call will close any open tags so that the prefix mapping
2200      * will not apply to the current element, but the up comming child.
2201      *
2202      * @see org.xml.sax.ContentHandler#startPrefixMapping
2203      *
2204      * @param prefix The Namespace prefix being declared.
2205      * @param uri The Namespace URI the prefix is mapped to.
2206      *
2207      * @throws org.xml.sax.SAXException The client may throw
2208      *            an exception during processing.
2209      *
2210      */
startPrefixMapping(String prefix, String uri)2211     public void startPrefixMapping(String prefix, String uri)
2212         throws org.xml.sax.SAXException
2213     {
2214         // the "true" causes the flush of any open tags
2215         startPrefixMapping(prefix, uri, true);
2216     }
2217 
2218     /**
2219      * Handle a prefix/uri mapping, which is associated with a startElement()
2220      * that is soon to follow. Need to close any open start tag to make
2221      * sure than any name space attributes due to this event are associated wih
2222      * the up comming element, not the current one.
2223      * @see ExtendedContentHandler#startPrefixMapping
2224      *
2225      * @param prefix The Namespace prefix being declared.
2226      * @param uri The Namespace URI the prefix is mapped to.
2227      * @param shouldFlush true if any open tags need to be closed first, this
2228      * will impact which element the mapping applies to (open parent, or its up
2229      * comming child)
2230      * @return returns true if the call made a change to the current
2231      * namespace information, false if it did not change anything, e.g. if the
2232      * prefix/namespace mapping was already in scope from before.
2233      *
2234      * @throws org.xml.sax.SAXException The client may throw
2235      *            an exception during processing.
2236      *
2237      *
2238      */
startPrefixMapping( String prefix, String uri, boolean shouldFlush)2239     public boolean startPrefixMapping(
2240         String prefix,
2241         String uri,
2242         boolean shouldFlush)
2243         throws org.xml.sax.SAXException
2244     {
2245 
2246         /* Remember the mapping, and at what depth it was declared
2247          * This is one greater than the current depth because these
2248          * mappings will apply to the next depth. This is in
2249          * consideration that startElement() will soon be called
2250          */
2251 
2252         boolean pushed;
2253         int pushDepth;
2254         if (shouldFlush)
2255         {
2256             flushPending();
2257             // the prefix mapping applies to the child element (one deeper)
2258             pushDepth = m_elemContext.m_currentElemDepth + 1;
2259         }
2260         else
2261         {
2262             // the prefix mapping applies to the current element
2263             pushDepth = m_elemContext.m_currentElemDepth;
2264         }
2265         pushed = m_prefixMap.pushNamespace(prefix, uri, pushDepth);
2266 
2267         if (pushed)
2268         {
2269             /* Brian M.: don't know if we really needto do this. The
2270              * callers of this object should have injected both
2271              * startPrefixMapping and the attributes.  We are
2272              * just covering our butt here.
2273              */
2274             String name;
2275             if (EMPTYSTRING.equals(prefix))
2276             {
2277                 name = "xmlns";
2278                 addAttributeAlways(XMLNS_URI, name, name, "CDATA", uri, false);
2279             }
2280             else
2281             {
2282                 if (!EMPTYSTRING.equals(uri))
2283                     // hack for XSLTC attribset16 test
2284                 { // that maps ns1 prefix to "" URI
2285                     name = "xmlns:" + prefix;
2286 
2287                     /* for something like xmlns:abc="w3.pretend.org"
2288                      *  the      uri is the value, that is why we pass it in the
2289                      * value, or 5th slot of addAttributeAlways()
2290                      */
2291                     addAttributeAlways(XMLNS_URI, prefix, name, "CDATA", uri, false);
2292                 }
2293             }
2294         }
2295         return pushed;
2296     }
2297 
2298     /**
2299      * Receive notification of an XML comment anywhere in the document. This
2300      * callback will be used for comments inside or outside the document
2301      * element, including comments in the external DTD subset (if read).
2302      * @param ch An array holding the characters in the comment.
2303      * @param start The starting position in the array.
2304      * @param length The number of characters to use from the array.
2305      * @throws org.xml.sax.SAXException The application may raise an exception.
2306      */
comment(char ch[], int start, int length)2307     public void comment(char ch[], int start, int length)
2308         throws org.xml.sax.SAXException
2309     {
2310 
2311         int start_old = start;
2312         if (isInEntityRef())
2313             return;
2314         if (m_doIndent) {
2315             m_childNodeNum++;
2316             flushCharactersBuffer();
2317         }
2318         if (m_elemContext.m_startTagOpen)
2319         {
2320             closeStartTag();
2321             m_elemContext.m_startTagOpen = false;
2322         }
2323         else if (m_needToCallStartDocument)
2324         {
2325             startDocumentInternal();
2326             m_needToCallStartDocument = false;
2327         }
2328 
2329         try
2330         {
2331             if (shouldIndent() && m_isStandalone)
2332                 indent();
2333 
2334             final int limit = start + length;
2335             boolean wasDash = false;
2336             if (m_cdataTagOpen)
2337                 closeCDATA();
2338 
2339             if (shouldIndent() && !m_isStandalone)
2340                 indent();
2341 
2342             final Writer writer = m_writer;
2343             writer.write(COMMENT_BEGIN);
2344             // Detect occurrences of two consecutive dashes, handle as necessary.
2345             for (int i = start; i < limit; i++)
2346             {
2347                 if (wasDash && ch[i] == '-')
2348                 {
2349                     writer.write(ch, start, i - start);
2350                     writer.write(" -");
2351                     start = i + 1;
2352                 }
2353                 wasDash = (ch[i] == '-');
2354             }
2355 
2356             // if we have some chars in the comment
2357             if (length > 0)
2358             {
2359                 // Output the remaining characters (if any)
2360                 final int remainingChars = (limit - start);
2361                 if (remainingChars > 0)
2362                     writer.write(ch, start, remainingChars);
2363                 // Protect comment end from a single trailing dash
2364                 if (ch[limit - 1] == '-')
2365                     writer.write(' ');
2366             }
2367             writer.write(COMMENT_END);
2368         }
2369         catch (IOException e)
2370         {
2371             throw new SAXException(e);
2372         }
2373 
2374         /*
2375          * Don't write out any indentation whitespace now,
2376          * because there may be non-whitespace text after this.
2377          *
2378          * Simply mark that at this point if we do decide
2379          * to indent that we should
2380          * add a newline on the end of the current line before
2381          * the indentation at the start of the next line.
2382          */
2383         m_startNewLine = true;
2384         // time to generate comment event
2385         if (m_tracer != null)
2386             super.fireCommentEvent(ch, start_old,length);
2387     }
2388 
2389     /**
2390      * Report the end of a CDATA section.
2391      * @throws org.xml.sax.SAXException The application may raise an exception.
2392      *
2393      *  @see  #startCDATA
2394      */
endCDATA()2395     public void endCDATA() throws org.xml.sax.SAXException
2396     {
2397         if (m_cdataTagOpen)
2398             closeCDATA();
2399         m_cdataStartCalled = false;
2400     }
2401 
2402     /**
2403      * Report the end of DTD declarations.
2404      * @throws org.xml.sax.SAXException The application may raise an exception.
2405      * @see #startDTD
2406      */
endDTD()2407     public void endDTD() throws org.xml.sax.SAXException
2408     {
2409         try
2410         {
2411             // Don't output doctype declaration until startDocumentInternal
2412             // has been called. Otherwise, it can appear before XML decl.
2413             if (m_needToCallStartDocument) {
2414                 return;
2415             }
2416 
2417             if (m_needToOutputDocTypeDecl)
2418             {
2419                 outputDocTypeDecl(m_elemContext.m_elementName, false);
2420                 m_needToOutputDocTypeDecl = false;
2421             }
2422             final Writer writer = m_writer;
2423             if (!m_inDoctype)
2424                 writer.write("]>");
2425             else
2426             {
2427                 writer.write('>');
2428             }
2429 
2430             writer.write(m_lineSep, 0, m_lineSepLen);
2431         }
2432         catch (IOException e)
2433         {
2434             throw new SAXException(e);
2435         }
2436 
2437     }
2438 
2439     /**
2440      * End the scope of a prefix-URI Namespace mapping.
2441      * @see org.xml.sax.ContentHandler#endPrefixMapping
2442      *
2443      * @param prefix The prefix that was being mapping.
2444      * @throws org.xml.sax.SAXException The client may throw
2445      *            an exception during processing.
2446      */
endPrefixMapping(String prefix)2447     public void endPrefixMapping(String prefix) throws org.xml.sax.SAXException
2448     { // do nothing
2449     }
2450 
2451     /**
2452      * Receive notification of ignorable whitespace in element content.
2453      *
2454      * Not sure how to get this invoked quite yet.
2455      *
2456      * @param ch The characters from the XML document.
2457      * @param start The start position in the array.
2458      * @param length The number of characters to read from the array.
2459      * @throws org.xml.sax.SAXException Any SAX exception, possibly
2460      *            wrapping another exception.
2461      * @see #characters
2462      *
2463      * @throws org.xml.sax.SAXException
2464      */
ignorableWhitespace(char ch[], int start, int length)2465     public void ignorableWhitespace(char ch[], int start, int length)
2466         throws org.xml.sax.SAXException
2467     {
2468 
2469         if (0 == length)
2470             return;
2471         characters(ch, start, length);
2472     }
2473 
2474     /**
2475      * Receive notification of a skipped entity.
2476      * @see org.xml.sax.ContentHandler#skippedEntity
2477      *
2478      * @param name The name of the skipped entity.  If it is a
2479      *       parameter                   entity, the name will begin with '%',
2480      * and if it is the external DTD subset, it will be the string
2481      * "[dtd]".
2482      * @throws org.xml.sax.SAXException Any SAX exception, possibly wrapping
2483      * another exception.
2484      */
skippedEntity(String name)2485     public void skippedEntity(String name) throws org.xml.sax.SAXException
2486     { // TODO: Should handle
2487     }
2488 
2489     /**
2490      * Report the start of a CDATA section.
2491      *
2492      * @throws org.xml.sax.SAXException The application may raise an exception.
2493      * @see #endCDATA
2494      */
startCDATA()2495     public void startCDATA() throws org.xml.sax.SAXException
2496     {
2497         if (m_doIndent) {
2498             m_childNodeNum++;
2499             flushCharactersBuffer();
2500         }
2501 
2502         m_cdataStartCalled = true;
2503     }
2504 
2505     /**
2506      * Report the beginning of an entity.
2507      *
2508      * The start and end of the document entity are not reported.
2509      * The start and end of the external DTD subset are reported
2510      * using the pseudo-name "[dtd]".  All other events must be
2511      * properly nested within start/end entity events.
2512      *
2513      * @param name The name of the entity.  If it is a parameter
2514      *        entity, the name will begin with '%'.
2515      * @throws org.xml.sax.SAXException The application may raise an exception.
2516      * @see #endEntity
2517      * @see org.xml.sax.ext.DeclHandler#internalEntityDecl
2518      * @see org.xml.sax.ext.DeclHandler#externalEntityDecl
2519      */
startEntity(String name)2520     public void startEntity(String name) throws org.xml.sax.SAXException
2521     {
2522         if (name.equals("[dtd]"))
2523             m_inExternalDTD = true;
2524 
2525         // if this is not the magic [dtd] name
2526         if (!m_inExternalDTD) {
2527             // if it's not in nested entity reference
2528             if (!isInEntityRef()) {
2529                 if (shouldFormatOutput()) {
2530                     m_charactersBuffer.addEntityReference(name);
2531                 } else {
2532                     outputEntityReference(name);
2533                 }
2534             }
2535             m_inEntityRef++;
2536         }
2537     }
2538 
2539     /**
2540      * Write out the entity reference with the form as "&amp;entityName;".
2541      *
2542      * @param name The name of the entity.
2543      */
outputEntityReference(String name)2544     private void outputEntityReference(String name) throws SAXException {
2545         startNonEscaping();
2546         characters("&" + name + ';');
2547         endNonEscaping();
2548         m_isprevtext = true;
2549     }
2550 
2551     /**
2552      * For the enclosing elements starting tag write out
2553      * out any attributes followed by ">"
2554      *
2555      * @throws org.xml.sax.SAXException
2556      */
closeStartTag()2557     protected void closeStartTag() throws SAXException
2558     {
2559         if (m_elemContext.m_startTagOpen)
2560         {
2561 
2562             try
2563             {
2564                 if (m_tracer != null)
2565                     super.fireStartElem(m_elemContext.m_elementName);
2566                 int nAttrs = m_attributes.getLength();
2567                 if (nAttrs > 0)
2568                 {
2569                      processAttributes(m_writer, nAttrs);
2570                     // clear attributes object for re-use with next element
2571                     m_attributes.clear();
2572                 }
2573                 m_writer.write('>');
2574             }
2575             catch (IOException e)
2576             {
2577                 throw new SAXException(e);
2578             }
2579 
2580             /* whether Xalan or XSLTC, we have the prefix mappings now, so
2581              * lets determine if the current element is specified in the cdata-
2582              * section-elements list.
2583              */
2584             if (m_StringOfCDATASections != null)
2585                 m_elemContext.m_isCdataSection = isCdataSection();
2586         }
2587 
2588     }
2589 
2590     /**
2591      * Report the start of DTD declarations, if any.
2592      *
2593      * Any declarations are assumed to be in the internal subset unless
2594      * otherwise indicated.
2595      *
2596      * @param name The document type name.
2597      * @param publicId The declared public identifier for the
2598      *        external DTD subset, or null if none was declared.
2599      * @param systemId The declared system identifier for the
2600      *        external DTD subset, or null if none was declared.
2601      * @throws org.xml.sax.SAXException The application may raise an
2602      *            exception.
2603      * @see #endDTD
2604      * @see #startEntity
2605      */
startDTD(String name, String publicId, String systemId)2606     public void startDTD(String name, String publicId, String systemId)
2607         throws org.xml.sax.SAXException
2608     {
2609         setDoctypeSystem(systemId);
2610         setDoctypePublic(publicId);
2611 
2612         m_elemContext.m_elementName = name;
2613         m_inDoctype = true;
2614     }
2615 
2616     /**
2617      * Returns the m_indentAmount.
2618      * @return int
2619      */
getIndentAmount()2620     public int getIndentAmount()
2621     {
2622         return m_indentAmount;
2623     }
2624 
2625     /**
2626      * Sets the m_indentAmount.
2627      *
2628      * @param m_indentAmount The m_indentAmount to set
2629      */
setIndentAmount(int m_indentAmount)2630     public void setIndentAmount(int m_indentAmount)
2631     {
2632         this.m_indentAmount = m_indentAmount;
2633     }
2634 
2635     /**
2636      * Tell if, based on space preservation constraints and the doIndent property,
2637      * if an indent should occur.
2638      *
2639      * @return True if an indent should occur.
2640      */
shouldIndent()2641     protected boolean shouldIndent()
2642     {
2643         return shouldFormatOutput() && (m_elemContext.m_currentElemDepth > 0 || m_isStandalone);
2644     }
2645 
2646     /**
2647      * Searches for the list of qname properties with the specified key in the
2648      * property list. If the key is not found in this property list, the default
2649      * property list, and its defaults, recursively, are then checked. The
2650      * method returns <code>null</code> if the property is not found.
2651      *
2652      * @param   key   the property key.
2653      * @param props the list of properties to search in.
2654      *
2655      * Sets the ArrayList of local-name/URI pairs of the cdata section elements
2656      * specified in the cdata-section-elements property.
2657      *
2658      * This method is essentially a copy of getQNameProperties() from
2659      * OutputProperties. Eventually this method should go away and a call
2660      * to setCdataSectionElements(List<String> v) should be made directly.
2661      */
setCdataSectionElements(String key, Properties props)2662     private void setCdataSectionElements(String key, Properties props) {
2663         String s = props.getProperty(key);
2664 
2665         if (null != s) {
2666             // List<String> of URI/LocalName pairs
2667             List<String> al = new ArrayList<>();
2668             int l = s.length();
2669             boolean inCurly = false;
2670             StringBuilder buf = new StringBuilder();
2671 
2672             // parse through string, breaking on whitespaces.  I do this instead
2673             // of a tokenizer so I can track whitespace inside of curly brackets,
2674             // which theoretically shouldn't happen if they contain legal URLs.
2675             for (int i = 0; i < l; i++)
2676             {
2677                 char c = s.charAt(i);
2678 
2679                 if (Character.isWhitespace(c))
2680                 {
2681                     if (!inCurly)
2682                     {
2683                         if (buf.length() > 0)
2684                         {
2685                             addCdataSectionElement(buf.toString(), al);
2686                             buf.setLength(0);
2687                         }
2688                         continue;
2689                     }
2690                 }
2691                 else if ('{' == c)
2692                     inCurly = true;
2693                 else if ('}' == c)
2694                     inCurly = false;
2695 
2696                 buf.append(c);
2697             }
2698 
2699             if (buf.length() > 0)
2700             {
2701                 addCdataSectionElement(buf.toString(), al);
2702                 buf.setLength(0);
2703             }
2704             // call the official, public method to set the collected names
2705             setCdataSectionElements(al);
2706         }
2707 
2708     }
2709 
2710     /**
2711      * Adds a URI/LocalName pair of strings to the list.
2712      *
2713      * @param URI_and_localName String of the form "{uri}local" or "local"
2714      *
2715      * @return a QName object
2716      */
addCdataSectionElement(String URI_and_localName, List<String> al)2717     private void addCdataSectionElement(String URI_and_localName, List<String> al) {
2718         StringTokenizer tokenizer = new StringTokenizer(URI_and_localName, "{}", false);
2719         String s1 = tokenizer.nextToken();
2720         String s2 = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
2721 
2722         if (null == s2) {
2723             // add null URI and the local name
2724             al.add(null);
2725             al.add(s1);
2726         } else {
2727             // add URI, then local name
2728             al.add(s1);
2729             al.add(s2);
2730         }
2731     }
2732 
2733     /**
2734      * Remembers the cdata sections specified in the cdata-section-elements.
2735      * The "official way to set URI and localName pairs.
2736      * This method should be used by both Xalan and XSLTC.
2737      *
2738      * @param URI_and_localNames an ArrayList of pairs of Strings (URI/local)
2739      */
setCdataSectionElements(List<String> URI_and_localNames)2740     public void setCdataSectionElements(List<String> URI_and_localNames) {
2741         // convert to the new way.
2742         if (URI_and_localNames != null) {
2743             final int len = URI_and_localNames.size() - 1;
2744             if (len > 0) {
2745                 final StringBuilder sb = new StringBuilder();
2746                 for (int i = 0; i < len; i += 2) {
2747                     // whitspace separated "{uri1}local1 {uri2}local2 ..."
2748                     if (i != 0)
2749                         sb.append(' ');
2750                     final String uri = URI_and_localNames.get(i);
2751                     final String localName = URI_and_localNames.get(i + 1);
2752                     if (uri != null) {
2753                         // If there is no URI don't put this in, just the localName then.
2754                         sb.append('{');
2755                         sb.append(uri);
2756                         sb.append('}');
2757                     }
2758                     sb.append(localName);
2759                 }
2760                 m_StringOfCDATASections = sb.toString();
2761             }
2762         }
2763         initCdataElems(m_StringOfCDATASections);
2764     }
2765 
2766     /**
2767      * Makes sure that the namespace URI for the given qualified attribute name
2768      * is declared.
2769      * @param ns the namespace URI
2770      * @param rawName the qualified name
2771      * @return returns null if no action is taken, otherwise it returns the
2772      * prefix used in declaring the namespace.
2773      * @throws SAXException
2774      */
ensureAttributesNamespaceIsDeclared( String ns, String localName, String rawName)2775     protected String ensureAttributesNamespaceIsDeclared(
2776         String ns,
2777         String localName,
2778         String rawName)
2779         throws org.xml.sax.SAXException
2780     {
2781 
2782         if (ns != null && ns.length() > 0)
2783         {
2784 
2785             // extract the prefix in front of the raw name
2786             int index = 0;
2787             String prefixFromRawName =
2788                 (index = rawName.indexOf(":")) < 0
2789                     ? ""
2790                     : rawName.substring(0, index);
2791 
2792             if (index > 0)
2793             {
2794                 // we have a prefix, lets see if it maps to a namespace
2795                 String uri = m_prefixMap.lookupNamespace(prefixFromRawName);
2796                 if (uri != null && uri.equals(ns))
2797                 {
2798                     // the prefix in the raw name is already maps to the given namespace uri
2799                     // so we don't need to do anything
2800                     return null;
2801                 }
2802                 else
2803                 {
2804                     // The uri does not map to the prefix in the raw name,
2805                     // so lets make the mapping.
2806                     this.startPrefixMapping(prefixFromRawName, ns, false);
2807                     this.addAttribute(
2808                         "http://www.w3.org/2000/xmlns/",
2809                         prefixFromRawName,
2810                         "xmlns:" + prefixFromRawName,
2811                         "CDATA",
2812                         ns, false);
2813                     return prefixFromRawName;
2814                 }
2815             }
2816             else
2817             {
2818                 // we don't have a prefix in the raw name.
2819                 // Does the URI map to a prefix already?
2820                 String prefix = m_prefixMap.lookupPrefix(ns);
2821                 if (prefix == null)
2822                 {
2823                     // uri is not associated with a prefix,
2824                     // so lets generate a new prefix to use
2825                     prefix = m_prefixMap.generateNextPrefix();
2826                     this.startPrefixMapping(prefix, ns, false);
2827                     this.addAttribute(
2828                         "http://www.w3.org/2000/xmlns/",
2829                         prefix,
2830                         "xmlns:" + prefix,
2831                         "CDATA",
2832                         ns, false);
2833                 }
2834 
2835                 return prefix;
2836 
2837             }
2838         }
2839         return null;
2840     }
2841 
ensurePrefixIsDeclared(String ns, String rawName)2842     void ensurePrefixIsDeclared(String ns, String rawName)
2843         throws org.xml.sax.SAXException
2844     {
2845 
2846         if (ns != null && ns.length() > 0)
2847         {
2848             int index;
2849             final boolean no_prefix = ((index = rawName.indexOf(":")) < 0);
2850             String prefix = (no_prefix) ? "" : rawName.substring(0, index);
2851 
2852             if (null != prefix)
2853             {
2854                 String foundURI = m_prefixMap.lookupNamespace(prefix);
2855 
2856                 if ((null == foundURI) || !foundURI.equals(ns))
2857                 {
2858                     this.startPrefixMapping(prefix, ns);
2859 
2860                     // Bugzilla1133: Generate attribute as well as namespace event.
2861                     // SAX does expect both.
2862 
2863                     this.addAttributeAlways(
2864                         "http://www.w3.org/2000/xmlns/",
2865                         no_prefix ? "xmlns" : prefix,  // local name
2866                         no_prefix ? "xmlns" : ("xmlns:"+ prefix), // qname
2867                         "CDATA",
2868                         ns,
2869                         false);
2870                 }
2871 
2872             }
2873         }
2874     }
2875 
2876     /**
2877      * This method flushes any pending events, which can be startDocument()
2878      * closing the opening tag of an element, or closing an open CDATA section.
2879      */
flushPending()2880     public void flushPending() throws SAXException
2881     {
2882             if (m_needToCallStartDocument)
2883             {
2884                 startDocumentInternal();
2885                 m_needToCallStartDocument = false;
2886             }
2887             if (m_elemContext.m_startTagOpen)
2888             {
2889                 closeStartTag();
2890                 m_elemContext.m_startTagOpen = false;
2891             }
2892 
2893             if (m_cdataTagOpen)
2894             {
2895                 closeCDATA();
2896                 m_cdataTagOpen = false;
2897             }
2898     }
2899 
setContentHandler(ContentHandler ch)2900     public void setContentHandler(ContentHandler ch)
2901     {
2902         // this method is really only useful in the ToSAXHandler classes but it is
2903         // in the interface.  If the method defined here is ever called
2904         // we are probably in trouble.
2905     }
2906 
2907     /**
2908      * Adds the given attribute to the set of attributes, even if there is
2909      * no currently open element. This is useful if a SAX startPrefixMapping()
2910      * should need to add an attribute before the element name is seen.
2911      *
2912      * This method is a copy of its super classes method, except that some
2913      * tracing of events is done.  This is so the tracing is only done for
2914      * stream serializers, not for SAX ones.
2915      *
2916      * @param uri the URI of the attribute
2917      * @param localName the local name of the attribute
2918      * @param rawName   the qualified name of the attribute
2919      * @param type the type of the attribute (probably CDATA)
2920      * @param value the value of the attribute
2921      * @param xslAttribute true if this attribute is coming from an xsl:attribute element.
2922      * @return true if the attribute value was added,
2923      * false if the attribute already existed and the value was
2924      * replaced with the new value.
2925      */
addAttributeAlways( String uri, String localName, String rawName, String type, String value, boolean xslAttribute)2926     public boolean addAttributeAlways(
2927         String uri,
2928         String localName,
2929         String rawName,
2930         String type,
2931         String value,
2932         boolean xslAttribute)
2933     {
2934         if (!m_charactersBuffer.isAnyCharactersBuffered()) {
2935             return doAddAttributeAlways(uri, localName, rawName, type, value, xslAttribute);
2936         } else {
2937             /*
2938              * If stylesheet includes xsl:copy-of an attribute node, XSLTC will
2939              * fire an addAttribute event. When a text node is handling in
2940              * ToStream, addAttribute has no effect. But closeStartTag call is
2941              * delayed to flushCharactersBuffer() method if the text node is
2942              * buffered, so here we ignore the attribute to avoid corrupting the
2943              * start tag content.
2944              *
2945              */
2946             return m_attributes.getIndex(rawName) < 0;
2947         }
2948     }
2949 
2950     /**
2951      * Does really add the attribute to the set of attributes.
2952      */
doAddAttributeAlways( String uri, String localName, String rawName, String type, String value, boolean xslAttribute)2953     private boolean doAddAttributeAlways(
2954         String uri,
2955         String localName,
2956         String rawName,
2957         String type,
2958         String value,
2959         boolean xslAttribute)
2960     {
2961         boolean was_added;
2962         int index;
2963         //if (uri == null || localName == null || uri.length() == 0)
2964         index = m_attributes.getIndex(rawName);
2965         // Don't use 'localName' as it gives incorrect value, rely only on 'rawName'
2966         /*else {
2967             index = m_attributes.getIndex(uri, localName);
2968         }*/
2969         if (index >= 0)
2970         {
2971             String old_value = null;
2972             if (m_tracer != null)
2973             {
2974                 old_value = m_attributes.getValue(index);
2975                 if (value.equals(old_value))
2976                     old_value = null;
2977             }
2978 
2979             /* We've seen the attribute before.
2980              * We may have a null uri or localName, but all we really
2981              * want to re-set is the value anyway.
2982              */
2983             m_attributes.setValue(index, value);
2984             was_added = false;
2985             if (old_value != null){
2986                 firePseudoAttributes();
2987             }
2988 
2989         }
2990         else
2991         {
2992             // the attribute doesn't exist yet, create it
2993             if (xslAttribute)
2994             {
2995                 /*
2996                  * This attribute is from an xsl:attribute element so we take some care in
2997                  * adding it, e.g.
2998                  *   <elem1  foo:attr1="1" xmlns:foo="uri1">
2999                  *       <xsl:attribute name="foo:attr2">2</xsl:attribute>
3000                  *   </elem1>
3001                  *
3002                  * We are adding attr1 and attr2 both as attributes of elem1,
3003                  * and this code is adding attr2 (the xsl:attribute ).
3004                  * We could have a collision with the prefix like in the example above.
3005                  */
3006 
3007                 // In the example above, is there a prefix like foo ?
3008                 final int colonIndex = rawName.indexOf(':');
3009                 if (colonIndex > 0)
3010                 {
3011                     String prefix = rawName.substring(0,colonIndex);
3012                     NamespaceMappings.MappingRecord existing_mapping = m_prefixMap.getMappingFromPrefix(prefix);
3013 
3014                     /* Before adding this attribute (foo:attr2),
3015                      * is the prefix for it (foo) already mapped at the current depth?
3016                      */
3017                     if (existing_mapping != null
3018                     && existing_mapping.m_declarationDepth == m_elemContext.m_currentElemDepth
3019                     && !existing_mapping.m_uri.equals(uri))
3020                     {
3021                         /*
3022                          * There is an existing mapping of this prefix,
3023                          * it differs from the one we need,
3024                          * and unfortunately it is at the current depth so we
3025                          * can not over-ride it.
3026                          */
3027 
3028                         /*
3029                          * Are we lucky enough that an existing other prefix maps to this URI ?
3030                          */
3031                         prefix = m_prefixMap.lookupPrefix(uri);
3032                         if (prefix == null)
3033                         {
3034                             /* Unfortunately there is no existing prefix that happens to map to ours,
3035                              * so to avoid a prefix collision we must generated a new prefix to use.
3036                              * This is OK because the prefix URI mapping
3037                              * defined in the xsl:attribute is short in scope,
3038                              * just the xsl:attribute element itself,
3039                              * and at this point in serialization the body of the
3040                              * xsl:attribute, if any, is just a String. Right?
3041                              *   . . . I sure hope so - Brian M.
3042                              */
3043                             prefix = m_prefixMap.generateNextPrefix();
3044                         }
3045 
3046                         rawName = prefix + ':' + localName;
3047                     }
3048                 }
3049 
3050                 try
3051                 {
3052                     /* This is our last chance to make sure the namespace for this
3053                      * attribute is declared, especially if we just generated an alternate
3054                      * prefix to avoid a collision (the new prefix/rawName will go out of scope
3055                      * soon and be lost ...  last chance here.
3056                      */
3057                     String prefixUsed =
3058                         ensureAttributesNamespaceIsDeclared(
3059                             uri,
3060                             localName,
3061                             rawName);
3062                 }
3063                 catch (SAXException e)
3064                 {
3065                     // TODO Auto-generated catch block
3066                     e.printStackTrace();
3067                 }
3068             }
3069 
3070             m_attributes.addAttribute(uri, localName, rawName, type, value);
3071             was_added = true;
3072             if (m_tracer != null){
3073                 firePseudoAttributes();
3074             }
3075         }
3076 
3077         if (m_doIndent && rawName.equals("xml:space")) {
3078             if (value.equals("preserve")) {
3079                 m_ispreserveSpace = true;
3080                 if (m_preserveSpaces.size() > 0)
3081                     m_preserveSpaces.setTop(m_ispreserveSpace);
3082             } else if (value.equals("default")) {
3083                 m_ispreserveSpace = false;
3084                 if (m_preserveSpaces.size() > 0)
3085                     m_preserveSpaces.setTop(m_ispreserveSpace);
3086             }
3087         }
3088 
3089         return was_added;
3090     }
3091 
3092     /**
3093      * To fire off the pseudo characters of attributes, as they currently
3094      * exist. This method should be called everytime an attribute is added,
3095      * or when an attribute value is changed, or an element is created.
3096      */
firePseudoAttributes()3097     protected void firePseudoAttributes() {
3098         if (m_tracer != null) {
3099             try {
3100                 // flush out the "<elemName" if not already flushed
3101                 m_writer.flush();
3102 
3103                 // make a StringBuffer to write the name="value" pairs to.
3104                 StringBuffer sb = new StringBuffer();
3105                 int nAttrs = m_attributes.getLength();
3106                 if (nAttrs > 0) {
3107                     // make a writer that internally appends to the same
3108                     // StringBuffer
3109                     Writer writer = new ToStream.WritertoStringBuffer(sb);
3110 
3111                     processAttributes(writer, nAttrs);
3112                     // Don't clear the attributes!
3113                     // We only want to see what would be written out
3114                     // at this point, we don't want to loose them.
3115                 }
3116                 sb.append('>');  // the potential > after the attributes.
3117                 // convert the StringBuffer to a char array and
3118                 // emit the trace event that these characters "might"
3119                 // be written
3120                 char ch[] = sb.toString().toCharArray();
3121                 m_tracer.fireGenerateEvent(
3122                     SerializerTrace.EVENTTYPE_OUTPUT_PSEUDO_CHARACTERS,
3123                     ch,
3124                     0,
3125                     ch.length);
3126             } catch (IOException ioe) {
3127                 // ignore ?
3128             } catch (SAXException se) {
3129                 // ignore ?
3130             }
3131         }
3132     }
3133 
3134     /**
3135      * This inner class is used only to collect attribute values
3136      * written by the method writeAttrString() into a string buffer.
3137      * In this manner trace events, and the real writing of attributes will use
3138      * the same code.
3139      */
3140     private class WritertoStringBuffer extends Writer {
3141         final private StringBuffer m_stringbuf;
3142 
3143         /**
3144          * @see java.io.Writer#write(char[], int, int)
3145          */
WritertoStringBuffer(StringBuffer sb)3146         WritertoStringBuffer(StringBuffer sb) {
3147             m_stringbuf = sb;
3148         }
3149 
write(char[] arg0, int arg1, int arg2)3150         public void write(char[] arg0, int arg1, int arg2) throws IOException {
3151             m_stringbuf.append(arg0, arg1, arg2);
3152         }
3153 
3154         /**
3155          * @see java.io.Writer#flush()
3156          */
flush()3157         public void flush() throws IOException {}
3158 
3159         /**
3160          * @see java.io.Writer#close()
3161          */
close()3162         public void close() throws IOException {}
3163 
write(int i)3164         public void write(int i) {
3165             m_stringbuf.append((char) i);
3166         }
3167 
write(String s)3168         public void write(String s) {
3169             m_stringbuf.append(s);
3170         }
3171     }
3172 
3173     /**
3174      * @see SerializationHandler#setTransformer(Transformer)
3175      */
setTransformer(Transformer transformer)3176     public void setTransformer(Transformer transformer) {
3177         super.setTransformer(transformer);
3178         if (m_tracer != null && !(m_writer instanceof SerializerTraceWriter)) {
3179             m_writer = new SerializerTraceWriter(m_writer, m_tracer);
3180         }
3181     }
3182 
3183     /**
3184      * Try's to reset the super class and reset this class for
3185      * re-use, so that you don't need to create a new serializer
3186      * (mostly for performance reasons).
3187      *
3188      * @return true if the class was successfuly reset.
3189      */
reset()3190     public boolean reset() {
3191         boolean wasReset = false;
3192         if (super.reset()) {
3193             resetToStream();
3194             wasReset = true;
3195         }
3196         return wasReset;
3197     }
3198 
3199     /**
3200      * Reset all of the fields owned by ToStream class
3201      *
3202      */
resetToStream()3203     private void resetToStream() {
3204          this.m_cdataStartCalled = false;
3205          /* The stream is being reset. It is one of
3206           * ToXMLStream, ToHTMLStream ... and this type can't be changed
3207           * so neither should m_charInfo which is associated with the
3208           * type of Stream. Just leave m_charInfo as-is for the next re-use.
3209           */
3210          // this.m_charInfo = null; // don't set to null
3211 
3212          this.m_disableOutputEscapingStates.clear();
3213 
3214          this.m_escaping = true;
3215          // Leave m_format alone for now - Brian M.
3216          // this.m_format = null;
3217          this.m_inDoctype = false;
3218          this.m_ispreserveSpace = false;
3219          this.m_preserveSpaces.clear();
3220          this.m_childNodeNum = 0;
3221          this.m_childNodeNumStack.clear();
3222          this.m_charactersBuffer.clear();
3223          this.m_isprevtext = false;
3224          this.m_isUTF8 = false; //  ?? used anywhere ??
3225          this.m_shouldFlush = true;
3226          this.m_spaceBeforeClose = false;
3227          this.m_startNewLine = false;
3228          this.m_lineSepUse = true;
3229          // DON'T SET THE WRITER TO NULL, IT MAY BE REUSED !!
3230          // this.m_writer = null;
3231          this.m_expandDTDEntities = true;
3232 
3233     }
3234 
3235     /**
3236       * Sets the character encoding coming from the xsl:output encoding stylesheet attribute.
3237       * @param encoding the character encoding
3238       */
setEncoding(String encoding)3239      public void setEncoding(String encoding)
3240      {
3241          setOutputProperty(OutputKeys.ENCODING,encoding);
3242      }
3243 
3244     /**
3245      * Simple stack for boolean values.
3246      *
3247      * This class is a copy of the one in com.sun.org.apache.xml.internal.utils.
3248      * It exists to cut the serializers dependancy on that package.
3249      * A minor changes from that package are:
3250      * doesn't implement Clonable
3251      *
3252      * @xsl.usage internal
3253      */
3254     static final class BoolStack {
3255         /** Array of boolean values */
3256         private boolean m_values[];
3257 
3258         /** Array size allocated */
3259         private int m_allocatedSize;
3260 
3261         /** Index into the array of booleans */
3262         private int m_index;
3263 
3264         /**
3265          * Default constructor.  Note that the default
3266          * block size is very small, for small lists.
3267          */
BoolStack()3268         public BoolStack() {
3269             this(32);
3270         }
3271 
3272         /**
3273          * Construct a IntVector, using the given block size.
3274          *
3275          * @param size array size to allocate
3276          */
BoolStack(int size)3277         public BoolStack(int size) {
3278             m_allocatedSize = size;
3279             m_values = new boolean[size];
3280             m_index = -1;
3281         }
3282 
3283         /**
3284          * Get the length of the list.
3285          *
3286          * @return Current length of the list
3287          */
size()3288         public final int size() {
3289             return m_index + 1;
3290         }
3291 
3292         /**
3293          * Clears the stack.
3294          *
3295          */
clear()3296         public final void clear() {
3297             m_index = -1;
3298         }
3299 
3300         /**
3301          * Pushes an item onto the top of this stack.
3302          *
3303          *
3304          * @param val the boolean to be pushed onto this stack.
3305          * @return  the <code>item</code> argument.
3306          */
push(boolean val)3307         public final boolean push(boolean val) {
3308             if (m_index == m_allocatedSize - 1)
3309                 grow();
3310 
3311             return (m_values[++m_index] = val);
3312         }
3313 
3314         /**
3315          * Removes the object at the top of this stack and returns that
3316          * object as the value of this function.
3317          *
3318          * @return     The object at the top of this stack.
3319          * @throws  EmptyStackException  if this stack is empty.
3320          */
pop()3321         public final boolean pop() {
3322             return m_values[m_index--];
3323         }
3324 
3325         /**
3326          * Removes the object at the top of this stack and returns the
3327          * next object at the top as the value of this function.
3328          *
3329          *
3330          * @return Next object to the top or false if none there
3331          */
popAndTop()3332         public final boolean popAndTop() {
3333             m_index--;
3334             return (m_index >= 0) ? m_values[m_index] : false;
3335         }
3336 
3337         /**
3338          * Set the item at the top of this stack
3339          *
3340          *
3341          * @param b Object to set at the top of this stack
3342          */
setTop(boolean b)3343         public final void setTop(boolean b) {
3344             m_values[m_index] = b;
3345         }
3346 
3347         /**
3348          * Looks at the object at the top of this stack without removing it
3349          * from the stack.
3350          *
3351          * @return     the object at the top of this stack.
3352          * @throws  EmptyStackException  if this stack is empty.
3353          */
peek()3354         public final boolean peek() {
3355             return m_values[m_index];
3356         }
3357 
3358         /**
3359          * Looks at the object at the top of this stack without removing it
3360          * from the stack.  If the stack is empty, it returns false.
3361          *
3362          * @return     the object at the top of this stack.
3363          */
peekOrFalse()3364         public final boolean peekOrFalse() {
3365             return (m_index > -1) ? m_values[m_index] : false;
3366         }
3367 
3368         /**
3369          * Looks at the object at the top of this stack without removing it
3370          * from the stack.  If the stack is empty, it returns true.
3371          *
3372          * @return     the object at the top of this stack.
3373          */
peekOrTrue()3374         public final boolean peekOrTrue() {
3375             return (m_index > -1) ? m_values[m_index] : true;
3376         }
3377 
3378         /**
3379          * Tests if this stack is empty.
3380          *
3381          * @return  <code>true</code> if this stack is empty;
3382          *          <code>false</code> otherwise.
3383          */
isEmpty()3384         public boolean isEmpty() {
3385             return (m_index == -1);
3386         }
3387 
3388         /**
3389          * Grows the size of the stack
3390          *
3391          */
grow()3392         private void grow() {
3393             m_allocatedSize *= 2;
3394             boolean newVector[] = new boolean[m_allocatedSize];
3395             System.arraycopy(m_values, 0, newVector, 0, m_index + 1);
3396             m_values = newVector;
3397         }
3398     }
3399 
3400 
3401     /**
3402      * This inner class is used to buffer the text nodes and the entity
3403      * reference nodes if indentation is on. There is only one CharacterBuffer
3404      * instance in ToStream, it contains a queue of GenericCharacters,
3405      * GenericCharacters can be a text node or an entity reference node. The
3406      * text nodes and entity reference nodes are joined together and then are
3407      * flushed.
3408      */
3409     private class CharacterBuffer {
3410         /**
3411          * GenericCharacters is immutable.
3412          */
3413         private abstract class GenericCharacters {
3414             /**
3415              * @return True if all characters in this Text are newlines.
3416              */
3417             abstract boolean flush(boolean skipBeginningNewlines) throws SAXException;
3418 
3419             /**
3420              * Converts this GenericCharacters to a new character array. This
3421              * method is used to handle cdata-section-elements attribute in
3422              * xsl:output. Therefore it doesn't need to consider
3423              * skipBeginningNewlines because the text will be involved with CDATA
3424              * tag.
3425              */
3426             abstract char[] toChars();
3427         }
3428 
3429         private List<GenericCharacters> bufferedCharacters = new ArrayList<>();
3430 
3431         /**
3432          * Append a text node to the buffer.
3433          */
addText(final char chars[], final int start, final int length)3434         public void addText(final char chars[], final int start, final int length) {
3435             bufferedCharacters.add(new GenericCharacters() {
3436                 char[] text;
3437 
3438                 {
3439                     text = Arrays.copyOfRange(chars, start, start + length);
3440                 }
3441 
3442                 boolean flush(boolean skipBeginningNewlines) throws SAXException {
3443                     int start = 0;
3444                     while (skipBeginningNewlines && text[start] == '\n') {
3445                         start++;
3446                         if (start == text.length) {
3447                             return true;
3448                         }
3449                     }
3450                     outputCharacters(text, start, text.length - start);
3451                     return false;
3452                 }
3453 
3454                 char[] toChars() {
3455                     return text;
3456                 }
3457             });
3458         }
3459 
3460         /**
3461          * Append an entity reference to the buffer.
3462          */
addEntityReference(String entityName)3463         public void addEntityReference(String entityName) {
3464             bufferedCharacters.add(new GenericCharacters() {
3465                 boolean flush(boolean skipBeginningNewlines) throws SAXException {
3466                     if (m_elemContext.m_startTagOpen)
3467                     {
3468                         closeStartTag();
3469                         m_elemContext.m_startTagOpen = false;
3470                     }
3471                     if (m_cdataTagOpen)
3472                         closeCDATA();
3473                     char[] cs = toChars();
3474                     try {
3475                         m_writer.write(cs, 0, cs.length);
3476                         m_isprevtext = true;
3477                     } catch (IOException e) {
3478                         throw new SAXException(e);
3479                     }
3480                     return false;
3481                 }
3482 
3483                 char[] toChars() {
3484                     return ("&" + entityName + ";").toCharArray();
3485                 }
3486             });
3487         }
3488 
3489         /**
3490          * Append a raw text to the buffer. Used to handle raw characters event.
3491          */
addRawText(final char chars[], final int start, final int length)3492         public void addRawText(final char chars[], final int start, final int length) {
3493             bufferedCharacters.add(new GenericCharacters() {
3494                 char[] text;
3495 
3496                 {
3497                     text = Arrays.copyOfRange(chars, start, start + length);
3498                 }
3499 
3500                 boolean flush(boolean skipBeginningNewlines) throws SAXException {
3501                     try {
3502                         int start = 0;
3503                         while (skipBeginningNewlines && text[start] == '\n') {
3504                             start++;
3505                             if (start == text.length) {
3506                                 return true;
3507                             }
3508                         }
3509                         m_writer.write(text, start, text.length - start);
3510                         m_isprevtext = true;
3511                     } catch (IOException e) {
3512                         throw new SAXException(e);
3513                     }
3514                     return false;
3515                 }
3516 
3517                 char[] toChars() {
3518                     return text;
3519                 }
3520             });
3521         }
3522 
3523         /**
3524          * @return True if any GenericCharacters are buffered.
3525          */
isAnyCharactersBuffered()3526         public boolean isAnyCharactersBuffered() {
3527             return bufferedCharacters.size() > 0;
3528         }
3529 
3530         /**
3531          * Flush all buffered GenericCharacters.
3532          */
flush(boolean skipBeginningNewlines)3533         public void flush(boolean skipBeginningNewlines) throws SAXException {
3534             Iterator<GenericCharacters> itr = bufferedCharacters.iterator();
3535 
3536             boolean continueSkipBeginningNewlines = skipBeginningNewlines;
3537             while (itr.hasNext()) {
3538                 GenericCharacters element = itr.next();
3539                 continueSkipBeginningNewlines = element.flush(continueSkipBeginningNewlines);
3540                 itr.remove();
3541             }
3542         }
3543 
3544         /**
3545          * Converts all buffered GenericCharacters to a new character array.
3546          */
toChars()3547         public char[] toChars() {
3548             StringBuilder sb = new StringBuilder();
3549             for (GenericCharacters element : bufferedCharacters) {
3550                 sb.append(element.toChars());
3551             }
3552             return sb.toString().toCharArray();
3553         }
3554 
3555         /**
3556          * Clear the buffer.
3557          */
clear()3558         public void clear() {
3559             bufferedCharacters.clear();
3560         }
3561     }
3562 
3563 
3564     // Implement DTDHandler
3565     /**
3566      * If this method is called, the serializer is used as a
3567      * DTDHandler, which changes behavior how the serializer
3568      * handles document entities.
3569      * @see org.xml.sax.DTDHandler#notationDecl(java.lang.String, java.lang.String, java.lang.String)
3570      */
notationDecl(String name, String pubID, String sysID)3571     public void notationDecl(String name, String pubID, String sysID) throws SAXException {
3572         // TODO Auto-generated method stub
3573         try {
3574             DTDprolog();
3575 
3576             m_writer.write("<!NOTATION ");
3577             m_writer.write(name);
3578             if (pubID != null) {
3579                 m_writer.write(" PUBLIC \"");
3580                 m_writer.write(pubID);
3581 
3582             }
3583             else {
3584                 m_writer.write(" SYSTEM \"");
3585                 m_writer.write(sysID);
3586             }
3587             m_writer.write("\" >");
3588             m_writer.write(m_lineSep, 0, m_lineSepLen);
3589         } catch (IOException e) {
3590             // TODO Auto-generated catch block
3591             e.printStackTrace();
3592         }
3593     }
3594 
3595     /**
3596      * If this method is called, the serializer is used as a
3597      * DTDHandler, which changes behavior how the serializer
3598      * handles document entities.
3599      * @see org.xml.sax.DTDHandler#unparsedEntityDecl(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
3600      */
unparsedEntityDecl(String name, String pubID, String sysID, String notationName)3601     public void unparsedEntityDecl(String name, String pubID, String sysID, String notationName) throws SAXException {
3602         // TODO Auto-generated method stub
3603         try {
3604             DTDprolog();
3605 
3606             m_writer.write("<!ENTITY ");
3607             m_writer.write(name);
3608             if (pubID != null) {
3609                 m_writer.write(" PUBLIC \"");
3610                 m_writer.write(pubID);
3611 
3612             }
3613             else {
3614                 m_writer.write(" SYSTEM \"");
3615                 m_writer.write(sysID);
3616             }
3617             m_writer.write("\" NDATA ");
3618             m_writer.write(notationName);
3619             m_writer.write(" >");
3620             m_writer.write(m_lineSep, 0, m_lineSepLen);
3621         } catch (IOException e) {
3622             // TODO Auto-generated catch block
3623             e.printStackTrace();
3624         }
3625     }
3626 
3627     /**
3628      * A private helper method to output the
3629      * @throws SAXException
3630      * @throws IOException
3631      */
DTDprolog()3632     private void DTDprolog() throws SAXException, IOException {
3633         final Writer writer = m_writer;
3634         if (m_needToOutputDocTypeDecl) {
3635             outputDocTypeDecl(m_elemContext.m_elementName, false);
3636             m_needToOutputDocTypeDecl = false;
3637         }
3638         if (m_inDoctype) {
3639             writer.write(" [");
3640             writer.write(m_lineSep, 0, m_lineSepLen);
3641             m_inDoctype = false;
3642         }
3643     }
3644 
3645     /**
3646      * If set to false the serializer does not expand DTD entities,
3647      * but leaves them as is, the default value is true;
3648      */
setDTDEntityExpansion(boolean expand)3649     public void setDTDEntityExpansion(boolean expand) {
3650         m_expandDTDEntities = expand;
3651     }
3652 
3653     /**
3654      * Remembers the cdata sections specified in the cdata-section-elements by appending the given
3655      * cdata section elements to the list. This method can be called multiple times, but once an
3656      * element is put in the list of cdata section elements it can not be removed.
3657      * This method should be used by both Xalan and XSLTC.
3658      *
3659      * @param URI_and_localNames a whitespace separated list of element names, each element
3660      * is a URI in curly braces (optional) and a local name. An example of such a parameter is:
3661      * "{http://company.com}price {myURI2}book chapter"
3662      */
addCdataSectionElements(String URI_and_localNames)3663     public void addCdataSectionElements(String URI_and_localNames)
3664     {
3665         if (URI_and_localNames != null)
3666             initCdataElems(URI_and_localNames);
3667         if (m_StringOfCDATASections == null)
3668             m_StringOfCDATASections = URI_and_localNames;
3669         else
3670             m_StringOfCDATASections += (" " + URI_and_localNames);
3671     }
3672 }
3673