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