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