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