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