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