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