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