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