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