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