jaxp/src/com/sun/org/apache/xml/internal/serializer/ToStream.java
changeset 12457 c348e06f0e82
parent 6 7f561c08de6b
child 12458 d601e4bba306
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jaxp/src/com/sun/org/apache/xml/internal/serializer/ToStream.java	Thu Apr 12 08:38:26 2012 -0700
@@ -0,0 +1,3375 @@
+/*
+ * reserved comment block
+ * DO NOT REMOVE OR ALTER!
+ */
+/*
+ * Copyright 2001-2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * $Id: ToStream.java,v 1.4 2005/11/10 06:43:26 suresh_emailid Exp $
+ */
+package com.sun.org.apache.xml.internal.serializer;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import javax.xml.transform.ErrorListener;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+
+import com.sun.org.apache.xml.internal.serializer.utils.MsgKey;
+import com.sun.org.apache.xml.internal.serializer.utils.Utils;
+import com.sun.org.apache.xml.internal.serializer.utils.WrappedRuntimeException;
+import org.w3c.dom.Node;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+//import com.sun.media.sound.IESecurity;
+
+/**
+ * This abstract class is a base class for other stream
+ * serializers (xml, html, text ...) that write output to a stream.
+ *
+ * @xsl.usage internal
+ */
+abstract public class ToStream extends SerializerBase
+{
+
+    private static final String COMMENT_BEGIN = "<!--";
+    private static final String COMMENT_END = "-->";
+
+    /** Stack to keep track of disabling output escaping. */
+    protected BoolStack m_disableOutputEscapingStates = new BoolStack();
+
+
+    /**
+     * The encoding information associated with this serializer.
+     * Although initially there is no encoding,
+     * there is a dummy EncodingInfo object that will say
+     * that every character is in the encoding. This is useful
+     * for a serializer that is in temporary output state and has
+     * no associated encoding. A serializer in final output state
+     * will have an encoding, and will worry about whether
+     * single chars or surrogate pairs of high/low chars form
+     * characters in the output encoding.
+     */
+    EncodingInfo m_encodingInfo = new EncodingInfo(null,null);
+
+    /**
+     * Method reference to the sun.io.CharToByteConverter#canConvert method
+     * for this encoding.  Invalid if m_charToByteConverter is null.
+     */
+    java.lang.reflect.Method m_canConvertMeth;
+
+
+
+    /**
+     * Boolean that tells if we already tried to get the converter.
+     */
+    boolean m_triedToGetConverter = false;
+
+
+    /**
+     * Opaque reference to the sun.io.CharToByteConverter for this
+     * encoding.
+     */
+    Object m_charToByteConverter = null;
+
+
+    /**
+     * Stack to keep track of whether or not we need to
+     * preserve whitespace.
+     *
+     * Used to push/pop values used for the field m_ispreserve, but
+     * m_ispreserve is only relevant if m_doIndent is true.
+     * If m_doIndent is false this field has no impact.
+     *
+     */
+    protected BoolStack m_preserves = new BoolStack();
+
+    /**
+     * State flag to tell if preservation of whitespace
+     * is important.
+     *
+     * Used only in shouldIndent() but only if m_doIndent is true.
+     * If m_doIndent is false this flag has no impact.
+     *
+     */
+    protected boolean m_ispreserve = false;
+
+    /**
+     * State flag that tells if the previous node processed
+     * was text, so we can tell if we should preserve whitespace.
+     *
+     * Used in endDocument() and shouldIndent() but
+     * only if m_doIndent is true.
+     * If m_doIndent is false this flag has no impact.
+     */
+    protected boolean m_isprevtext = false;
+
+    /**
+     * The maximum character size before we have to resort
+     * to escaping.
+     */
+    protected int m_maxCharacter = Encodings.getLastPrintable();
+
+
+    /**
+     * The system line separator for writing out line breaks.
+     * The default value is from the system property,
+     * but this value can be set through the xsl:output
+     * extension attribute xalan:line-separator.
+     */
+    protected char[] m_lineSep =
+        System.getProperty("line.separator").toCharArray();
+
+    /**
+     * True if the the system line separator is to be used.
+     */
+    protected boolean m_lineSepUse = true;
+
+    /**
+     * The length of the line seperator, since the write is done
+     * one character at a time.
+     */
+    protected int m_lineSepLen = m_lineSep.length;
+
+    /**
+     * Map that tells which characters should have special treatment, and it
+     *  provides character to entity name lookup.
+     */
+    protected CharInfo m_charInfo;
+
+    /** True if we control the buffer, and we should flush the output on endDocument. */
+    boolean m_shouldFlush = true;
+
+    /**
+     * Add space before '/>' for XHTML.
+     */
+    protected boolean m_spaceBeforeClose = false;
+
+    /**
+     * Flag to signal that a newline should be added.
+     *
+     * Used only in indent() which is called only if m_doIndent is true.
+     * If m_doIndent is false this flag has no impact.
+     */
+    boolean m_startNewLine;
+
+    /**
+     * Tells if we're in an internal document type subset.
+     */
+    protected boolean m_inDoctype = false;
+
+    /**
+       * Flag to quickly tell if the encoding is UTF8.
+       */
+    boolean m_isUTF8 = false;
+
+    /** The xsl:output properties. */
+    protected Properties m_format;
+
+    /**
+     * remembers if we are in between the startCDATA() and endCDATA() callbacks
+     */
+    protected boolean m_cdataStartCalled = false;
+
+    /**
+     * If this flag is true DTD entity references are not left as-is,
+     * which is exiting older behavior.
+     */
+    private boolean m_expandDTDEntities = true;
+
+
+    /**
+     * Default constructor
+     */
+    public ToStream()
+    {
+    }
+
+    /**
+     * This helper method to writes out "]]>" when closing a CDATA section.
+     *
+     * @throws org.xml.sax.SAXException
+     */
+    protected void closeCDATA() throws org.xml.sax.SAXException
+    {
+        try
+        {
+            m_writer.write(CDATA_DELIMITER_CLOSE);
+            // write out a CDATA section closing "]]>"
+            m_cdataTagOpen = false; // Remember that we have done so.
+        }
+        catch (IOException e)
+        {
+            throw new SAXException(e);
+        }
+    }
+
+    /**
+     * Serializes the DOM node. Throws an exception only if an I/O
+     * exception occured while serializing.
+     *
+     * @param node Node to serialize.
+     * @throws IOException An I/O exception occured while serializing
+     */
+    public void serialize(Node node) throws IOException
+    {
+
+        try
+        {
+            TreeWalker walker =
+                new TreeWalker(this);
+
+            walker.traverse(node);
+        }
+        catch (org.xml.sax.SAXException se)
+        {
+            throw new WrappedRuntimeException(se);
+        }
+    }
+
+    /**
+     * Return true if the character is the high member of a surrogate pair.
+     *
+     * NEEDSDOC @param c
+     *
+     * NEEDSDOC ($objectName$) @return
+     */
+    static final boolean isUTF16Surrogate(char c)
+    {
+        return (c & 0xFC00) == 0xD800;
+    }
+
+    /**
+     * Taken from XSLTC
+     */
+    private boolean m_escaping = true;
+
+    /**
+     * Flush the formatter's result stream.
+     *
+     * @throws org.xml.sax.SAXException
+     */
+    protected final void flushWriter() throws org.xml.sax.SAXException
+    {
+        final java.io.Writer writer = m_writer;
+        if (null != writer)
+        {
+            try
+            {
+                if (writer instanceof WriterToUTF8Buffered)
+                {
+                    if (m_shouldFlush)
+                         ((WriterToUTF8Buffered) writer).flush();
+                    else
+                         ((WriterToUTF8Buffered) writer).flushBuffer();
+                }
+                if (writer instanceof WriterToASCI)
+                {
+                    if (m_shouldFlush)
+                        writer.flush();
+                }
+                else
+                {
+                    // Flush always.
+                    // Not a great thing if the writer was created
+                    // by this class, but don't have a choice.
+                    writer.flush();
+                }
+            }
+            catch (IOException ioe)
+            {
+                throw new org.xml.sax.SAXException(ioe);
+            }
+        }
+    }
+
+    /**
+     * Get the output stream where the events will be serialized to.
+     *
+     * @return reference to the result stream, or null of only a writer was
+     * set.
+     */
+    public OutputStream getOutputStream()
+    {
+
+        if (m_writer instanceof WriterToUTF8Buffered)
+            return ((WriterToUTF8Buffered) m_writer).getOutputStream();
+        if (m_writer instanceof WriterToASCI)
+            return ((WriterToASCI) m_writer).getOutputStream();
+        else
+            return null;
+    }
+
+    // Implement DeclHandler
+
+    /**
+     *   Report an element type declaration.
+     *
+     *   <p>The content model will consist of the string "EMPTY", the
+     *   string "ANY", or a parenthesised group, optionally followed
+     *   by an occurrence indicator.  The model will be normalized so
+     *   that all whitespace is removed,and will include the enclosing
+     *   parentheses.</p>
+     *
+     *   @param name The element type name.
+     *   @param model The content model as a normalized string.
+     *   @exception SAXException The application may raise an exception.
+     */
+    public void elementDecl(String name, String model) throws SAXException
+    {
+        // Do not inline external DTD
+        if (m_inExternalDTD)
+            return;
+        try
+        {
+            final java.io.Writer writer = m_writer;
+            DTDprolog();
+
+            writer.write("<!ELEMENT ");
+            writer.write(name);
+            writer.write(' ');
+            writer.write(model);
+            writer.write('>');
+            writer.write(m_lineSep, 0, m_lineSepLen);
+        }
+        catch (IOException e)
+        {
+            throw new SAXException(e);
+        }
+
+    }
+
+    /**
+     * Report an internal entity declaration.
+     *
+     * <p>Only the effective (first) declaration for each entity
+     * will be reported.</p>
+     *
+     * @param name The name of the entity.  If it is a parameter
+     *        entity, the name will begin with '%'.
+     * @param value The replacement text of the entity.
+     * @exception SAXException The application may raise an exception.
+     * @see #externalEntityDecl
+     * @see org.xml.sax.DTDHandler#unparsedEntityDecl
+     */
+    public void internalEntityDecl(String name, String value)
+        throws SAXException
+    {
+        // Do not inline external DTD
+        if (m_inExternalDTD)
+            return;
+        try
+        {
+            DTDprolog();
+            outputEntityDecl(name, value);
+        }
+        catch (IOException e)
+        {
+            throw new SAXException(e);
+        }
+
+    }
+
+    /**
+     * Output the doc type declaration.
+     *
+     * @param name non-null reference to document type name.
+     * NEEDSDOC @param value
+     *
+     * @throws org.xml.sax.SAXException
+     */
+    void outputEntityDecl(String name, String value) throws IOException
+    {
+        final java.io.Writer writer = m_writer;
+        writer.write("<!ENTITY ");
+        writer.write(name);
+        writer.write(" \"");
+        writer.write(value);
+        writer.write("\">");
+        writer.write(m_lineSep, 0, m_lineSepLen);
+    }
+
+    /**
+     * Output a system-dependent line break.
+     *
+     * @throws org.xml.sax.SAXException
+     */
+    protected final void outputLineSep() throws IOException
+    {
+
+        m_writer.write(m_lineSep, 0, m_lineSepLen);
+    }
+
+    /**
+     * Specifies an output format for this serializer. It the
+     * serializer has already been associated with an output format,
+     * it will switch to the new format. This method should not be
+     * called while the serializer is in the process of serializing
+     * a document.
+     *
+     * @param format The output format to use
+     */
+    public void setOutputFormat(Properties format)
+    {
+
+        boolean shouldFlush = m_shouldFlush;
+
+        init(m_writer, format, false, false);
+
+        m_shouldFlush = shouldFlush;
+    }
+
+    /**
+     * Initialize the serializer with the specified writer and output format.
+     * Must be called before calling any of the serialize methods.
+     * This method can be called multiple times and the xsl:output properties
+     * passed in the 'format' parameter are accumulated across calls.
+     *
+     * @param writer The writer to use
+     * @param format The output format
+     * @param shouldFlush True if the writer should be flushed at EndDocument.
+     */
+    private synchronized void init(
+        Writer writer,
+        Properties format,
+        boolean defaultProperties,
+        boolean shouldFlush)
+    {
+
+        m_shouldFlush = shouldFlush;
+
+
+        // if we are tracing events we need to trace what
+        // characters are written to the output writer.
+        if (m_tracer != null
+         && !(writer instanceof SerializerTraceWriter)  )
+            m_writer = new SerializerTraceWriter(writer, m_tracer);
+        else
+            m_writer = writer;
+
+
+        m_format = format;
+        //        m_cdataSectionNames =
+        //            OutputProperties.getQNameProperties(
+        //                OutputKeys.CDATA_SECTION_ELEMENTS,
+        //                format);
+        setCdataSectionElements(OutputKeys.CDATA_SECTION_ELEMENTS, format);
+
+        setIndentAmount(
+            OutputPropertyUtils.getIntProperty(
+                OutputPropertiesFactory.S_KEY_INDENT_AMOUNT,
+                format));
+        setIndent(
+            OutputPropertyUtils.getBooleanProperty(OutputKeys.INDENT, format));
+
+        {
+            String sep =
+                    format.getProperty(OutputPropertiesFactory.S_KEY_LINE_SEPARATOR);
+            if (sep != null) {
+                m_lineSep = sep.toCharArray();
+                m_lineSepLen = sep.length();
+            }
+        }
+
+        boolean shouldNotWriteXMLHeader =
+            OutputPropertyUtils.getBooleanProperty(
+                OutputKeys.OMIT_XML_DECLARATION,
+                format);
+        setOmitXMLDeclaration(shouldNotWriteXMLHeader);
+        setDoctypeSystem(format.getProperty(OutputKeys.DOCTYPE_SYSTEM));
+        String doctypePublic = format.getProperty(OutputKeys.DOCTYPE_PUBLIC);
+        setDoctypePublic(doctypePublic);
+
+        // if standalone was explicitly specified
+        if (format.get(OutputKeys.STANDALONE) != null)
+        {
+            String val = format.getProperty(OutputKeys.STANDALONE);
+            if (defaultProperties)
+                setStandaloneInternal(val);
+            else
+                setStandalone(val);
+        }
+
+        setMediaType(format.getProperty(OutputKeys.MEDIA_TYPE));
+
+        if (null != doctypePublic)
+        {
+            if (doctypePublic.startsWith("-//W3C//DTD XHTML"))
+                m_spaceBeforeClose = true;
+        }
+
+        /*
+         * This code is added for XML 1.1 Version output.
+         */
+        String version = getVersion();
+        if (null == version)
+        {
+            version = format.getProperty(OutputKeys.VERSION);
+            setVersion(version);
+        }
+
+        // initCharsMap();
+        String encoding = getEncoding();
+        if (null == encoding)
+        {
+            encoding =
+                Encodings.getMimeEncoding(
+                    format.getProperty(OutputKeys.ENCODING));
+            setEncoding(encoding);
+        }
+
+        m_isUTF8 = encoding.equals(Encodings.DEFAULT_MIME_ENCODING);
+
+        // Access this only from the Hashtable level... we don't want to
+        // get default properties.
+        String entitiesFileName =
+            (String) format.get(OutputPropertiesFactory.S_KEY_ENTITIES);
+
+        if (null != entitiesFileName)
+        {
+
+            String method =
+                (String) format.get(OutputKeys.METHOD);
+
+            m_charInfo = CharInfo.getCharInfo(entitiesFileName, method);
+        }
+
+    }
+
+    /**
+     * Initialize the serializer with the specified writer and output format.
+     * Must be called before calling any of the serialize methods.
+     *
+     * @param writer The writer to use
+     * @param format The output format
+     */
+    private synchronized void init(Writer writer, Properties format)
+    {
+        init(writer, format, false, false);
+    }
+    /**
+     * Initialize the serializer with the specified output stream and output
+     * format. Must be called before calling any of the serialize methods.
+     *
+     * @param output The output stream to use
+     * @param format The output format
+     * @param defaultProperties true if the properties are the default
+     * properties
+     *
+     * @throws UnsupportedEncodingException The encoding specified   in the
+     * output format is not supported
+     */
+    protected synchronized void init(
+        OutputStream output,
+        Properties format,
+        boolean defaultProperties)
+        throws UnsupportedEncodingException
+    {
+
+        String encoding = getEncoding();
+        if (encoding == null)
+        {
+            // if not already set then get it from the properties
+            encoding =
+                Encodings.getMimeEncoding(
+                    format.getProperty(OutputKeys.ENCODING));
+            setEncoding(encoding);
+        }
+
+        if (encoding.equalsIgnoreCase("UTF-8"))
+        {
+            m_isUTF8 = true;
+            //            if (output instanceof java.io.BufferedOutputStream)
+            //            {
+            //                init(new WriterToUTF8(output), format, defaultProperties, true);
+            //            }
+            //            else if (output instanceof java.io.FileOutputStream)
+            //            {
+            //                init(new WriterToUTF8Buffered(output), format, defaultProperties, true);
+            //            }
+            //            else
+            //            {
+            //                // Not sure what to do in this case.  I'm going to be conservative
+            //                // and not buffer.
+            //                init(new WriterToUTF8(output), format, defaultProperties, true);
+            //            }
+
+
+                init(
+                    new WriterToUTF8Buffered(output),
+                    format,
+                    defaultProperties,
+                    true);
+
+
+        }
+        else if (
+            encoding.equals("WINDOWS-1250")
+                || encoding.equals("US-ASCII")
+                || encoding.equals("ASCII"))
+        {
+            init(new WriterToASCI(output), format, defaultProperties, true);
+        }
+        else
+        {
+            Writer osw;
+
+            try
+            {
+                osw = Encodings.getWriter(output, encoding);
+            }
+            catch (UnsupportedEncodingException uee)
+            {
+                System.out.println(
+                    "Warning: encoding \""
+                        + encoding
+                        + "\" not supported"
+                        + ", using "
+                        + Encodings.DEFAULT_MIME_ENCODING);
+
+                encoding = Encodings.DEFAULT_MIME_ENCODING;
+                setEncoding(encoding);
+                osw = Encodings.getWriter(output, encoding);
+            }
+
+            init(osw, format, defaultProperties, true);
+        }
+
+    }
+
+    /**
+     * Returns the output format for this serializer.
+     *
+     * @return The output format in use
+     */
+    public Properties getOutputFormat()
+    {
+        return m_format;
+    }
+
+    /**
+     * Specifies a writer to which the document should be serialized.
+     * This method should not be called while the serializer is in
+     * the process of serializing a document.
+     *
+     * @param writer The output writer stream
+     */
+    public void setWriter(Writer writer)
+    {
+        // if we are tracing events we need to trace what
+        // characters are written to the output writer.
+        if (m_tracer != null
+         && !(writer instanceof SerializerTraceWriter)  )
+            m_writer = new SerializerTraceWriter(writer, m_tracer);
+        else
+            m_writer = writer;
+    }
+
+    /**
+     * Set if the operating systems end-of-line line separator should
+     * be used when serializing.  If set false NL character
+     * (decimal 10) is left alone, otherwise the new-line will be replaced on
+     * output with the systems line separator. For example on UNIX this is
+     * NL, while on Windows it is two characters, CR NL, where CR is the
+     * carriage-return (decimal 13).
+     *
+     * @param use_sytem_line_break True if an input NL is replaced with the
+     * operating systems end-of-line separator.
+     * @return The previously set value of the serializer.
+     */
+    public boolean setLineSepUse(boolean use_sytem_line_break)
+    {
+        boolean oldValue = m_lineSepUse;
+        m_lineSepUse = use_sytem_line_break;
+        return oldValue;
+    }
+
+    /**
+     * Specifies an output stream to which the document should be
+     * serialized. This method should not be called while the
+     * serializer is in the process of serializing a document.
+     * <p>
+     * The encoding specified in the output properties is used, or
+     * if no encoding was specified, the default for the selected
+     * output method.
+     *
+     * @param output The output stream
+     */
+    public void setOutputStream(OutputStream output)
+    {
+
+        try
+        {
+            Properties format;
+            if (null == m_format)
+                format =
+                    OutputPropertiesFactory.getDefaultMethodProperties(
+                        Method.XML);
+            else
+                format = m_format;
+            init(output, format, true);
+        }
+        catch (UnsupportedEncodingException uee)
+        {
+
+            // Should have been warned in init, I guess...
+        }
+    }
+
+    /**
+     * @see SerializationHandler#setEscaping(boolean)
+     */
+    public boolean setEscaping(boolean escape)
+    {
+        final boolean temp = m_escaping;
+        m_escaping = escape;
+        return temp;
+
+    }
+
+
+    /**
+     * Might print a newline character and the indentation amount
+     * of the given depth.
+     *
+     * @param depth the indentation depth (element nesting depth)
+     *
+     * @throws org.xml.sax.SAXException if an error occurs during writing.
+     */
+    protected void indent(int depth) throws IOException
+    {
+
+        if (m_startNewLine)
+            outputLineSep();
+        /* For m_indentAmount > 0 this extra test might be slower
+         * but Xalan's default value is 0, so this extra test
+         * will run faster in that situation.
+         */
+        if (m_indentAmount > 0)
+            printSpace(depth * m_indentAmount);
+
+    }
+
+    /**
+     * Indent at the current element nesting depth.
+     * @throws IOException
+     */
+    protected void indent() throws IOException
+    {
+        indent(m_elemContext.m_currentElemDepth);
+    }
+    /**
+     * Prints <var>n</var> spaces.
+     * @param n         Number of spaces to print.
+     *
+     * @throws org.xml.sax.SAXException if an error occurs when writing.
+     */
+    private void printSpace(int n) throws IOException
+    {
+        final java.io.Writer writer = m_writer;
+        for (int i = 0; i < n; i++)
+        {
+            writer.write(' ');
+        }
+
+    }
+
+    /**
+     * Report an attribute type declaration.
+     *
+     * <p>Only the effective (first) declaration for an attribute will
+     * be reported.  The type will be one of the strings "CDATA",
+     * "ID", "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY",
+     * "ENTITIES", or "NOTATION", or a parenthesized token group with
+     * the separator "|" and all whitespace removed.</p>
+     *
+     * @param eName The name of the associated element.
+     * @param aName The name of the attribute.
+     * @param type A string representing the attribute type.
+     * @param valueDefault A string representing the attribute default
+     *        ("#IMPLIED", "#REQUIRED", or "#FIXED") or null if
+     *        none of these applies.
+     * @param value A string representing the attribute's default value,
+     *        or null if there is none.
+     * @exception SAXException The application may raise an exception.
+     */
+    public void attributeDecl(
+        String eName,
+        String aName,
+        String type,
+        String valueDefault,
+        String value)
+        throws SAXException
+    {
+        // Do not inline external DTD
+        if (m_inExternalDTD)
+            return;
+        try
+        {
+            final java.io.Writer writer = m_writer;
+            DTDprolog();
+
+            writer.write("<!ATTLIST ");
+            writer.write(eName);
+            writer.write(' ');
+
+            writer.write(aName);
+            writer.write(' ');
+            writer.write(type);
+            if (valueDefault != null)
+            {
+                writer.write(' ');
+                writer.write(valueDefault);
+            }
+
+            //writer.write(" ");
+            //writer.write(value);
+            writer.write('>');
+            writer.write(m_lineSep, 0, m_lineSepLen);
+        }
+        catch (IOException e)
+        {
+            throw new SAXException(e);
+        }
+    }
+
+    /**
+     * Get the character stream where the events will be serialized to.
+     *
+     * @return Reference to the result Writer, or null.
+     */
+    public Writer getWriter()
+    {
+        return m_writer;
+    }
+
+    /**
+     * Report a parsed external entity declaration.
+     *
+     * <p>Only the effective (first) declaration for each entity
+     * will be reported.</p>
+     *
+     * @param name The name of the entity.  If it is a parameter
+     *        entity, the name will begin with '%'.
+     * @param publicId The declared public identifier of the entity, or
+     *        null if none was declared.
+     * @param systemId The declared system identifier of the entity.
+     * @exception SAXException The application may raise an exception.
+     * @see #internalEntityDecl
+     * @see org.xml.sax.DTDHandler#unparsedEntityDecl
+     */
+    public void externalEntityDecl(
+        String name,
+        String publicId,
+        String systemId)
+        throws SAXException
+    {
+        try {
+            DTDprolog();
+
+            m_writer.write("<!ENTITY ");
+            m_writer.write(name);
+            if (publicId != null) {
+                m_writer.write(" PUBLIC \"");
+                m_writer.write(publicId);
+
+            }
+            else {
+                m_writer.write(" SYSTEM \"");
+                m_writer.write(systemId);
+            }
+            m_writer.write("\" >");
+            m_writer.write(m_lineSep, 0, m_lineSepLen);
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+
+    }
+
+    /**
+     * Tell if this character can be written without escaping.
+     */
+    protected boolean escapingNotNeeded(char ch)
+    {
+        final boolean ret;
+        if (ch < 127)
+        {
+            // This is the old/fast code here, but is this
+            // correct for all encodings?
+            if (ch >= 0x20 || (0x0A == ch || 0x0D == ch || 0x09 == ch))
+                ret= true;
+            else
+                ret = false;
+        }
+        else {
+            ret = m_encodingInfo.isInEncoding(ch);
+        }
+        return ret;
+    }
+
+    /**
+     * Once a surrogate has been detected, write out the pair of
+     * characters if it is in the encoding, or if there is no
+     * encoding, otherwise write out an entity reference
+     * of the value of the unicode code point of the character
+     * represented by the high/low surrogate pair.
+     * <p>
+     * An exception is thrown if there is no low surrogate in the pair,
+     * because the array ends unexpectely, or if the low char is there
+     * but its value is such that it is not a low surrogate.
+     *
+     * @param c the first (high) part of the surrogate, which
+     * must be confirmed before calling this method.
+     * @param ch Character array.
+     * @param i position Where the surrogate was detected.
+     * @param end The end index of the significant characters.
+     * @return 0 if the pair of characters was written out as-is,
+     * the unicode code point of the character represented by
+     * the surrogate pair if an entity reference with that value
+     * was written out.
+     *
+     * @throws IOException
+     * @throws org.xml.sax.SAXException if invalid UTF-16 surrogate detected.
+     */
+    protected int writeUTF16Surrogate(char c, char ch[], int i, int end)
+        throws IOException
+    {
+        int codePoint = 0;
+        if (i + 1 >= end)
+        {
+            throw new IOException(
+                Utils.messages.createMessage(
+                    MsgKey.ER_INVALID_UTF16_SURROGATE,
+                    new Object[] { Integer.toHexString((int) c)}));
+        }
+
+        final char high = c;
+        final char low = ch[i+1];
+        if (!Encodings.isLowUTF16Surrogate(low)) {
+            throw new IOException(
+                Utils.messages.createMessage(
+                    MsgKey.ER_INVALID_UTF16_SURROGATE,
+                    new Object[] {
+                        Integer.toHexString((int) c)
+                            + " "
+                            + Integer.toHexString(low)}));
+        }
+
+        final java.io.Writer writer = m_writer;
+
+        // If we make it to here we have a valid high, low surrogate pair
+        if (m_encodingInfo.isInEncoding(c,low)) {
+            // If the character formed by the surrogate pair
+            // is in the encoding, so just write it out
+            writer.write(ch,i,2);
+        }
+        else {
+            // Don't know what to do with this char, it is
+            // not in the encoding and not a high char in
+            // a surrogate pair, so write out as an entity ref
+            final String encoding = getEncoding();
+            if (encoding != null) {
+                /* The output encoding is known,
+                 * so somthing is wrong.
+                  */
+                codePoint = Encodings.toCodePoint(high, low);
+                // not in the encoding, so write out a character reference
+                writer.write('&');
+                writer.write('#');
+                writer.write(Integer.toString(codePoint));
+                writer.write(';');
+            } else {
+                /* The output encoding is not known,
+                 * so just write it out as-is.
+                 */
+                writer.write(ch, i, 2);
+            }
+        }
+        // non-zero only if character reference was written out.
+        return codePoint;
+    }
+
+    /**
+     * Handle one of the default entities, return false if it
+     * is not a default entity.
+     *
+     * @param ch character to be escaped.
+     * @param i index into character array.
+     * @param chars non-null reference to character array.
+     * @param len length of chars.
+     * @param fromTextNode true if the characters being processed
+     * are from a text node, false if they are from an attribute value
+     * @param escLF true if the linefeed should be escaped.
+     *
+     * @return i+1 if the character was written, else i.
+     *
+     * @throws java.io.IOException
+     */
+    protected int accumDefaultEntity(
+        java.io.Writer writer,
+        char ch,
+        int i,
+        char[] chars,
+        int len,
+        boolean fromTextNode,
+        boolean escLF)
+        throws IOException
+    {
+
+        if (!escLF && CharInfo.S_LINEFEED == ch)
+        {
+            writer.write(m_lineSep, 0, m_lineSepLen);
+        }
+        else
+        {
+            // if this is text node character and a special one of those,
+            // or if this is a character from attribute value and a special one of those
+            if ((fromTextNode && m_charInfo.isSpecialTextChar(ch)) || (!fromTextNode && m_charInfo.isSpecialAttrChar(ch)))
+            {
+                String outputStringForChar = m_charInfo.getOutputStringForChar(ch);
+
+                if (null != outputStringForChar)
+                {
+                    writer.write(outputStringForChar);
+                }
+                else
+                    return i;
+            }
+            else
+                return i;
+        }
+
+        return i + 1;
+
+    }
+    /**
+     * Normalize the characters, but don't escape.
+     *
+     * @param ch The characters from the XML document.
+     * @param start The start position in the array.
+     * @param length The number of characters to read from the array.
+     * @param isCData true if a CDATA block should be built around the characters.
+     * @param useSystemLineSeparator true if the operating systems
+     * end-of-line separator should be output rather than a new-line character.
+     *
+     * @throws IOException
+     * @throws org.xml.sax.SAXException
+     */
+    void writeNormalizedChars(
+        char ch[],
+        int start,
+        int length,
+        boolean isCData,
+        boolean useSystemLineSeparator)
+        throws IOException, org.xml.sax.SAXException
+    {
+        final java.io.Writer writer = m_writer;
+        int end = start + length;
+
+        for (int i = start; i < end; i++)
+        {
+            char c = ch[i];
+
+            if (CharInfo.S_LINEFEED == c && useSystemLineSeparator)
+            {
+                writer.write(m_lineSep, 0, m_lineSepLen);
+            }
+            else if (isCData && (!escapingNotNeeded(c)))
+            {
+                //                if (i != 0)
+                if (m_cdataTagOpen)
+                    closeCDATA();
+
+                // This needs to go into a function...
+                if (Encodings.isHighUTF16Surrogate(c))
+                {
+                    writeUTF16Surrogate(c, ch, i, end);
+                    i++ ; // process two input characters
+                }
+                else
+                {
+                    writer.write("&#");
+
+                    String intStr = Integer.toString((int) c);
+
+                    writer.write(intStr);
+                    writer.write(';');
+                }
+
+                //                if ((i != 0) && (i < (end - 1)))
+                //                if (!m_cdataTagOpen && (i < (end - 1)))
+                //                {
+                //                    writer.write(CDATA_DELIMITER_OPEN);
+                //                    m_cdataTagOpen = true;
+                //                }
+            }
+            else if (
+                isCData
+                    && ((i < (end - 2))
+                        && (']' == c)
+                        && (']' == ch[i + 1])
+                        && ('>' == ch[i + 2])))
+            {
+                writer.write(CDATA_CONTINUE);
+
+                i += 2;
+            }
+            else
+            {
+                if (escapingNotNeeded(c))
+                {
+                    if (isCData && !m_cdataTagOpen)
+                    {
+                        writer.write(CDATA_DELIMITER_OPEN);
+                        m_cdataTagOpen = true;
+                    }
+                    writer.write(c);
+                }
+
+                // This needs to go into a function...
+                else if (Encodings.isHighUTF16Surrogate(c))
+                {
+                    if (m_cdataTagOpen)
+                        closeCDATA();
+                    writeUTF16Surrogate(c, ch, i, end);
+                    i++; // process two input characters
+                }
+                else
+                {
+                    if (m_cdataTagOpen)
+                        closeCDATA();
+                    writer.write("&#");
+
+                    String intStr = Integer.toString((int) c);
+
+                    writer.write(intStr);
+                    writer.write(';');
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Ends an un-escaping section.
+     *
+     * @see #startNonEscaping
+     *
+     * @throws org.xml.sax.SAXException
+     */
+    public void endNonEscaping() throws org.xml.sax.SAXException
+    {
+        m_disableOutputEscapingStates.pop();
+    }
+
+    /**
+     * Starts an un-escaping section. All characters printed within an un-
+     * escaping section are printed as is, without escaping special characters
+     * into entity references. Only XML and HTML serializers need to support
+     * this method.
+     * <p> The contents of the un-escaping section will be delivered through the
+     * regular <tt>characters</tt> event.
+     *
+     * @throws org.xml.sax.SAXException
+     */
+    public void startNonEscaping() throws org.xml.sax.SAXException
+    {
+        m_disableOutputEscapingStates.push(true);
+    }
+
+    /**
+     * Receive notification of cdata.
+     *
+     * <p>The Parser will call this method to report each chunk of
+     * character data.  SAX parsers may return all contiguous character
+     * data in a single chunk, or they may split it into several
+     * chunks; however, all of the characters in any single event
+     * must come from the same external entity, so that the Locator
+     * provides useful information.</p>
+     *
+     * <p>The application must not attempt to read from the array
+     * outside of the specified range.</p>
+     *
+     * <p>Note that some parsers will report whitespace using the
+     * ignorableWhitespace() method rather than this one (validating
+     * parsers must do so).</p>
+     *
+     * @param ch The characters from the XML document.
+     * @param start The start position in the array.
+     * @param length The number of characters to read from the array.
+     * @throws org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #ignorableWhitespace
+     * @see org.xml.sax.Locator
+     *
+     * @throws org.xml.sax.SAXException
+     */
+    protected void cdata(char ch[], int start, final int length)
+        throws org.xml.sax.SAXException
+    {
+
+        try
+        {
+            final int old_start = start;
+            if (m_elemContext.m_startTagOpen)
+            {
+                closeStartTag();
+                m_elemContext.m_startTagOpen = false;
+            }
+            m_ispreserve = true;
+
+            if (shouldIndent())
+                indent();
+
+            boolean writeCDataBrackets =
+                (((length >= 1) && escapingNotNeeded(ch[start])));
+
+            /* Write out the CDATA opening delimiter only if
+             * we are supposed to, and if we are not already in
+             * the middle of a CDATA section
+             */
+            if (writeCDataBrackets && !m_cdataTagOpen)
+            {
+                m_writer.write(CDATA_DELIMITER_OPEN);
+                m_cdataTagOpen = true;
+            }
+
+            // writer.write(ch, start, length);
+            if (isEscapingDisabled())
+            {
+                charactersRaw(ch, start, length);
+            }
+            else
+                writeNormalizedChars(ch, start, length, true, m_lineSepUse);
+
+            /* used to always write out CDATA closing delimiter here,
+             * but now we delay, so that we can merge CDATA sections on output.
+             * need to write closing delimiter later
+             */
+            if (writeCDataBrackets)
+            {
+                /* if the CDATA section ends with ] don't leave it open
+                 * as there is a chance that an adjacent CDATA sections
+                 * starts with ]>.
+                 * We don't want to merge ]] with > , or ] with ]>
+                 */
+                if (ch[start + length - 1] == ']')
+                    closeCDATA();
+            }
+
+            // time to fire off CDATA event
+            if (m_tracer != null)
+                super.fireCDATAEvent(ch, old_start, length);
+        }
+        catch (IOException ioe)
+        {
+            throw new org.xml.sax.SAXException(
+                Utils.messages.createMessage(
+                    MsgKey.ER_OIERROR,
+                    null),
+                ioe);
+            //"IO error", ioe);
+        }
+    }
+
+    /**
+     * Tell if the character escaping should be disabled for the current state.
+     *
+     * @return true if the character escaping should be disabled.
+     */
+    private boolean isEscapingDisabled()
+    {
+        return m_disableOutputEscapingStates.peekOrFalse();
+    }
+
+    /**
+     * If available, when the disable-output-escaping attribute is used,
+     * output raw text without escaping.
+     *
+     * @param ch The characters from the XML document.
+     * @param start The start position in the array.
+     * @param length The number of characters to read from the array.
+     *
+     * @throws org.xml.sax.SAXException
+     */
+    protected void charactersRaw(char ch[], int start, int length)
+        throws org.xml.sax.SAXException
+    {
+
+        if (m_inEntityRef)
+            return;
+        try
+        {
+            if (m_elemContext.m_startTagOpen)
+            {
+                closeStartTag();
+                m_elemContext.m_startTagOpen = false;
+            }
+
+            m_ispreserve = true;
+
+            m_writer.write(ch, start, length);
+        }
+        catch (IOException e)
+        {
+            throw new SAXException(e);
+        }
+
+    }
+
+    /**
+     * Receive notification of character data.
+     *
+     * <p>The Parser will call this method to report each chunk of
+     * character data.  SAX parsers may return all contiguous character
+     * data in a single chunk, or they may split it into several
+     * chunks; however, all of the characters in any single event
+     * must come from the same external entity, so that the Locator
+     * provides useful information.</p>
+     *
+     * <p>The application must not attempt to read from the array
+     * outside of the specified range.</p>
+     *
+     * <p>Note that some parsers will report whitespace using the
+     * ignorableWhitespace() method rather than this one (validating
+     * parsers must do so).</p>
+     *
+     * @param chars The characters from the XML document.
+     * @param start The start position in the array.
+     * @param length The number of characters to read from the array.
+     * @throws org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #ignorableWhitespace
+     * @see org.xml.sax.Locator
+     *
+     * @throws org.xml.sax.SAXException
+     */
+    public void characters(final char chars[], final int start, final int length)
+        throws org.xml.sax.SAXException
+    {
+        // It does not make sense to continue with rest of the method if the number of
+        // characters to read from array is 0.
+        // Section 7.6.1 of XSLT 1.0 (http://www.w3.org/TR/xslt#value-of) suggest no text node
+        // is created if string is empty.
+        if (length == 0 || (m_inEntityRef && !m_expandDTDEntities))
+            return;
+        if (m_elemContext.m_startTagOpen)
+        {
+            closeStartTag();
+            m_elemContext.m_startTagOpen = false;
+        }
+        else if (m_needToCallStartDocument)
+        {
+            startDocumentInternal();
+        }
+
+        if (m_cdataStartCalled || m_elemContext.m_isCdataSection)
+        {
+            /* either due to startCDATA() being called or due to
+             * cdata-section-elements atribute, we need this as cdata
+             */
+            cdata(chars, start, length);
+
+            return;
+        }
+
+        if (m_cdataTagOpen)
+            closeCDATA();
+        // the check with _escaping is a bit of a hack for XLSTC
+
+        if (m_disableOutputEscapingStates.peekOrFalse() || (!m_escaping))
+        {
+            charactersRaw(chars, start, length);
+
+            // time to fire off characters generation event
+            if (m_tracer != null)
+                super.fireCharEvent(chars, start, length);
+
+            return;
+        }
+
+        if (m_elemContext.m_startTagOpen)
+        {
+            closeStartTag();
+            m_elemContext.m_startTagOpen = false;
+        }
+
+
+        try
+        {
+            int i;
+            char ch1;
+            int startClean;
+
+            // skip any leading whitspace
+            // don't go off the end and use a hand inlined version
+            // of isWhitespace(ch)
+            final int end = start + length;
+            int lastDirty = start - 1; // last character that needed processing
+            for (i = start;
+                ((i < end)
+                    && ((ch1 = chars[i]) == 0x20
+                        || (ch1 == 0xA && m_lineSepUse)
+                        || ch1 == 0xD
+                        || ch1 == 0x09));
+                i++)
+            {
+                /*
+                 * We are processing leading whitespace, but are doing the same
+                 * processing for dirty characters here as for non-whitespace.
+                 *
+                 */
+                if (!m_charInfo.isTextASCIIClean(ch1))
+                {
+                    lastDirty = processDirty(chars,end, i,ch1, lastDirty, true);
+                    i = lastDirty;
+                }
+            }
+            /* If there is some non-whitespace, mark that we may need
+             * to preserve this. This is only important if we have indentation on.
+             */
+            if (i < end)
+                m_ispreserve = true;
+
+
+//            int lengthClean;    // number of clean characters in a row
+//            final boolean[] isAsciiClean = m_charInfo.getASCIIClean();
+
+            final boolean isXML10 = XMLVERSION10.equals(getVersion());
+            // we've skipped the leading whitespace, now deal with the rest
+            for (; i < end; i++)
+            {
+                {
+                    // A tight loop to skip over common clean chars
+                    // This tight loop makes it easier for the JIT
+                    // to optimize.
+                    char ch2;
+                    while (i<end
+                            && ((ch2 = chars[i])<127)
+                            && m_charInfo.isTextASCIIClean(ch2))
+                            i++;
+                    if (i == end)
+                        break;
+                }
+
+                final char ch = chars[i];
+                /*  The check for isCharacterInC0orC1Ranger and
+                 *  isNELorLSEPCharacter has been added
+                 *  to support Control Characters in XML 1.1
+                 */
+                if (!isCharacterInC0orC1Range(ch) &&
+                    (isXML10 || !isNELorLSEPCharacter(ch)) &&
+                    (escapingNotNeeded(ch) && (!m_charInfo.isSpecialTextChar(ch)))
+                        || ('"' == ch))
+                {
+                    ; // a character needing no special processing
+                }
+                else
+                {
+                    lastDirty = processDirty(chars,end, i, ch, lastDirty, true);
+                    i = lastDirty;
+                }
+            }
+
+            // we've reached the end. Any clean characters at the
+            // end of the array than need to be written out?
+            startClean = lastDirty + 1;
+            if (i > startClean)
+            {
+                int lengthClean = i - startClean;
+                m_writer.write(chars, startClean, lengthClean);
+            }
+
+            // For indentation purposes, mark that we've just writen text out
+            m_isprevtext = true;
+        }
+        catch (IOException e)
+        {
+            throw new SAXException(e);
+        }
+
+        // time to fire off characters generation event
+        if (m_tracer != null)
+            super.fireCharEvent(chars, start, length);
+    }
+    /**
+     * This method checks if a given character is between C0 or C1 range
+     * of Control characters.
+     * This method is added to support Control Characters for XML 1.1
+     * If a given character is TAB (0x09), LF (0x0A) or CR (0x0D), this method
+     * return false. Since they are whitespace characters, no special processing is needed.
+     *
+     * @param ch
+     * @return boolean
+     */
+    private static boolean isCharacterInC0orC1Range(char ch)
+    {
+        if(ch == 0x09 || ch == 0x0A || ch == 0x0D)
+                return false;
+        else
+                return (ch >= 0x7F && ch <= 0x9F)|| (ch >= 0x01 && ch <= 0x1F);
+    }
+    /**
+     * This method checks if a given character either NEL (0x85) or LSEP (0x2028)
+     * These are new end of line charcters added in XML 1.1.  These characters must be
+     * written as Numeric Character References (NCR) in XML 1.1 output document.
+     *
+     * @param ch
+     * @return boolean
+     */
+    private static boolean isNELorLSEPCharacter(char ch)
+    {
+        return (ch == 0x85 || ch == 0x2028);
+    }
+    /**
+     * Process a dirty character and any preeceding clean characters
+     * that were not yet processed.
+     * @param chars array of characters being processed
+     * @param end one (1) beyond the last character
+     * in chars to be processed
+     * @param i the index of the dirty character
+     * @param ch the character in chars[i]
+     * @param lastDirty the last dirty character previous to i
+     * @param fromTextNode true if the characters being processed are
+     * from a text node, false if they are from an attribute value.
+     * @return the index of the last character processed
+     */
+    private int processDirty(
+        char[] chars,
+        int end,
+        int i,
+        char ch,
+        int lastDirty,
+        boolean fromTextNode) throws IOException
+    {
+        int startClean = lastDirty + 1;
+        // if we have some clean characters accumulated
+        // process them before the dirty one.
+        if (i > startClean)
+        {
+            int lengthClean = i - startClean;
+            m_writer.write(chars, startClean, lengthClean);
+        }
+
+        // process the "dirty" character
+        if (CharInfo.S_LINEFEED == ch && fromTextNode)
+        {
+            m_writer.write(m_lineSep, 0, m_lineSepLen);
+        }
+        else
+        {
+            startClean =
+                accumDefaultEscape(
+                    m_writer,
+                    (char)ch,
+                    i,
+                    chars,
+                    end,
+                    fromTextNode,
+                    false);
+            i = startClean - 1;
+        }
+        // Return the index of the last character that we just processed
+        // which is a dirty character.
+        return i;
+    }
+
+    /**
+     * Receive notification of character data.
+     *
+     * @param s The string of characters to process.
+     *
+     * @throws org.xml.sax.SAXException
+     */
+    public void characters(String s) throws org.xml.sax.SAXException
+    {
+        if (m_inEntityRef && !m_expandDTDEntities)
+            return;
+        final int length = s.length();
+        if (length > m_charsBuff.length)
+        {
+            m_charsBuff = new char[length * 2 + 1];
+        }
+        s.getChars(0, length, m_charsBuff, 0);
+        characters(m_charsBuff, 0, length);
+    }
+
+    /**
+     * Escape and writer.write a character.
+     *
+     * @param ch character to be escaped.
+     * @param i index into character array.
+     * @param chars non-null reference to character array.
+     * @param len length of chars.
+     * @param fromTextNode true if the characters being processed are
+     * from a text node, false if the characters being processed are from
+     * an attribute value.
+     * @param escLF true if the linefeed should be escaped.
+     *
+     * @return i+1 if a character was written, i+2 if two characters
+     * were written out, else return i.
+     *
+     * @throws org.xml.sax.SAXException
+     */
+    protected int accumDefaultEscape(
+        Writer writer,
+        char ch,
+        int i,
+        char[] chars,
+        int len,
+        boolean fromTextNode,
+        boolean escLF)
+        throws IOException
+    {
+
+        int pos = accumDefaultEntity(writer, ch, i, chars, len, fromTextNode, escLF);
+
+        if (i == pos)
+        {
+            if (Encodings.isHighUTF16Surrogate(ch))
+            {
+
+                // Should be the UTF-16 low surrogate of the hig/low pair.
+                char next;
+                // Unicode code point formed from the high/low pair.
+                int codePoint = 0;
+
+                if (i + 1 >= len)
+                {
+                    throw new IOException(
+                        Utils.messages.createMessage(
+                            MsgKey.ER_INVALID_UTF16_SURROGATE,
+                            new Object[] { Integer.toHexString(ch)}));
+                    //"Invalid UTF-16 surrogate detected: "
+
+                    //+Integer.toHexString(ch)+ " ?");
+                }
+                else
+                {
+                    next = chars[++i];
+
+                    if (!(Encodings.isLowUTF16Surrogate(next)))
+                        throw new IOException(
+                            Utils.messages.createMessage(
+                                MsgKey
+                                    .ER_INVALID_UTF16_SURROGATE,
+                                new Object[] {
+                                    Integer.toHexString(ch)
+                                        + " "
+                                        + Integer.toHexString(next)}));
+                    //"Invalid UTF-16 surrogate detected: "
+
+                    //+Integer.toHexString(ch)+" "+Integer.toHexString(next));
+                    codePoint = Encodings.toCodePoint(ch,next);
+                }
+
+                writer.write("&#");
+                writer.write(Integer.toString(codePoint));
+                writer.write(';');
+                pos += 2; // count the two characters that went into writing out this entity
+            }
+            else
+            {
+                /*  This if check is added to support control characters in XML 1.1.
+                 *  If a character is a Control Character within C0 and C1 range, it is desirable
+                 *  to write it out as Numeric Character Reference(NCR) regardless of XML Version
+                 *  being used for output document.
+                 */
+                if (isCharacterInC0orC1Range(ch) ||
+                        (XMLVERSION11.equals(getVersion()) && isNELorLSEPCharacter(ch)))
+                {
+                    writer.write("&#");
+                    writer.write(Integer.toString(ch));
+                    writer.write(';');
+                }
+                else if ((!escapingNotNeeded(ch) ||
+                    (  (fromTextNode && m_charInfo.isSpecialTextChar(ch))
+                     || (!fromTextNode && m_charInfo.isSpecialAttrChar(ch))))
+                && m_elemContext.m_currentElemDepth > 0)
+                {
+                    writer.write("&#");
+                    writer.write(Integer.toString(ch));
+                    writer.write(';');
+                }
+                else
+                {
+                    writer.write(ch);
+                }
+                pos++;  // count the single character that was processed
+            }
+
+        }
+        return pos;
+    }
+
+    /**
+     * Receive notification of the beginning of an element, although this is a
+     * SAX method additional namespace or attribute information can occur before
+     * or after this call, that is associated with this element.
+     *
+     *
+     * @param namespaceURI The Namespace URI, or the empty string if the
+     *        element has no Namespace URI or if Namespace
+     *        processing is not being performed.
+     * @param localName The local name (without prefix), or the
+     *        empty string if Namespace processing is not being
+     *        performed.
+     * @param name The element type name.
+     * @param atts The attributes attached to the element, if any.
+     * @throws org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see org.xml.sax.ContentHandler#startElement
+     * @see org.xml.sax.ContentHandler#endElement
+     * @see org.xml.sax.AttributeList
+     *
+     * @throws org.xml.sax.SAXException
+     */
+    public void startElement(
+        String namespaceURI,
+        String localName,
+        String name,
+        Attributes atts)
+        throws org.xml.sax.SAXException
+    {
+        if (m_inEntityRef)
+            return;
+
+        if (m_needToCallStartDocument)
+        {
+            startDocumentInternal();
+            m_needToCallStartDocument = false;
+        }
+        else if (m_cdataTagOpen)
+            closeCDATA();
+        try
+        {
+            if ((true == m_needToOutputDocTypeDecl)
+                && (null != getDoctypeSystem()))
+            {
+                outputDocTypeDecl(name, true);
+            }
+
+            m_needToOutputDocTypeDecl = false;
+
+            /* before we over-write the current elementLocalName etc.
+             * lets close out the old one (if we still need to)
+             */
+            if (m_elemContext.m_startTagOpen)
+            {
+                closeStartTag();
+                m_elemContext.m_startTagOpen = false;
+            }
+
+            if (namespaceURI != null)
+                ensurePrefixIsDeclared(namespaceURI, name);
+
+            m_ispreserve = false;
+
+            if (shouldIndent() && m_startNewLine)
+            {
+                indent();
+            }
+
+            m_startNewLine = true;
+
+            final java.io.Writer writer = m_writer;
+            writer.write('<');
+            writer.write(name);
+        }
+        catch (IOException e)
+        {
+            throw new SAXException(e);
+        }
+
+        // process the attributes now, because after this SAX call they might be gone
+        if (atts != null)
+            addAttributes(atts);
+
+        m_elemContext = m_elemContext.push(namespaceURI,localName,name);
+        m_isprevtext = false;
+
+        if (m_tracer != null){
+            firePseudoAttributes();
+        }
+
+    }
+
+    /**
+      * Receive notification of the beginning of an element, additional
+      * namespace or attribute information can occur before or after this call,
+      * that is associated with this element.
+      *
+      *
+      * @param elementNamespaceURI The Namespace URI, or the empty string if the
+      *        element has no Namespace URI or if Namespace
+      *        processing is not being performed.
+      * @param elementLocalName The local name (without prefix), or the
+      *        empty string if Namespace processing is not being
+      *        performed.
+      * @param elementName The element type name.
+      * @throws org.xml.sax.SAXException Any SAX exception, possibly
+      *            wrapping another exception.
+      * @see org.xml.sax.ContentHandler#startElement
+      * @see org.xml.sax.ContentHandler#endElement
+      * @see org.xml.sax.AttributeList
+      *
+      * @throws org.xml.sax.SAXException
+      */
+    public void startElement(
+        String elementNamespaceURI,
+        String elementLocalName,
+        String elementName)
+        throws SAXException
+    {
+        startElement(elementNamespaceURI, elementLocalName, elementName, null);
+    }
+
+    public void startElement(String elementName) throws SAXException
+    {
+        startElement(null, null, elementName, null);
+    }
+
+    /**
+     * Output the doc type declaration.
+     *
+     * @param name non-null reference to document type name.
+     * NEEDSDOC @param closeDecl
+     *
+     * @throws java.io.IOException
+     */
+    void outputDocTypeDecl(String name, boolean closeDecl) throws SAXException
+    {
+        if (m_cdataTagOpen)
+            closeCDATA();
+        try
+        {
+            final java.io.Writer writer = m_writer;
+            writer.write("<!DOCTYPE ");
+            writer.write(name);
+
+            String doctypePublic = getDoctypePublic();
+            if (null != doctypePublic)
+            {
+                writer.write(" PUBLIC \"");
+                writer.write(doctypePublic);
+                writer.write('\"');
+            }
+
+            String doctypeSystem = getDoctypeSystem();
+            if (null != doctypeSystem)
+            {
+                if (null == doctypePublic)
+                    writer.write(" SYSTEM \"");
+                else
+                    writer.write(" \"");
+
+                writer.write(doctypeSystem);
+
+                if (closeDecl)
+                {
+                    writer.write("\">");
+                    writer.write(m_lineSep, 0, m_lineSepLen);
+                    closeDecl = false; // done closing
+                }
+                else
+                    writer.write('\"');
+            }
+            boolean dothis = false;
+            if (dothis)
+            {
+                // at one point this code seemed right,
+                // but not anymore - Brian M.
+                if (closeDecl)
+                {
+                    writer.write('>');
+                    writer.write(m_lineSep, 0, m_lineSepLen);
+                }
+            }
+        }
+        catch (IOException e)
+        {
+            throw new SAXException(e);
+        }
+    }
+
+    /**
+     * Process the attributes, which means to write out the currently
+     * collected attributes to the writer. The attributes are not
+     * cleared by this method
+     *
+     * @param writer the writer to write processed attributes to.
+     * @param nAttrs the number of attributes in m_attributes
+     * to be processed
+     *
+     * @throws java.io.IOException
+     * @throws org.xml.sax.SAXException
+     */
+    public void processAttributes(java.io.Writer writer, int nAttrs) throws IOException, SAXException
+    {
+            /* real SAX attributes are not passed in, so process the
+             * attributes that were collected after the startElement call.
+             * _attribVector is a "cheap" list for Stream serializer output
+             * accumulated over a series of calls to attribute(name,value)
+             */
+            String encoding = getEncoding();
+            for (int i = 0; i < nAttrs; i++)
+            {
+                // elementAt is JDK 1.1.8
+                final String name = m_attributes.getQName(i);
+                final String value = m_attributes.getValue(i);
+                writer.write(' ');
+                writer.write(name);
+                writer.write("=\"");
+                writeAttrString(writer, value, encoding);
+                writer.write('\"');
+            }
+    }
+
+    /**
+     * Returns the specified <var>string</var> after substituting <VAR>specials</VAR>,
+     * and UTF-16 surrogates for chracter references <CODE>&amp;#xnn</CODE>.
+     *
+     * @param   string      String to convert to XML format.
+     * @param   encoding    CURRENTLY NOT IMPLEMENTED.
+     *
+     * @throws java.io.IOException
+     */
+    public void writeAttrString(
+        Writer writer,
+        String string,
+        String encoding)
+        throws IOException
+    {
+        final int len = string.length();
+        if (len > m_attrBuff.length)
+        {
+           m_attrBuff = new char[len*2 + 1];
+        }
+        string.getChars(0,len, m_attrBuff, 0);
+        final char[] stringChars = m_attrBuff;
+
+        for (int i = 0; i < len; )
+        {
+            char ch = stringChars[i];
+            if (escapingNotNeeded(ch) && (!m_charInfo.isSpecialAttrChar(ch)))
+            {
+                writer.write(ch);
+                i++;
+            }
+            else
+            { // I guess the parser doesn't normalize cr/lf in attributes. -sb
+//                if ((CharInfo.S_CARRIAGERETURN == ch)
+//                    && ((i + 1) < len)
+//                    && (CharInfo.S_LINEFEED == stringChars[i + 1]))
+//                {
+//                    i++;
+//                    ch = CharInfo.S_LINEFEED;
+//                }
+
+                i = accumDefaultEscape(writer, ch, i, stringChars, len, false, true);
+            }
+        }
+
+    }
+
+    /**
+     * Receive notification of the end of an element.
+     *
+     *
+     * @param namespaceURI The Namespace URI, or the empty string if the
+     *        element has no Namespace URI or if Namespace
+     *        processing is not being performed.
+     * @param localName The local name (without prefix), or the
+     *        empty string if Namespace processing is not being
+     *        performed.
+     * @param name The element type name
+     * @throws org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     *
+     * @throws org.xml.sax.SAXException
+     */
+    public void endElement(String namespaceURI, String localName, String name)
+        throws org.xml.sax.SAXException
+    {
+
+        if (m_inEntityRef)
+            return;
+
+        // namespaces declared at the current depth are no longer valid
+        // so get rid of them
+        m_prefixMap.popNamespaces(m_elemContext.m_currentElemDepth, null);
+
+        try
+        {
+            final java.io.Writer writer = m_writer;
+            if (m_elemContext.m_startTagOpen)
+            {
+                if (m_tracer != null)
+                    super.fireStartElem(m_elemContext.m_elementName);
+                int nAttrs = m_attributes.getLength();
+                if (nAttrs > 0)
+                {
+                    processAttributes(m_writer, nAttrs);
+                    // clear attributes object for re-use with next element
+                    m_attributes.clear();
+                }
+                if (m_spaceBeforeClose)
+                    writer.write(" />");
+                else
+                    writer.write("/>");
+                /* don't need to pop cdataSectionState because
+                 * this element ended so quickly that we didn't get
+                 * to push the state.
+                 */
+
+            }
+            else
+            {
+                if (m_cdataTagOpen)
+                    closeCDATA();
+
+                if (shouldIndent())
+                    indent(m_elemContext.m_currentElemDepth - 1);
+                writer.write('<');
+                writer.write('/');
+                writer.write(name);
+                writer.write('>');
+            }
+        }
+        catch (IOException e)
+        {
+            throw new SAXException(e);
+        }
+
+        if (!m_elemContext.m_startTagOpen && m_doIndent)
+        {
+            m_ispreserve = m_preserves.isEmpty() ? false : m_preserves.pop();
+        }
+
+        m_isprevtext = false;
+
+        // fire off the end element event
+        if (m_tracer != null)
+            super.fireEndElem(name);
+        m_elemContext = m_elemContext.m_prev;
+    }
+
+    /**
+     * Receive notification of the end of an element.
+     * @param name The element type name
+     * @throws org.xml.sax.SAXException Any SAX exception, possibly
+     *     wrapping another exception.
+     */
+    public void endElement(String name) throws org.xml.sax.SAXException
+    {
+        endElement(null, null, name);
+    }
+
+    /**
+     * Begin the scope of a prefix-URI Namespace mapping
+     * just before another element is about to start.
+     * This call will close any open tags so that the prefix mapping
+     * will not apply to the current element, but the up comming child.
+     *
+     * @see org.xml.sax.ContentHandler#startPrefixMapping
+     *
+     * @param prefix The Namespace prefix being declared.
+     * @param uri The Namespace URI the prefix is mapped to.
+     *
+     * @throws org.xml.sax.SAXException The client may throw
+     *            an exception during processing.
+     *
+     */
+    public void startPrefixMapping(String prefix, String uri)
+        throws org.xml.sax.SAXException
+    {
+        // the "true" causes the flush of any open tags
+        startPrefixMapping(prefix, uri, true);
+    }
+
+    /**
+     * Handle a prefix/uri mapping, which is associated with a startElement()
+     * that is soon to follow. Need to close any open start tag to make
+     * sure than any name space attributes due to this event are associated wih
+     * the up comming element, not the current one.
+     * @see ExtendedContentHandler#startPrefixMapping
+     *
+     * @param prefix The Namespace prefix being declared.
+     * @param uri The Namespace URI the prefix is mapped to.
+     * @param shouldFlush true if any open tags need to be closed first, this
+     * will impact which element the mapping applies to (open parent, or its up
+     * comming child)
+     * @return returns true if the call made a change to the current
+     * namespace information, false if it did not change anything, e.g. if the
+     * prefix/namespace mapping was already in scope from before.
+     *
+     * @throws org.xml.sax.SAXException The client may throw
+     *            an exception during processing.
+     *
+     *
+     */
+    public boolean startPrefixMapping(
+        String prefix,
+        String uri,
+        boolean shouldFlush)
+        throws org.xml.sax.SAXException
+    {
+
+        /* Remember the mapping, and at what depth it was declared
+         * This is one greater than the current depth because these
+         * mappings will apply to the next depth. This is in
+         * consideration that startElement() will soon be called
+         */
+
+        boolean pushed;
+        int pushDepth;
+        if (shouldFlush)
+        {
+            flushPending();
+            // the prefix mapping applies to the child element (one deeper)
+            pushDepth = m_elemContext.m_currentElemDepth + 1;
+        }
+        else
+        {
+            // the prefix mapping applies to the current element
+            pushDepth = m_elemContext.m_currentElemDepth;
+        }
+        pushed = m_prefixMap.pushNamespace(prefix, uri, pushDepth);
+
+        if (pushed)
+        {
+            /* Brian M.: don't know if we really needto do this. The
+             * callers of this object should have injected both
+             * startPrefixMapping and the attributes.  We are
+             * just covering our butt here.
+             */
+            String name;
+            if (EMPTYSTRING.equals(prefix))
+            {
+                name = "xmlns";
+                addAttributeAlways(XMLNS_URI, name, name, "CDATA", uri, false);
+            }
+            else
+            {
+                if (!EMPTYSTRING.equals(uri))
+                    // hack for XSLTC attribset16 test
+                { // that maps ns1 prefix to "" URI
+                    name = "xmlns:" + prefix;
+
+                    /* for something like xmlns:abc="w3.pretend.org"
+                     *  the      uri is the value, that is why we pass it in the
+                     * value, or 5th slot of addAttributeAlways()
+                     */
+                    addAttributeAlways(XMLNS_URI, prefix, name, "CDATA", uri, false);
+                }
+            }
+        }
+        return pushed;
+    }
+
+    /**
+     * Receive notification of an XML comment anywhere in the document. This
+     * callback will be used for comments inside or outside the document
+     * element, including comments in the external DTD subset (if read).
+     * @param ch An array holding the characters in the comment.
+     * @param start The starting position in the array.
+     * @param length The number of characters to use from the array.
+     * @throws org.xml.sax.SAXException The application may raise an exception.
+     */
+    public void comment(char ch[], int start, int length)
+        throws org.xml.sax.SAXException
+    {
+
+        int start_old = start;
+        if (m_inEntityRef)
+            return;
+        if (m_elemContext.m_startTagOpen)
+        {
+            closeStartTag();
+            m_elemContext.m_startTagOpen = false;
+        }
+        else if (m_needToCallStartDocument)
+        {
+            startDocumentInternal();
+            m_needToCallStartDocument = false;
+        }
+
+        try
+        {
+            if (shouldIndent())
+                indent();
+
+            final int limit = start + length;
+            boolean wasDash = false;
+            if (m_cdataTagOpen)
+                closeCDATA();
+            final java.io.Writer writer = m_writer;
+            writer.write(COMMENT_BEGIN);
+            // Detect occurrences of two consecutive dashes, handle as necessary.
+            for (int i = start; i < limit; i++)
+            {
+                if (wasDash && ch[i] == '-')
+                {
+                    writer.write(ch, start, i - start);
+                    writer.write(" -");
+                    start = i + 1;
+                }
+                wasDash = (ch[i] == '-');
+            }
+
+            // if we have some chars in the comment
+            if (length > 0)
+            {
+                // Output the remaining characters (if any)
+                final int remainingChars = (limit - start);
+                if (remainingChars > 0)
+                    writer.write(ch, start, remainingChars);
+                // Protect comment end from a single trailing dash
+                if (ch[limit - 1] == '-')
+                    writer.write(' ');
+            }
+            writer.write(COMMENT_END);
+        }
+        catch (IOException e)
+        {
+            throw new SAXException(e);
+        }
+
+        m_startNewLine = true;
+        // time to generate comment event
+        if (m_tracer != null)
+            super.fireCommentEvent(ch, start_old,length);
+    }
+
+    /**
+     * Report the end of a CDATA section.
+     * @throws org.xml.sax.SAXException The application may raise an exception.
+     *
+     *  @see  #startCDATA
+     */
+    public void endCDATA() throws org.xml.sax.SAXException
+    {
+        if (m_cdataTagOpen)
+            closeCDATA();
+        m_cdataStartCalled = false;
+    }
+
+    /**
+     * Report the end of DTD declarations.
+     * @throws org.xml.sax.SAXException The application may raise an exception.
+     * @see #startDTD
+     */
+    public void endDTD() throws org.xml.sax.SAXException
+    {
+        try
+        {
+            // Don't output doctype declaration until startDocumentInternal
+            // has been called. Otherwise, it can appear before XML decl.
+            if (m_needToCallStartDocument) {
+                return;
+            }
+
+            if (m_needToOutputDocTypeDecl)
+            {
+                outputDocTypeDecl(m_elemContext.m_elementName, false);
+                m_needToOutputDocTypeDecl = false;
+            }
+            final java.io.Writer writer = m_writer;
+            if (!m_inDoctype)
+                writer.write("]>");
+            else
+            {
+                writer.write('>');
+            }
+
+            writer.write(m_lineSep, 0, m_lineSepLen);
+        }
+        catch (IOException e)
+        {
+            throw new SAXException(e);
+        }
+
+    }
+
+    /**
+     * End the scope of a prefix-URI Namespace mapping.
+     * @see org.xml.sax.ContentHandler#endPrefixMapping
+     *
+     * @param prefix The prefix that was being mapping.
+     * @throws org.xml.sax.SAXException The client may throw
+     *            an exception during processing.
+     */
+    public void endPrefixMapping(String prefix) throws org.xml.sax.SAXException
+    { // do nothing
+    }
+
+    /**
+     * Receive notification of ignorable whitespace in element content.
+     *
+     * Not sure how to get this invoked quite yet.
+     *
+     * @param ch The characters from the XML document.
+     * @param start The start position in the array.
+     * @param length The number of characters to read from the array.
+     * @throws org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #characters
+     *
+     * @throws org.xml.sax.SAXException
+     */
+    public void ignorableWhitespace(char ch[], int start, int length)
+        throws org.xml.sax.SAXException
+    {
+
+        if (0 == length)
+            return;
+        characters(ch, start, length);
+    }
+
+    /**
+     * Receive notification of a skipped entity.
+     * @see org.xml.sax.ContentHandler#skippedEntity
+     *
+     * @param name The name of the skipped entity.  If it is a
+     *       parameter                   entity, the name will begin with '%',
+     * and if it is the external DTD subset, it will be the string
+     * "[dtd]".
+     * @throws org.xml.sax.SAXException Any SAX exception, possibly wrapping
+     * another exception.
+     */
+    public void skippedEntity(String name) throws org.xml.sax.SAXException
+    { // TODO: Should handle
+    }
+
+    /**
+     * Report the start of a CDATA section.
+     *
+     * @throws org.xml.sax.SAXException The application may raise an exception.
+     * @see #endCDATA
+     */
+    public void startCDATA() throws org.xml.sax.SAXException
+    {
+        m_cdataStartCalled = true;
+    }
+
+    /**
+     * Report the beginning of an entity.
+     *
+     * The start and end of the document entity are not reported.
+     * The start and end of the external DTD subset are reported
+     * using the pseudo-name "[dtd]".  All other events must be
+     * properly nested within start/end entity events.
+     *
+     * @param name The name of the entity.  If it is a parameter
+     *        entity, the name will begin with '%'.
+     * @throws org.xml.sax.SAXException The application may raise an exception.
+     * @see #endEntity
+     * @see org.xml.sax.ext.DeclHandler#internalEntityDecl
+     * @see org.xml.sax.ext.DeclHandler#externalEntityDecl
+     */
+    public void startEntity(String name) throws org.xml.sax.SAXException
+    {
+        if (name.equals("[dtd]"))
+            m_inExternalDTD = true;
+
+        if (!m_expandDTDEntities && !m_inExternalDTD) {
+            /* Only leave the entity as-is if
+             * we've been told not to expand them
+             * and this is not the magic [dtd] name.
+             */
+            startNonEscaping();
+            characters("&" + name + ';');
+            endNonEscaping();
+        }
+
+        m_inEntityRef = true;
+    }
+
+    /**
+     * For the enclosing elements starting tag write out
+     * out any attributes followed by ">"
+     *
+     * @throws org.xml.sax.SAXException
+     */
+    protected void closeStartTag() throws SAXException
+    {
+        if (m_elemContext.m_startTagOpen)
+        {
+
+            try
+            {
+                if (m_tracer != null)
+                    super.fireStartElem(m_elemContext.m_elementName);
+                int nAttrs = m_attributes.getLength();
+                if (nAttrs > 0)
+                {
+                     processAttributes(m_writer, nAttrs);
+                    // clear attributes object for re-use with next element
+                    m_attributes.clear();
+                }
+                m_writer.write('>');
+            }
+            catch (IOException e)
+            {
+                throw new SAXException(e);
+            }
+
+            /* whether Xalan or XSLTC, we have the prefix mappings now, so
+             * lets determine if the current element is specified in the cdata-
+             * section-elements list.
+             */
+            if (m_cdataSectionElements != null)
+                m_elemContext.m_isCdataSection = isCdataSection();
+
+            if (m_doIndent)
+            {
+                m_isprevtext = false;
+                m_preserves.push(m_ispreserve);
+            }
+        }
+
+    }
+
+    /**
+     * Report the start of DTD declarations, if any.
+     *
+     * Any declarations are assumed to be in the internal subset unless
+     * otherwise indicated.
+     *
+     * @param name The document type name.
+     * @param publicId The declared public identifier for the
+     *        external DTD subset, or null if none was declared.
+     * @param systemId The declared system identifier for the
+     *        external DTD subset, or null if none was declared.
+     * @throws org.xml.sax.SAXException The application may raise an
+     *            exception.
+     * @see #endDTD
+     * @see #startEntity
+     */
+    public void startDTD(String name, String publicId, String systemId)
+        throws org.xml.sax.SAXException
+    {
+        setDoctypeSystem(systemId);
+        setDoctypePublic(publicId);
+
+        m_elemContext.m_elementName = name;
+        m_inDoctype = true;
+    }
+
+    /**
+     * Returns the m_indentAmount.
+     * @return int
+     */
+    public int getIndentAmount()
+    {
+        return m_indentAmount;
+    }
+
+    /**
+     * Sets the m_indentAmount.
+     *
+     * @param m_indentAmount The m_indentAmount to set
+     */
+    public void setIndentAmount(int m_indentAmount)
+    {
+        this.m_indentAmount = m_indentAmount;
+    }
+
+    /**
+     * Tell if, based on space preservation constraints and the doIndent property,
+     * if an indent should occur.
+     *
+     * @return True if an indent should occur.
+     */
+    protected boolean shouldIndent()
+    {
+        return m_doIndent && (!m_ispreserve && !m_isprevtext);
+    }
+
+    /**
+     * Searches for the list of qname properties with the specified key in the
+     * property list. If the key is not found in this property list, the default
+     * property list, and its defaults, recursively, are then checked. The
+     * method returns <code>null</code> if the property is not found.
+     *
+     * @param   key   the property key.
+     * @param props the list of properties to search in.
+     *
+     * Sets the vector of local-name/URI pairs of the cdata section elements
+     * specified in the cdata-section-elements property.
+     *
+     * This method is essentially a copy of getQNameProperties() from
+     * OutputProperties. Eventually this method should go away and a call
+     * to setCdataSectionElements(Vector v) should be made directly.
+     */
+    private void setCdataSectionElements(String key, Properties props)
+    {
+
+        String s = props.getProperty(key);
+
+        if (null != s)
+        {
+            // Vector of URI/LocalName pairs
+            Vector v = new Vector();
+            int l = s.length();
+            boolean inCurly = false;
+            StringBuffer buf = new StringBuffer();
+
+            // parse through string, breaking on whitespaces.  I do this instead
+            // of a tokenizer so I can track whitespace inside of curly brackets,
+            // which theoretically shouldn't happen if they contain legal URLs.
+            for (int i = 0; i < l; i++)
+            {
+                char c = s.charAt(i);
+
+                if (Character.isWhitespace(c))
+                {
+                    if (!inCurly)
+                    {
+                        if (buf.length() > 0)
+                        {
+                            addCdataSectionElement(buf.toString(), v);
+                            buf.setLength(0);
+                        }
+                        continue;
+                    }
+                }
+                else if ('{' == c)
+                    inCurly = true;
+                else if ('}' == c)
+                    inCurly = false;
+
+                buf.append(c);
+            }
+
+            if (buf.length() > 0)
+            {
+                addCdataSectionElement(buf.toString(), v);
+                buf.setLength(0);
+            }
+            // call the official, public method to set the collected names
+            setCdataSectionElements(v);
+        }
+
+    }
+
+    /**
+     * Adds a URI/LocalName pair of strings to the list.
+     *
+     * @param URI_and_localName String of the form "{uri}local" or "local"
+     *
+     * @return a QName object
+     */
+    private void addCdataSectionElement(String URI_and_localName, Vector v)
+    {
+
+        StringTokenizer tokenizer =
+            new StringTokenizer(URI_and_localName, "{}", false);
+        String s1 = tokenizer.nextToken();
+        String s2 = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
+
+        if (null == s2)
+        {
+            // add null URI and the local name
+            v.addElement(null);
+            v.addElement(s1);
+        }
+        else
+        {
+            // add URI, then local name
+            v.addElement(s1);
+            v.addElement(s2);
+        }
+    }
+
+    /**
+     * Remembers the cdata sections specified in the cdata-section-elements.
+     * The "official way to set URI and localName pairs.
+     * This method should be used by both Xalan and XSLTC.
+     *
+     * @param URI_and_localNames a vector of pairs of Strings (URI/local)
+     */
+    public void setCdataSectionElements(Vector URI_and_localNames)
+    {
+        m_cdataSectionElements = URI_and_localNames;
+    }
+
+    /**
+     * Makes sure that the namespace URI for the given qualified attribute name
+     * is declared.
+     * @param ns the namespace URI
+     * @param rawName the qualified name
+     * @return returns null if no action is taken, otherwise it returns the
+     * prefix used in declaring the namespace.
+     * @throws SAXException
+     */
+    protected String ensureAttributesNamespaceIsDeclared(
+        String ns,
+        String localName,
+        String rawName)
+        throws org.xml.sax.SAXException
+    {
+
+        if (ns != null && ns.length() > 0)
+        {
+
+            // extract the prefix in front of the raw name
+            int index = 0;
+            String prefixFromRawName =
+                (index = rawName.indexOf(":")) < 0
+                    ? ""
+                    : rawName.substring(0, index);
+
+            if (index > 0)
+            {
+                // we have a prefix, lets see if it maps to a namespace
+                String uri = m_prefixMap.lookupNamespace(prefixFromRawName);
+                if (uri != null && uri.equals(ns))
+                {
+                    // the prefix in the raw name is already maps to the given namespace uri
+                    // so we don't need to do anything
+                    return null;
+                }
+                else
+                {
+                    // The uri does not map to the prefix in the raw name,
+                    // so lets make the mapping.
+                    this.startPrefixMapping(prefixFromRawName, ns, false);
+                    this.addAttribute(
+                        "http://www.w3.org/2000/xmlns/",
+                        prefixFromRawName,
+                        "xmlns:" + prefixFromRawName,
+                        "CDATA",
+                        ns, false);
+                    return prefixFromRawName;
+                }
+            }
+            else
+            {
+                // we don't have a prefix in the raw name.
+                // Does the URI map to a prefix already?
+                String prefix = m_prefixMap.lookupPrefix(ns);
+                if (prefix == null)
+                {
+                    // uri is not associated with a prefix,
+                    // so lets generate a new prefix to use
+                    prefix = m_prefixMap.generateNextPrefix();
+                    this.startPrefixMapping(prefix, ns, false);
+                    this.addAttribute(
+                        "http://www.w3.org/2000/xmlns/",
+                        prefix,
+                        "xmlns:" + prefix,
+                        "CDATA",
+                        ns, false);
+                }
+
+                return prefix;
+
+            }
+        }
+        return null;
+    }
+
+    void ensurePrefixIsDeclared(String ns, String rawName)
+        throws org.xml.sax.SAXException
+    {
+
+        if (ns != null && ns.length() > 0)
+        {
+            int index;
+            final boolean no_prefix = ((index = rawName.indexOf(":")) < 0);
+            String prefix = (no_prefix) ? "" : rawName.substring(0, index);
+
+            if (null != prefix)
+            {
+                String foundURI = m_prefixMap.lookupNamespace(prefix);
+
+                if ((null == foundURI) || !foundURI.equals(ns))
+                {
+                    this.startPrefixMapping(prefix, ns);
+
+                    // Bugzilla1133: Generate attribute as well as namespace event.
+                    // SAX does expect both.
+
+                    this.addAttributeAlways(
+                        "http://www.w3.org/2000/xmlns/",
+                        no_prefix ? "xmlns" : prefix,  // local name
+                        no_prefix ? "xmlns" : ("xmlns:"+ prefix), // qname
+                        "CDATA",
+                        ns,
+                        false);
+                }
+
+            }
+        }
+    }
+
+    /**
+     * This method flushes any pending events, which can be startDocument()
+     * closing the opening tag of an element, or closing an open CDATA section.
+     */
+    public void flushPending() throws SAXException
+    {
+            if (m_needToCallStartDocument)
+            {
+                startDocumentInternal();
+                m_needToCallStartDocument = false;
+            }
+            if (m_elemContext.m_startTagOpen)
+            {
+                closeStartTag();
+                m_elemContext.m_startTagOpen = false;
+            }
+
+            if (m_cdataTagOpen)
+            {
+                closeCDATA();
+                m_cdataTagOpen = false;
+            }
+    }
+
+    public void setContentHandler(ContentHandler ch)
+    {
+        // this method is really only useful in the ToSAXHandler classes but it is
+        // in the interface.  If the method defined here is ever called
+        // we are probably in trouble.
+    }
+
+    /**
+     * Adds the given attribute to the set of attributes, even if there is
+     * no currently open element. This is useful if a SAX startPrefixMapping()
+     * should need to add an attribute before the element name is seen.
+     *
+     * This method is a copy of its super classes method, except that some
+     * tracing of events is done.  This is so the tracing is only done for
+     * stream serializers, not for SAX ones.
+     *
+     * @param uri the URI of the attribute
+     * @param localName the local name of the attribute
+     * @param rawName   the qualified name of the attribute
+     * @param type the type of the attribute (probably CDATA)
+     * @param value the value of the attribute
+     * @param xslAttribute true if this attribute is coming from an xsl:attribute element.
+     * @return true if the attribute value was added,
+     * false if the attribute already existed and the value was
+     * replaced with the new value.
+     */
+    public boolean addAttributeAlways(
+        String uri,
+        String localName,
+        String rawName,
+        String type,
+        String value,
+        boolean xslAttribute)
+    {
+        boolean was_added;
+        int index;
+        //if (uri == null || localName == null || uri.length() == 0)
+            index = m_attributes.getIndex(rawName);
+        // Don't use 'localName' as it gives incorrect value, rely only on 'rawName'
+        /*else {
+            index = m_attributes.getIndex(uri, localName);
+        }*/
+        if (index >= 0)
+        {
+            String old_value = null;
+            if (m_tracer != null)
+            {
+                old_value = m_attributes.getValue(index);
+                if (value.equals(old_value))
+                    old_value = null;
+            }
+
+            /* We've seen the attribute before.
+             * We may have a null uri or localName, but all we really
+             * want to re-set is the value anyway.
+             */
+            m_attributes.setValue(index, value);
+            was_added = false;
+            if (old_value != null){
+                firePseudoAttributes();
+            }
+
+        }
+        else
+        {
+            // the attribute doesn't exist yet, create it
+            if (xslAttribute)
+            {
+                /*
+                 * This attribute is from an xsl:attribute element so we take some care in
+                 * adding it, e.g.
+                 *   <elem1  foo:attr1="1" xmlns:foo="uri1">
+                 *       <xsl:attribute name="foo:attr2">2</xsl:attribute>
+                 *   </elem1>
+                 *
+                 * We are adding attr1 and attr2 both as attributes of elem1,
+                 * and this code is adding attr2 (the xsl:attribute ).
+                 * We could have a collision with the prefix like in the example above.
+                 */
+
+                // In the example above, is there a prefix like foo ?
+                final int colonIndex = rawName.indexOf(':');
+                if (colonIndex > 0)
+                {
+                    String prefix = rawName.substring(0,colonIndex);
+                    NamespaceMappings.MappingRecord existing_mapping = m_prefixMap.getMappingFromPrefix(prefix);
+
+                    /* Before adding this attribute (foo:attr2),
+                     * is the prefix for it (foo) already mapped at the current depth?
+                     */
+                    if (existing_mapping != null
+                    && existing_mapping.m_declarationDepth == m_elemContext.m_currentElemDepth
+                    && !existing_mapping.m_uri.equals(uri))
+                    {
+                        /*
+                         * There is an existing mapping of this prefix,
+                         * it differs from the one we need,
+                         * and unfortunately it is at the current depth so we
+                         * can not over-ride it.
+                         */
+
+                        /*
+                         * Are we lucky enough that an existing other prefix maps to this URI ?
+                         */
+                        prefix = m_prefixMap.lookupPrefix(uri);
+                        if (prefix == null)
+                        {
+                            /* Unfortunately there is no existing prefix that happens to map to ours,
+                             * so to avoid a prefix collision we must generated a new prefix to use.
+                             * This is OK because the prefix URI mapping
+                             * defined in the xsl:attribute is short in scope,
+                             * just the xsl:attribute element itself,
+                             * and at this point in serialization the body of the
+                             * xsl:attribute, if any, is just a String. Right?
+                             *   . . . I sure hope so - Brian M.
+                             */
+                            prefix = m_prefixMap.generateNextPrefix();
+                        }
+
+                        rawName = prefix + ':' + localName;
+                    }
+                }
+
+                try
+                {
+                    /* This is our last chance to make sure the namespace for this
+                     * attribute is declared, especially if we just generated an alternate
+                     * prefix to avoid a collision (the new prefix/rawName will go out of scope
+                     * soon and be lost ...  last chance here.
+                     */
+                    String prefixUsed =
+                        ensureAttributesNamespaceIsDeclared(
+                            uri,
+                            localName,
+                            rawName);
+                }
+                catch (SAXException e)
+                {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                }
+            }
+            m_attributes.addAttribute(uri, localName, rawName, type, value);
+            was_added = true;
+            if (m_tracer != null){
+                firePseudoAttributes();
+            }
+        }
+        return was_added;
+    }
+
+    /**
+     * To fire off the pseudo characters of attributes, as they currently
+     * exist. This method should be called everytime an attribute is added,
+     * or when an attribute value is changed, or an element is created.
+     */
+
+    protected void firePseudoAttributes()
+    {
+        if (m_tracer != null)
+        {
+            try
+            {
+                // flush out the "<elemName" if not already flushed
+                m_writer.flush();
+
+                // make a StringBuffer to write the name="value" pairs to.
+                StringBuffer sb = new StringBuffer();
+                int nAttrs = m_attributes.getLength();
+                if (nAttrs > 0)
+                {
+                    // make a writer that internally appends to the same
+                    // StringBuffer
+                    java.io.Writer writer =
+                        new ToStream.WritertoStringBuffer(sb);
+
+                    processAttributes(writer, nAttrs);
+                    // Don't clear the attributes!
+                    // We only want to see what would be written out
+                    // at this point, we don't want to loose them.
+                }
+                sb.append('>');  // the potential > after the attributes.
+                // convert the StringBuffer to a char array and
+                // emit the trace event that these characters "might"
+                // be written
+                char ch[] = sb.toString().toCharArray();
+                m_tracer.fireGenerateEvent(
+                    SerializerTrace.EVENTTYPE_OUTPUT_PSEUDO_CHARACTERS,
+                    ch,
+                    0,
+                    ch.length);
+            }
+            catch (IOException ioe)
+            {
+                // ignore ?
+            }
+            catch (SAXException se)
+            {
+                // ignore ?
+            }
+        }
+    }
+
+    /**
+     * This inner class is used only to collect attribute values
+     * written by the method writeAttrString() into a string buffer.
+     * In this manner trace events, and the real writing of attributes will use
+     * the same code.
+     */
+    private class WritertoStringBuffer extends java.io.Writer
+    {
+        final private StringBuffer m_stringbuf;
+        /**
+         * @see java.io.Writer#write(char[], int, int)
+         */
+        WritertoStringBuffer(StringBuffer sb)
+        {
+            m_stringbuf = sb;
+        }
+
+        public void write(char[] arg0, int arg1, int arg2) throws IOException
+        {
+            m_stringbuf.append(arg0, arg1, arg2);
+        }
+        /**
+         * @see java.io.Writer#flush()
+         */
+        public void flush() throws IOException
+        {
+        }
+        /**
+         * @see java.io.Writer#close()
+         */
+        public void close() throws IOException
+        {
+        }
+
+        public void write(int i)
+        {
+            m_stringbuf.append((char) i);
+        }
+
+        public void write(String s)
+        {
+            m_stringbuf.append(s);
+        }
+    }
+
+    /**
+     * @see SerializationHandler#setTransformer(Transformer)
+     */
+    public void setTransformer(Transformer transformer) {
+        super.setTransformer(transformer);
+        if (m_tracer != null
+         && !(m_writer instanceof SerializerTraceWriter)  )
+            m_writer = new SerializerTraceWriter(m_writer, m_tracer);
+
+
+    }
+    /**
+     * Try's to reset the super class and reset this class for
+     * re-use, so that you don't need to create a new serializer
+     * (mostly for performance reasons).
+     *
+     * @return true if the class was successfuly reset.
+     */
+    public boolean reset()
+    {
+        boolean wasReset = false;
+        if (super.reset())
+        {
+            resetToStream();
+            wasReset = true;
+        }
+        return wasReset;
+    }
+
+    /**
+     * Reset all of the fields owned by ToStream class
+     *
+     */
+    private void resetToStream()
+    {
+         this.m_cdataStartCalled = false;
+         /* The stream is being reset. It is one of
+          * ToXMLStream, ToHTMLStream ... and this type can't be changed
+          * so neither should m_charInfo which is associated with the
+          * type of Stream. Just leave m_charInfo as-is for the next re-use.
+          *
+          */
+         // this.m_charInfo = null; // don't set to null
+
+         this.m_disableOutputEscapingStates.clear();
+
+         this.m_escaping = true;
+         // Leave m_format alone for now - Brian M.
+         // this.m_format = null;
+         this.m_inDoctype = false;
+         this.m_ispreserve = false;
+         this.m_ispreserve = false;
+         this.m_isprevtext = false;
+         this.m_isUTF8 = false; //  ?? used anywhere ??
+         this.m_preserves.clear();
+         this.m_shouldFlush = true;
+         this.m_spaceBeforeClose = false;
+         this.m_startNewLine = false;
+         this.m_lineSepUse = true;
+         // DON'T SET THE WRITER TO NULL, IT MAY BE REUSED !!
+         // this.m_writer = null;
+         this.m_expandDTDEntities = true;
+
+    }
+
+    /**
+      * Sets the character encoding coming from the xsl:output encoding stylesheet attribute.
+      * @param encoding the character encoding
+      */
+     public void setEncoding(String encoding)
+     {
+         String old = getEncoding();
+         super.setEncoding(encoding);
+         if (old == null || !old.equals(encoding)) {
+            // If we have changed the setting of the
+            m_encodingInfo = Encodings.getEncodingInfo(encoding);
+
+            if (encoding != null && m_encodingInfo.name == null) {
+                // We tried to get an EncodingInfo for Object for the given
+                // encoding, but it came back with an internall null name
+                // so the encoding is not supported by the JDK, issue a message.
+                String msg = Utils.messages.createMessage(
+                                MsgKey.ER_ENCODING_NOT_SUPPORTED,new Object[]{ encoding });
+                try
+                {
+                        // Prepare to issue the warning message
+                        Transformer tran = super.getTransformer();
+                        if (tran != null) {
+                                ErrorListener errHandler = tran.getErrorListener();
+                                // Issue the warning message
+                                if (null != errHandler && m_sourceLocator != null)
+                                        errHandler.warning(new TransformerException(msg, m_sourceLocator));
+                                else
+                                        System.out.println(msg);
+                    }
+                        else
+                                System.out.println(msg);
+                }
+                catch (Exception e){}
+            }
+         }
+         return;
+     }
+
+    /**
+     * Simple stack for boolean values.
+     *
+     * This class is a copy of the one in com.sun.org.apache.xml.internal.utils.
+     * It exists to cut the serializers dependancy on that package.
+     * A minor changes from that package are:
+     * doesn't implement Clonable
+     *
+     * @xsl.usage internal
+     */
+    static final class BoolStack
+    {
+
+      /** Array of boolean values          */
+      private boolean m_values[];
+
+      /** Array size allocated           */
+      private int m_allocatedSize;
+
+      /** Index into the array of booleans          */
+      private int m_index;
+
+      /**
+       * Default constructor.  Note that the default
+       * block size is very small, for small lists.
+       */
+      public BoolStack()
+      {
+        this(32);
+      }
+
+      /**
+       * Construct a IntVector, using the given block size.
+       *
+       * @param size array size to allocate
+       */
+      public BoolStack(int size)
+      {
+
+        m_allocatedSize = size;
+        m_values = new boolean[size];
+        m_index = -1;
+      }
+
+      /**
+       * Get the length of the list.
+       *
+       * @return Current length of the list
+       */
+      public final int size()
+      {
+        return m_index + 1;
+      }
+
+      /**
+       * Clears the stack.
+       *
+       */
+      public final void clear()
+      {
+        m_index = -1;
+      }
+
+      /**
+       * Pushes an item onto the top of this stack.
+       *
+       *
+       * @param val the boolean to be pushed onto this stack.
+       * @return  the <code>item</code> argument.
+       */
+      public final boolean push(boolean val)
+      {
+
+        if (m_index == m_allocatedSize - 1)
+          grow();
+
+        return (m_values[++m_index] = val);
+      }
+
+      /**
+       * Removes the object at the top of this stack and returns that
+       * object as the value of this function.
+       *
+       * @return     The object at the top of this stack.
+       * @throws  EmptyStackException  if this stack is empty.
+       */
+      public final boolean pop()
+      {
+        return m_values[m_index--];
+      }
+
+      /**
+       * Removes the object at the top of this stack and returns the
+       * next object at the top as the value of this function.
+       *
+       *
+       * @return Next object to the top or false if none there
+       */
+      public final boolean popAndTop()
+      {
+
+        m_index--;
+
+        return (m_index >= 0) ? m_values[m_index] : false;
+      }
+
+      /**
+       * Set the item at the top of this stack
+       *
+       *
+       * @param b Object to set at the top of this stack
+       */
+      public final void setTop(boolean b)
+      {
+        m_values[m_index] = b;
+      }
+
+      /**
+       * Looks at the object at the top of this stack without removing it
+       * from the stack.
+       *
+       * @return     the object at the top of this stack.
+       * @throws  EmptyStackException  if this stack is empty.
+       */
+      public final boolean peek()
+      {
+        return m_values[m_index];
+      }
+
+      /**
+       * Looks at the object at the top of this stack without removing it
+       * from the stack.  If the stack is empty, it returns false.
+       *
+       * @return     the object at the top of this stack.
+       */
+      public final boolean peekOrFalse()
+      {
+        return (m_index > -1) ? m_values[m_index] : false;
+      }
+
+      /**
+       * Looks at the object at the top of this stack without removing it
+       * from the stack.  If the stack is empty, it returns true.
+       *
+       * @return     the object at the top of this stack.
+       */
+      public final boolean peekOrTrue()
+      {
+        return (m_index > -1) ? m_values[m_index] : true;
+      }
+
+      /**
+       * Tests if this stack is empty.
+       *
+       * @return  <code>true</code> if this stack is empty;
+       *          <code>false</code> otherwise.
+       */
+      public boolean isEmpty()
+      {
+        return (m_index == -1);
+      }
+
+      /**
+       * Grows the size of the stack
+       *
+       */
+      private void grow()
+      {
+
+        m_allocatedSize *= 2;
+
+        boolean newVector[] = new boolean[m_allocatedSize];
+
+        System.arraycopy(m_values, 0, newVector, 0, m_index + 1);
+
+        m_values = newVector;
+      }
+    }
+
+    // Implement DTDHandler
+    /**
+     * If this method is called, the serializer is used as a
+     * DTDHandler, which changes behavior how the serializer
+     * handles document entities.
+     * @see org.xml.sax.DTDHandler#notationDecl(java.lang.String, java.lang.String, java.lang.String)
+     */
+    public void notationDecl(String name, String pubID, String sysID) throws SAXException {
+        // TODO Auto-generated method stub
+        try {
+            DTDprolog();
+
+            m_writer.write("<!NOTATION ");
+            m_writer.write(name);
+            if (pubID != null) {
+                m_writer.write(" PUBLIC \"");
+                m_writer.write(pubID);
+
+            }
+            else {
+                m_writer.write(" SYSTEM \"");
+                m_writer.write(sysID);
+            }
+            m_writer.write("\" >");
+            m_writer.write(m_lineSep, 0, m_lineSepLen);
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * If this method is called, the serializer is used as a
+     * DTDHandler, which changes behavior how the serializer
+     * handles document entities.
+     * @see org.xml.sax.DTDHandler#unparsedEntityDecl(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
+     */
+    public void unparsedEntityDecl(String name, String pubID, String sysID, String notationName) throws SAXException {
+        // TODO Auto-generated method stub
+        try {
+            DTDprolog();
+
+            m_writer.write("<!ENTITY ");
+            m_writer.write(name);
+            if (pubID != null) {
+                m_writer.write(" PUBLIC \"");
+                m_writer.write(pubID);
+
+            }
+            else {
+                m_writer.write(" SYSTEM \"");
+                m_writer.write(sysID);
+            }
+            m_writer.write("\" NDATA ");
+            m_writer.write(notationName);
+            m_writer.write(" >");
+            m_writer.write(m_lineSep, 0, m_lineSepLen);
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * A private helper method to output the
+     * @throws SAXException
+     * @throws IOException
+     */
+    private void DTDprolog() throws SAXException, IOException {
+        final java.io.Writer writer = m_writer;
+        if (m_needToOutputDocTypeDecl)
+        {
+            outputDocTypeDecl(m_elemContext.m_elementName, false);
+            m_needToOutputDocTypeDecl = false;
+        }
+        if (m_inDoctype)
+        {
+            writer.write(" [");
+            writer.write(m_lineSep, 0, m_lineSepLen);
+            m_inDoctype = false;
+        }
+    }
+
+    /**
+     * If set to false the serializer does not expand DTD entities,
+     * but leaves them as is, the default value is true;
+     */
+    public void setDTDEntityExpansion(boolean expand) {
+        m_expandDTDEntities = expand;
+    }
+}