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