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