/*
 * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
 */
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

package com.sun.org.apache.xml.internal.serializer;

import com.sun.org.apache.xml.internal.serializer.utils.MsgKey;
import com.sun.org.apache.xml.internal.serializer.utils.Utils;
import java.io.IOException;
import java.util.HashMap;
import java.util.Set;
import java.util.ArrayList;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.SourceLocator;
import javax.xml.transform.Transformer;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ext.Locator2;

/**
 * This class acts as a base class for the XML "serializers"
 * and the stream serializers.
 * It contains a number of common fields and methods.
 *
 * @xsl.usage internal
 */
public abstract class SerializerBase
    implements SerializationHandler, SerializerConstants
{

    /**
     * To fire off the end element trace event
     * @param name Name of element
     */
    protected void fireEndElem(String name)
        throws org.xml.sax.SAXException
    {
        if (m_tracer != null) {
            flushMyWriter();
            m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_ENDELEMENT,name, (Attributes)null);
        }
    }

    /**
     * Report the characters trace event
     * @param chars  content of characters
     * @param start  starting index of characters to output
     * @param length  number of characters to output
     */
    protected void fireCharEvent(char[] chars, int start, int length)
        throws org.xml.sax.SAXException
    {
        if (m_tracer != null) {
            flushMyWriter();
            m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_CHARACTERS, chars, start,length);
        }
    }

    /**
     * true if we still need to call startDocumentInternal()
     */
    protected boolean m_needToCallStartDocument = true;

    /** True if a trailing "]]>" still needs to be written to be
     * written out. Used to merge adjacent CDATA sections
     */
    protected boolean m_cdataTagOpen = false;

    /**
     * All the attributes of the current element, collected from
     * startPrefixMapping() calls, or addAddtribute() calls, or
     * from the SAX attributes in a startElement() call.
     */
    protected AttributesImplSerializer m_attributes = new AttributesImplSerializer();

    /**
     * Tells if we're in an EntityRef event, true if it's greater than 0. Use
     * integer type to handle nested entity reference, increase m_inEntityRef in
     * startEntity, decrease m_inEntityRef in endEntity.
     */
    protected int m_inEntityRef = 0;

    /** This flag is set while receiving events from the external DTD */
    protected boolean m_inExternalDTD = false;

    /**
     * The System ID for the doc type.
     */
    protected String m_doctypeSystem;

    /**
     * The public ID for the doc type.
     */
    protected String m_doctypePublic;

    /**
     * Flag to tell that we need to add the doctype decl, which we can't do
     * until the first element is encountered.
     */
    boolean m_needToOutputDocTypeDecl = true;

    /**
     * Tells if we should write the XML declaration.
     */
    protected boolean m_shouldNotWriteXMLHeader = false;

    /**
     * The standalone value for the doctype.
     */
    private String m_standalone;

    /**
     * True if standalone was specified.
     */
    protected boolean m_standaloneWasSpecified = false;

    /**
     * Determine if the output is a standalone.
     */
    protected boolean m_isStandalone = false;

    /**
     * Flag to tell if indenting (pretty-printing) is on.
     */
    protected boolean m_doIndent = false;

    /**
     * Amount to indent.
     */
    protected int m_indentAmount = 4;

    /**
     * Tells the XML version, for writing out to the XML decl.
     */
    protected String m_version = null;

    /**
     * The mediatype.  Not used right now.
     */
    protected String m_mediatype;

    /**
     * The transformer that was around when this output handler was created (if
     * any).
     */
    private Transformer m_transformer;

    /**
     * Namespace support, that keeps track of currently defined
     * prefix/uri mappings. As processed elements come and go, so do
     * the associated mappings for that element.
     */
    protected NamespaceMappings m_prefixMap;

    /**
     * Handle for firing generate events.  This interface may be implemented
     * by the referenced transformer object.
     */
    protected SerializerTrace m_tracer;

    protected SourceLocator m_sourceLocator;

    /**
     * The writer to send output to. This field is only used in the ToStream
     * serializers, but exists here just so that the fireStartDoc() and
     * other fire... methods can flush this writer when tracing.
     */
    protected java.io.Writer m_writer = null;

    /**
     * A reference to "stack frame" corresponding to
     * the current element. Such a frame is pushed at a startElement()
     * and popped at an endElement(). This frame contains information about
     * the element, such as its namespace URI.
     */
    protected ElemContext m_elemContext = new ElemContext();

    /**
     * A utility buffer for converting Strings passed to
     * character() methods to character arrays.
     * Reusing this buffer means not creating a new character array
     * everytime and it runs faster.
     */
    protected char[] m_charsBuff = new char[60];

    /**
     * A utility buffer for converting Strings passed to
     * attribute methods to character arrays.
     * Reusing this buffer means not creating a new character array
     * everytime and it runs faster.
     */
    protected char[] m_attrBuff = new char[30];

    private Locator m_locator = null;

    protected boolean m_needToCallSetDocumentInfo = true;

    /**
     * Receive notification of a comment.
     *
     * @see ExtendedLexicalHandler#comment(String)
     */
    public void comment(String data) throws SAXException {
        final int length = data.length();
        if (length > m_charsBuff.length) {
            m_charsBuff = new char[length * 2 + 1];
        }
        data.getChars(0, length, m_charsBuff, 0);
        comment(m_charsBuff, 0, length);
    }

    /**
     * If at runtime, when the qname of the attribute is
     * known, another prefix is specified for the attribute, then we can
     * patch or hack the name with this method. For
     * a qname of the form "ns?:otherprefix:name", this function patches the
     * qname by simply ignoring "otherprefix".
     * TODO: This method is a HACK! We do not have access to the
     * XML file, it sometimes generates a NS prefix of the form "ns?" for
     * an attribute.
     */
    protected String patchName(String qname) {
        final int lastColon = qname.lastIndexOf(':');

        if (lastColon > 0) {
            final int firstColon = qname.indexOf(':');
            final String prefix = qname.substring(0, firstColon);
            final String localName = qname.substring(lastColon + 1);

            // If uri is "" then ignore prefix
            final String uri = m_prefixMap.lookupNamespace(prefix);
            if (uri != null && uri.length() == 0) {
                return localName;
            } else if (firstColon != lastColon) {
                return prefix + ':' + localName;
            }
        }
        return qname;
    }

    /**
     * Returns the local name of a qualified name. If the name has no prefix,
     * then it works as the identity (SAX2).
     * @param qname the qualified name
     * @return the name, but excluding any prefix and colon.
     */
    protected static String getLocalName(String qname) {
        final int col = qname.lastIndexOf(':');
        return (col > 0) ? qname.substring(col + 1) : qname;
    }

    /**
     * Receive an object for locating the origin of SAX document events.
     *
     * @param locator An object that can return the location of any SAX document
     * event.
     *
     * Receive an object for locating the origin of SAX document events.
     *
     * <p>SAX parsers are strongly encouraged (though not absolutely
     * required) to supply a locator: if it does so, it must supply
     * the locator to the application by invoking this method before
     * invoking any of the other methods in the DocumentHandler
     * interface.</p>
     *
     * <p>The locator allows the application to determine the end
     * position of any document-related event, even if the parser is
     * not reporting an error.  Typically, the application will
     * use this information for reporting its own errors (such as
     * character content that does not match an application's
     * business rules).  The information returned by the locator
     * is probably not sufficient for use with a search engine.</p>
     *
     * <p>Note that the locator will return correct information only
     * during the invocation of the events in this interface.  The
     * application should not attempt to use it at any other time.</p>
     */
    public void setDocumentLocator(Locator locator) {
        m_locator = locator;
    }

    /**
     * Adds the given attribute to the set of collected attributes , but only if
     * there is a currently open element.
     *
     * An element is currently open if a startElement() notification has
     * occured but the start of the element has not yet been written to the
     * output.  In the stream case this means that we have not yet been forced
     * to close the elements opening tag by another notification, such as a
     * character notification.
     *
     * @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:attriute element
     * @see ExtendedContentHandler#addAttribute(String, String, String, String, String)
     */
    public void addAttribute(String uri, String localName, String rawName,
                             String type, String value, boolean XSLAttribute)
        throws SAXException
    {
        if (m_elemContext.m_startTagOpen) {
            addAttributeAlways(uri, localName, rawName, type, value, XSLAttribute);
        }
    }

    /**
     * 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.
     *
     * @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 was added,
     * false if an existing value was replaced.
     */
    public boolean addAttributeAlways(String uri, String localName, String rawName,
                                      String type, String value, boolean XSLAttribute)
    {
        boolean was_added;
        int index;

        if (localName == null || uri == null || uri.length() == 0)
            index = m_attributes.getIndex(rawName);
        else {
            index = m_attributes.getIndex(uri,localName);
        }
        if (index >= 0) {
            /* 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;
        } else {
            // the attribute doesn't exist yet, create it
            m_attributes.addAttribute(uri, localName, rawName, type, value);
            was_added = true;
        }
        return was_added;
    }

    /**
     *  Adds  the given attribute to the set of collected attributes,
     * but only if there is a currently open element.
     *
     * @param name the attribute's qualified name
     * @param value the value of the attribute
     */
    public void addAttribute(String name, final String value) {
        if (m_elemContext.m_startTagOpen) {
            final String patchedName = patchName(name);
            final String localName = getLocalName(patchedName);
            final String uri = getNamespaceURI(patchedName, false);

            addAttributeAlways(uri,localName, patchedName, "CDATA", value, false);
        }
    }

    /**
     * Adds the given xsl:attribute to the set of collected attributes,
     * but only if there is a currently open element.
     *
     * @param name the attribute's qualified name (prefix:localName)
     * @param value the value of the attribute
     * @param uri the URI that the prefix of the name points to
     */
    public void addXSLAttribute(String name, final String value, final String uri) {
        if (m_elemContext.m_startTagOpen) {
            final String patchedName = patchName(name);
            final String localName = getLocalName(patchedName);

            addAttributeAlways(uri,localName, patchedName, "CDATA", value, true);
        }
    }

    /**
     * Add the given attributes to the currently collected ones. These
     * attributes are always added, regardless of whether on not an element
     * is currently open.
     * @param atts List of attributes to add to this list
     */
    public void addAttributes(Attributes atts) throws SAXException {
        int nAtts = atts.getLength();
        for (int i = 0; i < nAtts; i++) {
            String uri = atts.getURI(i);

            if (null == uri)
                uri = "";

            addAttributeAlways(
                uri,
                atts.getLocalName(i),
                atts.getQName(i),
                atts.getType(i),
                atts.getValue(i),
                false);
        }
    }

    /**
     * Return a {@link ContentHandler} interface into this serializer.
     * If the serializer does not support the {@link ContentHandler}
     * interface, it should return null.
     *
     * @return A {@link ContentHandler} interface into this serializer,
     *  or null if the serializer is not SAX 2 capable
     * @throws IOException An I/O exception occured
     */
    public ContentHandler asContentHandler() throws IOException {
        return this;
    }

    /**
     * Report the end of an entity.
     *
     * @param name The name of the entity that is ending.
     * @throws org.xml.sax.SAXException The application may raise an exception.
     * @see #startEntity
     */
    public void endEntity(String name) throws org.xml.sax.SAXException {
        if (name.equals("[dtd]"))
            m_inExternalDTD = false;

        if (!m_inExternalDTD)
            m_inEntityRef--;

        if (m_tracer != null)
            this.fireEndEntity(name);
    }

    /**
     * This method checks if current node is in entity reference.
     *
     * @return True if current node is in entity reference.
     */
    protected boolean isInEntityRef() {
        return m_inEntityRef > 0;
    }

    /**
     * Flush and close the underlying java.io.Writer. This method applies to
     * ToStream serializers, not ToSAXHandler serializers.
     * @see ToStream
     */
    public void close() {
        // do nothing (base behavior)
    }

    /**
     * Initialize global variables
     */
    protected void initCDATA() {
        // CDATA stack
        // _cdataStack = new Stack();
        // _cdataStack.push(new Integer(-1)); // push dummy value
    }

    /**
     * Returns the character encoding to be used in the output document.
     * @return the character encoding to be used in the output document.
     */
    public String getEncoding() {
        return getOutputProperty(OutputKeys.ENCODING);
    }

   /**
     * Sets the character encoding coming from the xsl:output encoding stylesheet attribute.
     * @param m_encoding the character encoding
     */
    public void setEncoding(String encoding) {
        setOutputProperty(OutputKeys.ENCODING,encoding);
    }

    /**
     * Sets the value coming from the xsl:output omit-xml-declaration stylesheet attribute
     * @param b true if the XML declaration is to be omitted from the output
     * document.
     */
    public void setOmitXMLDeclaration(boolean b) {
        String val = b ? "yes":"no";
        setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,val);
    }

    /**
     * @return true if the XML declaration is to be omitted from the output
     * document.
     */
    public boolean getOmitXMLDeclaration() {
        return m_shouldNotWriteXMLHeader;
    }

    /**
     * Returns the previously set value of the value to be used as the public
     * identifier in the document type declaration (DTD).
     *
     *@return the public identifier to be used in the DOCTYPE declaration in the
     * output document.
     */
    public String getDoctypePublic()
    {
        return m_doctypePublic;
    }

    /** Set the value coming from the xsl:output doctype-public stylesheet attribute.
      * @param doctypePublic the public identifier to be used in the DOCTYPE
      * declaration in the output document.
      */
    public void setDoctypePublic(String doctypePublic)
    {
        setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, doctypePublic);
    }


    /**
     * Returns the previously set value of the value to be used
     * as the system identifier in the document type declaration (DTD).
         * @return the system identifier to be used in the DOCTYPE declaration in
         * the output document.
     *
     */
    public String getDoctypeSystem()
    {
        return m_doctypeSystem;
    }

    /** Set the value coming from the xsl:output doctype-system stylesheet attribute.
      * @param doctypeSystem the system identifier to be used in the DOCTYPE
      * declaration in the output document.
      */
    public void setDoctypeSystem(String doctypeSystem)
    {
        setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doctypeSystem);
    }

    /** Set the value coming from the xsl:output doctype-public and doctype-system stylesheet properties
     * @param doctypeSystem the system identifier to be used in the DOCTYPE
     * declaration in the output document.
     * @param doctypePublic the public identifier to be used in the DOCTYPE
     * declaration in the output document.
     */
    public void setDoctype(String doctypeSystem, String doctypePublic)
    {
        setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doctypeSystem);
        setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, doctypePublic);
    }

    /**
     * Sets the value coming from the xsl:output standalone stylesheet attribute.
     * @param standalone a value of "yes" indicates that the
     * <code>standalone</code> delaration is to be included in the output
     * document. This method remembers if the value was explicitly set using
     * this method, verses if the value is the default value.
     */
    public void setStandalone(String standalone) {
        setOutputProperty(OutputKeys.STANDALONE, standalone);
    }

    /**
     * Sets the XSL standalone attribute, but does not remember if this is a
     * default or explicite setting.
     * @param standalone "yes" | "no"
     */
    protected void setStandaloneInternal(String standalone) {
        if ("yes".equals(standalone))
            m_standalone = "yes";
        else
            m_standalone = "no";

    }

    /**
     * Gets the XSL standalone attribute
     * @return a value of "yes" if the <code>standalone</code> delaration is to
     * be included in the output document.
     *  @see XSLOutputAttributes#getStandalone()
     */
    public String getStandalone() {
        return m_standalone;
    }

    /**
     * @return true if the output document should be indented to visually
     * indicate its structure.
     */
    public boolean getIndent() {
        return m_doIndent;
    }
    /**
     * Gets the mediatype the media-type or MIME type associated with the output
     * document.
     * @return the mediatype the media-type or MIME type associated with the
     * output document.
     */
    public String getMediaType() {
        return m_mediatype;
    }

    /**
     * Gets the version of the output format.
     * @return the version of the output format.
     */
    public String getVersion() {
        return m_version;
    }

    /**
     * Sets the value coming from the xsl:output version attribute.
     * @param version the version of the output format.
     * @see SerializationHandler#setVersion(String)
     */
    public void setVersion(String version) {
        setOutputProperty(OutputKeys.VERSION, version);
    }

    /**
     * Sets the value coming from the xsl:output media-type stylesheet attribute.
     * @param mediaType the non-null media-type or MIME type associated with the
     * output document.
     * @see javax.xml.transform.OutputKeys#MEDIA_TYPE
     * @see SerializationHandler#setMediaType(String)
     */
    public void setMediaType(String mediaType) {
        setOutputProperty(OutputKeys.MEDIA_TYPE,mediaType);
    }

    /**
     * @return the number of spaces to indent for each indentation level.
     */
    public int getIndentAmount() {
        return m_indentAmount;
    }

    /**
     * Sets the indentation amount.
     * @param m_indentAmount The m_indentAmount to set
     */
    public void setIndentAmount(int m_indentAmount) {
        this.m_indentAmount = m_indentAmount;
    }

    /**
     * Sets the value coming from the xsl:output indent stylesheet
     * attribute.
     * @param doIndent true if the output document should be indented to
     * visually indicate its structure.
     * @see XSLOutputAttributes#setIndent(boolean)
     */
    public void setIndent(boolean doIndent) {
        String val = doIndent ? "yes":"no";
        setOutputProperty(OutputKeys.INDENT,val);
    }

    /**
     * Sets the isStandalone property
     * @param isStandalone true if the ORACLE_IS_STANDALONE is set to yes
     * @see OutputPropertiesFactory ORACLE_IS_STANDALONE
     */
    public void setIsStandalone(boolean isStandalone) {
       m_isStandalone = isStandalone;
    }

    /**
     * This method is used when a prefix/uri namespace mapping
     * is indicated after the element was started with a
     * startElement() and before and endElement().
     * startPrefixMapping(prefix,uri) would be used before the
     * startElement() call.
     * @param uri the URI of the namespace
     * @param prefix the prefix associated with the given URI.
     *
     * @see ExtendedContentHandler#namespaceAfterStartElement(String, String)
     */
    public void namespaceAfterStartElement(String uri, String prefix)
        throws SAXException
    {
        // default behavior is to do nothing
    }

    /**
     * Return a {@link DOMSerializer} interface into this serializer. If the
     * serializer does not support the {@link DOMSerializer} interface, it should
     * return null.
     *
     * @return A {@link DOMSerializer} interface into this serializer,  or null
     * if the serializer is not DOM capable
     * @throws IOException An I/O exception occured
     * @see Serializer#asDOMSerializer()
     */
    public DOMSerializer asDOMSerializer() throws IOException {
        return this;
    }

    /**
     * Tell if two strings are equal, without worry if the first string is null.
     *
     * @param p String reference, which may be null.
     * @param t String reference, which may be null.
     *
     * @return true if strings are equal.
     */
    private static final boolean subPartMatch(String p, String t) {
        return (p == t) || ((null != p) && (p.equals(t)));
    }

    /**
     * Returns the local name of a qualified name.
     * If the name has no prefix,
     * then it works as the identity (SAX2).
     *
     * @param qname a qualified name
     * @return returns the prefix of the qualified name,
     * or null if there is no prefix.
     */
    protected static final String getPrefixPart(String qname) {
        final int col = qname.indexOf(':');
        return (col > 0) ? qname.substring(0, col) : null;
        //return (col > 0) ? qname.substring(0,col) : "";
    }

    /**
     * Some users of the serializer may need the current namespace mappings
     * @return the current namespace mappings (prefix/uri)
     * @see ExtendedContentHandler#getNamespaceMappings()
     */
    public NamespaceMappings getNamespaceMappings() {
        return m_prefixMap;
    }

    /**
     * Returns the prefix currently pointing to the given URI (if any).
     * @param namespaceURI the uri of the namespace in question
     * @return a prefix pointing to the given URI (if any).
     * @see ExtendedContentHandler#getPrefix(String)
     */
    public String getPrefix(String namespaceURI) {
        String prefix = m_prefixMap.lookupPrefix(namespaceURI);
        return prefix;
    }

    /**
     * Returns the URI of an element or attribute. Note that default namespaces
     * do not apply directly to attributes.
     * @param qname a qualified name
     * @param isElement true if the qualified name is the name of
     * an element.
     * @return returns the namespace URI associated with the qualified name.
     */
    public String getNamespaceURI(String qname, boolean isElement) {
        String uri = EMPTYSTRING;
        int col = qname.lastIndexOf(':');
        final String prefix = (col > 0) ? qname.substring(0, col) : EMPTYSTRING;

        if (!EMPTYSTRING.equals(prefix) || isElement) {
            if (m_prefixMap != null) {
                uri = m_prefixMap.lookupNamespace(prefix);
                if (uri == null && !prefix.equals(XMLNS_PREFIX)) {
                    throw new RuntimeException(
                        Utils.messages.createMessage(
                            MsgKey.ER_NAMESPACE_PREFIX,
                            new Object[] { qname.substring(0, col) }  ));
                }
            }
        }
        return uri;
    }

    /**
     * Returns the URI of prefix (if any)
     *
         * @param prefix the prefix whose URI is searched for
     * @return the namespace URI currently associated with the
     * prefix, null if the prefix is undefined.
     */
    public String getNamespaceURIFromPrefix(String prefix) {
        String uri = null;
        if (m_prefixMap != null)
            uri = m_prefixMap.lookupNamespace(prefix);
        return uri;
    }

    /**
     * Entity reference event.
     *
     * @param name Name of entity
     *
     * @throws org.xml.sax.SAXException
     */
    public void entityReference(String name) throws org.xml.sax.SAXException {
        flushPending();

        startEntity(name);
        endEntity(name);

        if (m_tracer != null)
            fireEntityReference(name);
    }

    /**
     * Sets the transformer associated with this serializer
     * @param t the transformer associated with this serializer.
     * @see SerializationHandler#setTransformer(Transformer)
     */
    public void setTransformer(Transformer t) {
        m_transformer = t;

        // If this transformer object implements the SerializerTrace interface
        // then assign m_tracer to the transformer object so it can be used
        // to fire trace events.
        if ((m_transformer instanceof SerializerTrace) &&
            (((SerializerTrace) m_transformer).hasTraceListeners())) {
           m_tracer = (SerializerTrace) m_transformer;
        } else {
           m_tracer = null;
        }
    }

    /**
     * Gets the transformer associated with this serializer
     * @return returns the transformer associated with this serializer.
     * @see SerializationHandler#getTransformer()
     */
    public Transformer getTransformer() {
        return m_transformer;
    }

    /**
     * This method gets the nodes value as a String and uses that String as if
     * it were an input character notification.
     * @param node the Node to serialize
     * @throws org.xml.sax.SAXException
     */
    public void characters(org.w3c.dom.Node node)
        throws org.xml.sax.SAXException
    {
        flushPending();
        String data = node.getNodeValue();
        if (data != null) {
            final int length = data.length();
            if (length > m_charsBuff.length) {
                m_charsBuff = new char[length * 2 + 1];
            }
            data.getChars(0, length, m_charsBuff, 0);
            characters(m_charsBuff, 0, length);
        }
    }


    /**
     * @see org.xml.sax.ErrorHandler#error(SAXParseException)
     */
    public void error(SAXParseException exc) throws SAXException {
    }

    /**
     * @see org.xml.sax.ErrorHandler#fatalError(SAXParseException)
     */
    public void fatalError(SAXParseException exc) throws SAXException {
        m_elemContext.m_startTagOpen = false;
    }

    /**
     * @see org.xml.sax.ErrorHandler#warning(SAXParseException)
     */
    public void warning(SAXParseException exc) throws SAXException {
    }

    /**
     * To fire off start entity trace event
     * @param name Name of entity
     */
    protected void fireStartEntity(String name)
        throws org.xml.sax.SAXException
    {
        if (m_tracer != null)
        {
            flushMyWriter();
            m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_ENTITYREF, name);
        }
    }

    /**
     * This method is only used internally when flushing the writer from the
     * various fire...() trace events.  Due to the writer being wrapped with
     * SerializerTraceWriter it may cause the flush of these trace events:
     * EVENTTYPE_OUTPUT_PSEUDO_CHARACTERS
     * EVENTTYPE_OUTPUT_CHARACTERS
     * which trace the output written to the output stream.
     *
     */
    private void flushMyWriter() {
        if (m_writer != null) {
            try {
                m_writer.flush();
            } catch(IOException ioe) {
            }
        }
    }

    /**
     * Report the CDATA trace event
     * @param chars  content of CDATA
     * @param start  starting index of characters to output
     * @param length  number of characters to output
     */
    protected void fireCDATAEvent(char[] chars, int start, int length)
        throws org.xml.sax.SAXException
    {
        if (m_tracer != null) {
            flushMyWriter();
            m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_CDATA, chars, start,length);
        }
    }

    /**
     * Report the comment trace event
     * @param chars  content of comment
     * @param start  starting index of comment to output
     * @param length  number of characters to output
     */
    protected void fireCommentEvent(char[] chars, int start, int length)
        throws org.xml.sax.SAXException
    {
        if (m_tracer != null) {
            flushMyWriter();
            m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_COMMENT, new String(chars, start, length));
        }
    }


    /**
     * To fire off end entity trace event
     * @param name Name of entity
     */
    public void fireEndEntity(String name)
        throws org.xml.sax.SAXException
    {
        if (m_tracer != null)
            flushMyWriter();
        // we do not need to handle this.
    }

    /**
     * To fire off start document trace  event
     */
     protected void fireStartDoc()
        throws org.xml.sax.SAXException
    {
        if (m_tracer != null)
        {
            flushMyWriter();
            m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_STARTDOCUMENT);
        }
    }


    /**
     * To fire off end document trace event
     */
    protected void fireEndDoc()
        throws org.xml.sax.SAXException
    {
        if (m_tracer != null)
        {
            flushMyWriter();
            m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_ENDDOCUMENT);
        }
    }

    /**
     * Report the start element trace event. This trace method needs to be
     * called just before the attributes are cleared.
     *
     * @param elemName the qualified name of the element
     *
     */
    protected void fireStartElem(String elemName)
        throws org.xml.sax.SAXException
    {
        if (m_tracer != null)
        {
            flushMyWriter();
            m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_STARTELEMENT,
                elemName, m_attributes);
        }
    }


    /**
     * To fire off the end element event
     * @param name Name of element
     */
//    protected void fireEndElem(String name)
//        throws org.xml.sax.SAXException
//    {
//        if (m_tracer != null)
//            m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_ENDELEMENT,name, (Attributes)null);
//    }


    /**
     * To fire off the PI trace event
     * @param name Name of PI
     */
    protected void fireEscapingEvent(String name, String data)
        throws org.xml.sax.SAXException
    {

        if (m_tracer != null)
        {
            flushMyWriter();
            m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_PI,name, data);
        }
    }


    /**
     * To fire off the entity reference trace event
     * @param name Name of entity reference
     */
    protected void fireEntityReference(String name)
        throws org.xml.sax.SAXException
    {
        if (m_tracer != null)
        {
            flushMyWriter();
            m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_ENTITYREF,name, (Attributes)null);
        }
    }

    /**
     * Receive notification of the beginning of a document.
     * This method is never a self generated call,
     * but only called externally.
     *
     * <p>The SAX parser will invoke this method only once, before any
     * other methods in this interface or in DTDHandler (except for
     * setDocumentLocator).</p>
     *
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *            wrapping another exception.
     *
     * @throws org.xml.sax.SAXException
     */
    public void startDocument() throws org.xml.sax.SAXException
    {

        // if we do get called with startDocument(), handle it right away
        startDocumentInternal();
        m_needToCallStartDocument = false;
        return;
    }

    /**
     * This method handles what needs to be done at a startDocument() call,
     * whether from an external caller, or internally called in the
     * serializer.  For historical reasons the serializer is flexible to
     * startDocument() not always being called.
     * Even if no external call is
     * made into startDocument() this method will always be called as a self
     * generated internal startDocument, it handles what needs to be done at a
     * startDocument() call.
     *
     * This method exists just to make sure that startDocument() is only ever
     * called from an external caller, which in principle is just a matter of
     * style.
     *
     * @throws SAXException
     */
    protected void startDocumentInternal() throws org.xml.sax.SAXException
    {
        if (m_tracer != null)
            this.fireStartDoc();

    }

    /* This method extracts version and encoding information from SAX events.
     */
    protected void setDocumentInfo() {
        if (m_locator == null)
                return;
        try{
            String strVersion = ((Locator2)m_locator).getXMLVersion();
            if (strVersion != null)
                setVersion(strVersion);
            /*String strEncoding = ((Locator2)m_locator).getEncoding();
            if (strEncoding != null)
                setEncoding(strEncoding); */

        }catch(ClassCastException e){}
    }

    /**
     * This method is used to set the source locator, which might be used to
     * generated an error message.
     * @param locator the source locator
     *
     * @see ExtendedContentHandler#setSourceLocator(javax.xml.transform.SourceLocator)
     */
    public void setSourceLocator(SourceLocator locator) {
        m_sourceLocator = locator;
    }

    /**
     * Used only by TransformerSnapshotImpl to restore the serialization
     * to a previous state.
     *
     * @param mappings NamespaceMappings
     */
    public void setNamespaceMappings(NamespaceMappings mappings) {
        m_prefixMap = mappings;
    }

    public boolean reset() {
        resetSerializerBase();
        return true;
    }

    /**
     * Reset all of the fields owned by SerializerBase
     *
     */
    private void resetSerializerBase() {
        this.m_attributes.clear();
        this.m_StringOfCDATASections = null;
        this.m_elemContext = new ElemContext();
        this.m_doctypePublic = null;
        this.m_doctypeSystem = null;
        this.m_doIndent = false;
        this.m_indentAmount = 4;
        this.m_inEntityRef = 0;
        this.m_inExternalDTD = false;
        this.m_mediatype = null;
        this.m_needToCallStartDocument = true;
        this.m_needToOutputDocTypeDecl = false;
        if (this.m_prefixMap != null)
            this.m_prefixMap.reset();
        this.m_shouldNotWriteXMLHeader = false;
        this.m_sourceLocator = null;
        this.m_standalone = null;
        this.m_standaloneWasSpecified = false;
        this.m_tracer = null;
        this.m_transformer = null;
        this.m_version = null;
        // don't set writer to null, so that it might be re-used
        //this.m_writer = null;
    }

    /**
     * Returns true if the serializer is used for temporary output rather than
     * final output.
     *
     * This concept is made clear in the XSLT 2.0 draft.
     */
    final boolean inTemporaryOutputState() {
        /* This is a hack. We should really be letting the serializer know
         * that it is in temporary output state with an explicit call, but
         * from a pragmatic point of view (for now anyways) having no output
         * encoding at all, not even the default UTF-8 indicates that the
         * serializer is being used for temporary RTF.
         */
        return (getEncoding() == null);

    }

    /**
     * This method adds an attribute the the current element,
     * but should not be used for an xsl:attribute child.
     * @see ExtendedContentHandler#addAttribute(java.lang.String, java.lang.String,
     *          java.lang.String, java.lang.String, java.lang.String)
     */
    public void addAttribute(String uri, String localName, String rawName,
                             String type, String value) throws SAXException
    {
        if (m_elemContext.m_startTagOpen) {
            addAttributeAlways(uri, localName, rawName, type, value, false);
        }
    }

    /**
     * @see org.xml.sax.DTDHandler#notationDecl(java.lang.String,
     *          java.lang.String, java.lang.String)
     */
    public void notationDecl(String arg0, String arg1, String arg2)
        throws SAXException
    {
        // This method just provides a definition to satisfy the interface
        // A particular sub-class of SerializerBase provides the implementation
        // (if desired)
    }

    /**
     * @see org.xml.sax.DTDHandler#unparsedEntityDecl(java.lang.String,
     *          java.lang.String, java.lang.String, java.lang.String)
     */
    public void unparsedEntityDecl(String arg0, String arg1, String arg2,
                                   String arg3) throws SAXException {
        // This method just provides a definition to satisfy the interface
        // A particular sub-class of SerializerBase provides the implementation
        // (if desired)
    }

    /**
     * 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) {
        // This method just provides a definition to satisfy the interface
        // A particular sub-class of SerializerBase provides the implementation (if desired)
    }


    /**
     * The CDATA section names stored in a whitespace separateed list with
     * each element being a word of the form "{uri}localName" This list
     * comes from the cdata-section-elements attribute.
     *
     * This field replaces m_cdataSectionElements Vector.
     */
    protected String m_StringOfCDATASections = null;

    boolean m_docIsEmpty = true;
    void initCdataElems(String s)
    {
        if (s != null)
        {
            int max = s.length();

            // true if we are in the middle of a pair of curly braces that delimit a URI
            boolean inCurly = false;

            // true if we found a URI but haven't yet processed the local name
            boolean foundURI = false;

            StringBuilder buf = new StringBuilder();
            String uri = null;
            String localName = null;

            // 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 < max; i++)
            {
                char c = s.charAt(i);

                if (Character.isWhitespace(c))
                {
                    if (!inCurly)
                    {
                        if (buf.length() > 0)
                        {
                            localName = buf.toString();
                            if (!foundURI)
                                uri = "";
                            addCDATAElement(uri,localName);
                            buf.setLength(0);
                            foundURI = false;
                        }
                        continue;
                    }
                    else
                        buf.append(c); // add whitespace to the URI
                }
                else if ('{' == c) // starting a URI
                    inCurly = true;
                else if ('}' == c)
                {
                    // we just ended a URI
                    foundURI = true;
                    uri = buf.toString();
                    buf.setLength(0);
                    inCurly = false;
                }
                else
                {
                    // append non-whitespace, non-curly to current URI or localName being gathered.
                    buf.append(c);
                }

            }

            if (buf.length() > 0)
            {
                // We have one last localName to process.
                localName = buf.toString();
                if (!foundURI)
                    uri = "";
                addCDATAElement(uri,localName);
            }
        }
    }

    protected java.util.HashMap<String, HashMap<String, String>> m_CdataElems = null;
    private void addCDATAElement(String uri, String localName)
    {
        if (m_CdataElems == null) {
            m_CdataElems = new java.util.HashMap<>();
        }

        HashMap<String,String> h = m_CdataElems.get(localName);
        if (h == null) {
            h = new HashMap<>();
            m_CdataElems.put(localName,h);
        }
        h.put(uri,uri);

    }


    /**
     * Return true if nothing has been sent to this result tree yet.
     * <p>
     * This is not a public API.
     *
     * @xsl.usage internal
     */
    public boolean documentIsEmpty() {
        // If we haven't called startDocument() yet, then this document is empty
        return m_docIsEmpty && (m_elemContext.m_currentElemDepth == 0);
    }

    /**
     * Return true if the current element in m_elemContext
     * is a CDATA section.
     * CDATA sections are specified in the <xsl:output> attribute
     * cdata-section-names or in the JAXP equivalent property.
     * In any case the format of the value of such a property is:
     * <pre>
     * "{uri1}localName1 {uri2}localName2 . . . "
     * </pre>
     *
     * <p>
     * This method is not a public API, but is only used internally by the serializer.
     */
    protected boolean isCdataSection() {
        boolean b = false;

        if (null != m_StringOfCDATASections) {
            if (m_elemContext.m_elementLocalName == null) {
                String localName =  getLocalName(m_elemContext.m_elementName);
                m_elemContext.m_elementLocalName = localName;
            }

            if ( m_elemContext.m_elementURI == null) {

                m_elemContext.m_elementURI = getElementURI();
            }
            else if ( m_elemContext.m_elementURI.length() == 0) {
                if ( m_elemContext.m_elementName == null) {
                    m_elemContext.m_elementName = m_elemContext.m_elementLocalName;
                    // leave URI as "", meaning in no namespace
                }
                else if (m_elemContext.m_elementLocalName.length() < m_elemContext.m_elementName.length()){
                    // We were told the URI was "", yet the name has a prefix since the name is longer than the localname.
                    // So we will fix that incorrect information here.
                    m_elemContext.m_elementURI = getElementURI();
                }
            }

            HashMap<String, String> h = null;
            if (m_CdataElems != null) {
                h = m_CdataElems.get(m_elemContext.m_elementLocalName);
            }
            if (h != null) {
                Object obj = h.get(m_elemContext.m_elementURI);
                if (obj != null)
                    b = true;
            }

        }
        return b;
    }

    /**
     * Before this call m_elementContext.m_elementURI is null,
     * which means it is not yet known. After this call it
     * is non-null, but possibly "" meaning that it is in the
     * default namespace.
     *
     * @return The URI of the element, never null, but possibly "".
     */
    private String getElementURI() {
        String uri = null;
        // At this point in processing we have received all the
        // namespace mappings
        // As we still don't know the elements namespace,
        // we now figure it out.

        String prefix = getPrefixPart(m_elemContext.m_elementName);

        if (prefix == null) {
            // no prefix so lookup the URI of the default namespace
            uri = m_prefixMap.lookupNamespace("");
        } else {
            uri = m_prefixMap.lookupNamespace(prefix);
        }
        if (uri == null) {
            // We didn't find the namespace for the
            // prefix ... ouch, that shouldn't happen.
            // This is a hack, we really don't know
            // the namespace
            uri = EMPTYSTRING;
        }

        return uri;
    }


    /**
     * Get the value of an output property,
     * the explicit value, if any, otherwise the
     * default value, if any, otherwise null.
     */
    public String getOutputProperty(String name) {
        String val = getOutputPropertyNonDefault(name);
        // If no explicit value, try to get the default value
        if (val == null)
            val = getOutputPropertyDefault(name);
        return val;

    }
    /**
     * Get the value of an output property,
     * not the default value. If there is a default
     * value, but no non-default value this method
     * will return null.
     * <p>
     *
     */
    public String getOutputPropertyNonDefault(String name) {
        return getProp(name,false);
    }

    /**
     * Return a {@link DOM3Serializer} interface into this serializer. If the
     * serializer does not support the {@link DOM3Serializer} interface, it should
     * return null.
     *
     * @return A {@link DOM3Serializer} interface into this serializer,  or null
     * if the serializer is not DOM capable
     * @throws IOException An I/O exception occured
     * @see org.apache.xml.serializer.Serializer#asDOM3Serializer()
     */
    public Object asDOM3Serializer() throws IOException
    {
        return new com.sun.org.apache.xml.internal.serializer.dom3.DOM3SerializerImpl(this);
    }

    /**
     * Get the default value of an xsl:output property,
     * which would be null only if no default value exists
     * for the property.
     */
    public String getOutputPropertyDefault(String name) {
        return getProp(name, true);
    }

    /**
     * Set the value for the output property, typically from
     * an xsl:output element, but this does not change what
     * the default value is.
     */
    public void setOutputProperty(String name, String val) {
        setProp(name,val,false);
    }

    /**
     * Set the default value for an output property, but this does
     * not impact any explicitly set value.
     */
    public void setOutputPropertyDefault(String name, String val) {
        setProp(name,val,true);

    }

    /**
     * A mapping of keys to explicitly set values, for example if
     * and <xsl:output/> has an "encoding" attribute, this
     * map will have what that attribute maps to.
     */
    private HashMap<String, String> m_OutputProps;

    /**
     * A mapping of keys to default values, for example if
     * the default value of the encoding is "UTF-8" then this
     * map will have that "encoding" maps to "UTF-8".
     */
    private HashMap<String, String> m_OutputPropsDefault;

    Set<String> getOutputPropDefaultKeys() {
        return m_OutputPropsDefault.keySet();
    }

    Set<String> getOutputPropKeys() {
        return m_OutputProps.keySet();
    }

    private String getProp(String name, boolean defaultVal) {
        if (m_OutputProps == null) {
            m_OutputProps = new HashMap<>();
            m_OutputPropsDefault = new HashMap<>();
        }

        String val;
        if (defaultVal)
            val = m_OutputPropsDefault.get(name);
        else
            val = m_OutputProps.get(name);

        return val;
    }

    /**
     *
     * @param name The name of the property, e.g. "{http://myprop}indent-tabs" or "indent".
     * @param val The value of the property, e.g. "4"
     * @param defaultVal true if this is a default value being set for the property as
     * opposed to a user define on, set say explicitly in the stylesheet or via JAXP
     */
    void setProp(String name, String val, boolean defaultVal) {
        if (m_OutputProps == null) {
            m_OutputProps = new HashMap<>();
            m_OutputPropsDefault = new HashMap<>();
        }

        if (defaultVal)
            m_OutputPropsDefault.put(name,val);
        else {
            if (OutputKeys.CDATA_SECTION_ELEMENTS.equals(name) && val != null) {
                initCdataElems(val);
                String oldVal = m_OutputProps.get(name);
                String newVal;
                if (oldVal == null)
                    newVal = oldVal + ' ' + val;
                else
                    newVal = val;
                m_OutputProps.put(name,newVal);
            }
            else {
                m_OutputProps.put(name,val);
            }
        }
    }

    /**
     * Get the first char of the local name
     * @param name Either a local name, or a local name
     * preceeded by a uri enclosed in curly braces.
     */
    static char getFirstCharLocName(String name) {
        final char first;
        int i = name.indexOf('}');
        if (i < 0)
            first = name.charAt(0);
        else
            first = name.charAt(i+1);
        return first;
    }
}
