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             if (shouldIndent() && m_isStandalone)
2397                 indent();
2398 
2399             final int limit = start + length;
2400             boolean wasDash = false;
2401             if (m_cdataTagOpen)
2402                 closeCDATA();
2403 
2404             if (shouldIndent() && !m_isStandalone)
2405                 indent();
2406 
2407             final java.io.Writer writer = m_writer;
2408             writer.write(COMMENT_BEGIN);
2409             // Detect occurrences of two consecutive dashes, handle as necessary.
2410             for (int i = start; i < limit; i++)
2411             {
2412                 if (wasDash && ch[i] == '-')
2413                 {
2414                     writer.write(ch, start, i - start);
2415                     writer.write(" -");
2416                     start = i + 1;
2417                 }
2418                 wasDash = (ch[i] == '-');
2419             }
2420 
2421             // if we have some chars in the comment
2422             if (length > 0)
2423             {
2424                 // Output the remaining characters (if any)
2425                 final int remainingChars = (limit - start);
2426                 if (remainingChars > 0)
2427                     writer.write(ch, start, remainingChars);
2428                 // Protect comment end from a single trailing dash
2429                 if (ch[limit - 1] == '-')
2430                     writer.write(' ');
2431             }
2432             writer.write(COMMENT_END);
2433         }
2434         catch (IOException e)
2435         {
2436             throw new SAXException(e);
2437         }
2438 
2439         /*
2440          * Don't write out any indentation whitespace now,
2441          * because there may be non-whitespace text after this.
2442          *
2443          * Simply mark that at this point if we do decide
2444          * to indent that we should
2445          * add a newline on the end of the current line before
2446          * the indentation at the start of the next line.
2447          */
2448         m_startNewLine = true;
2449         // time to generate comment event
2450         if (m_tracer != null)
2451             super.fireCommentEvent(ch, start_old,length);
2452     }
2453 
2454     /**
2455      * Report the end of a CDATA section.
2456      * @throws org.xml.sax.SAXException The application may raise an exception.
2457      *
2458      *  @see  #startCDATA
2459      */
2460     public void endCDATA() throws org.xml.sax.SAXException
2461     {
2462         if (m_cdataTagOpen)
2463             closeCDATA();
2464         m_cdataStartCalled = false;
2465     }
2466 
2467     /**
2468      * Report the end of DTD declarations.
2469      * @throws org.xml.sax.SAXException The application may raise an exception.
2470      * @see #startDTD
2471      */
2472     public void endDTD() throws org.xml.sax.SAXException
2473     {
2474         try
2475         {
2476             // Don't output doctype declaration until startDocumentInternal
2477             // has been called. Otherwise, it can appear before XML decl.
2478             if (m_needToCallStartDocument) {
2479                 return;
2480             }
2481 
2482             if (m_needToOutputDocTypeDecl)
2483             {
2484                 outputDocTypeDecl(m_elemContext.m_elementName, false);
2485                 m_needToOutputDocTypeDecl = false;
2486             }
2487             final java.io.Writer writer = m_writer;
2488             if (!m_inDoctype)
2489                 writer.write("]>");
2490             else
2491             {
2492                 writer.write('>');
2493             }
2494 
2495             writer.write(m_lineSep, 0, m_lineSepLen);
2496         }
2497         catch (IOException e)
2498         {
2499             throw new SAXException(e);
2500         }
2501 
2502     }
2503 
2504     /**
2505      * End the scope of a prefix-URI Namespace mapping.
2506      * @see org.xml.sax.ContentHandler#endPrefixMapping
2507      *
2508      * @param prefix The prefix that was being mapping.
2509      * @throws org.xml.sax.SAXException The client may throw
2510      *            an exception during processing.
2511      */
2512     public void endPrefixMapping(String prefix) throws org.xml.sax.SAXException
2513     { // do nothing
2514     }
2515 
2516     /**
2517      * Receive notification of ignorable whitespace in element content.
2518      *
2519      * Not sure how to get this invoked quite yet.
2520      *
2521      * @param ch The characters from the XML document.
2522      * @param start The start position in the array.
2523      * @param length The number of characters to read from the array.
2524      * @throws org.xml.sax.SAXException Any SAX exception, possibly
2525      *            wrapping another exception.
2526      * @see #characters
2527      *
2528      * @throws org.xml.sax.SAXException
2529      */
2530     public void ignorableWhitespace(char ch[], int start, int length)
2531         throws org.xml.sax.SAXException
2532     {
2533 
2534         if (0 == length)
2535             return;
2536         characters(ch, start, length);
2537     }
2538 
2539     /**
2540      * Receive notification of a skipped entity.
2541      * @see org.xml.sax.ContentHandler#skippedEntity
2542      *
2543      * @param name The name of the skipped entity.  If it is a
2544      *       parameter                   entity, the name will begin with '%',
2545      * and if it is the external DTD subset, it will be the string
2546      * "[dtd]".
2547      * @throws org.xml.sax.SAXException Any SAX exception, possibly wrapping
2548      * another exception.
2549      */
2550     public void skippedEntity(String name) throws org.xml.sax.SAXException
2551     { // TODO: Should handle
2552     }
2553 
2554     /**
2555      * Report the start of a CDATA section.
2556      *
2557      * @throws org.xml.sax.SAXException The application may raise an exception.
2558      * @see #endCDATA
2559      */
2560     public void startCDATA() throws org.xml.sax.SAXException
2561     {
2562         m_cdataStartCalled = true;
2563     }
2564 
2565     /**
2566      * Report the beginning of an entity.
2567      *
2568      * The start and end of the document entity are not reported.
2569      * The start and end of the external DTD subset are reported
2570      * using the pseudo-name "[dtd]".  All other events must be
2571      * properly nested within start/end entity events.
2572      *
2573      * @param name The name of the entity.  If it is a parameter
2574      *        entity, the name will begin with '%'.
2575      * @throws org.xml.sax.SAXException The application may raise an exception.
2576      * @see #endEntity
2577      * @see org.xml.sax.ext.DeclHandler#internalEntityDecl
2578      * @see org.xml.sax.ext.DeclHandler#externalEntityDecl
2579      */
2580     public void startEntity(String name) throws org.xml.sax.SAXException
2581     {
2582         if (name.equals("[dtd]"))
2583             m_inExternalDTD = true;
2584 
2585         if (!m_expandDTDEntities && !m_inExternalDTD) {
2586             /* Only leave the entity as-is if
2587              * we've been told not to expand them
2588              * and this is not the magic [dtd] name.
2589              */
2590             startNonEscaping();
2591             characters("&" + name + ';');
2592             endNonEscaping();
2593         }
2594 
2595         m_inEntityRef = true;
2596     }
2597 
2598     /**
2599      * For the enclosing elements starting tag write out
2600      * out any attributes followed by ">"
2601      *
2602      * @throws org.xml.sax.SAXException
2603      */
2604     protected void closeStartTag() throws SAXException
2605     {
2606         if (m_elemContext.m_startTagOpen)
2607         {
2608 
2609             try
2610             {
2611                 if (m_tracer != null)
2612                     super.fireStartElem(m_elemContext.m_elementName);
2613                 int nAttrs = m_attributes.getLength();
2614                 if (nAttrs > 0)
2615                 {
2616                      processAttributes(m_writer, nAttrs);
2617                     // clear attributes object for re-use with next element
2618                     m_attributes.clear();
2619                 }
2620                 m_writer.write('>');
2621             }
2622             catch (IOException e)
2623             {
2624                 throw new SAXException(e);
2625             }
2626 
2627             /* whether Xalan or XSLTC, we have the prefix mappings now, so
2628              * lets determine if the current element is specified in the cdata-
2629              * section-elements list.
2630              */
2631             if (m_cdataSectionElements != null)
2632                 m_elemContext.m_isCdataSection = isCdataSection();
2633 
2634             if (m_doIndent)
2635             {
2636                 m_isprevtext = false;
2637                 m_preserves.push(m_ispreserve);
2638             }
2639         }
2640 
2641     }
2642 
2643     /**
2644      * Report the start of DTD declarations, if any.
2645      *
2646      * Any declarations are assumed to be in the internal subset unless
2647      * otherwise indicated.
2648      *
2649      * @param name The document type name.
2650      * @param publicId The declared public identifier for the
2651      *        external DTD subset, or null if none was declared.
2652      * @param systemId The declared system identifier for the
2653      *        external DTD subset, or null if none was declared.
2654      * @throws org.xml.sax.SAXException The application may raise an
2655      *            exception.
2656      * @see #endDTD
2657      * @see #startEntity
2658      */
2659     public void startDTD(String name, String publicId, String systemId)
2660         throws org.xml.sax.SAXException
2661     {
2662         setDoctypeSystem(systemId);
2663         setDoctypePublic(publicId);
2664 
2665         m_elemContext.m_elementName = name;
2666         m_inDoctype = true;
2667     }
2668 
2669     /**
2670      * Returns the m_indentAmount.
2671      * @return int
2672      */
2673     public int getIndentAmount()
2674     {
2675         return m_indentAmount;
2676     }
2677 
2678     /**
2679      * Sets the m_indentAmount.
2680      *
2681      * @param m_indentAmount The m_indentAmount to set
2682      */
2683     public void setIndentAmount(int m_indentAmount)
2684     {
2685         this.m_indentAmount = m_indentAmount;
2686     }
2687 
2688     /**
2689      * Tell if, based on space preservation constraints and the doIndent property,
2690      * if an indent should occur.
2691      *
2692      * @return True if an indent should occur.
2693      */
2694     protected boolean shouldIndent()
2695     {
2696         return m_doIndent && (!m_ispreserve && !m_isprevtext) && (m_elemContext.m_currentElemDepth > 0 || m_isStandalone);
2697     }
2698 
2699     /**
2700      * Searches for the list of qname properties with the specified key in the
2701      * property list. If the key is not found in this property list, the default
2702      * property list, and its defaults, recursively, are then checked. The
2703      * method returns <code>null</code> if the property is not found.
2704      *
2705      * @param   key   the property key.
2706      * @param props the list of properties to search in.
2707      *
2708      * Sets the vector of local-name/URI pairs of the cdata section elements
2709      * specified in the cdata-section-elements property.
2710      *
2711      * This method is essentially a copy of getQNameProperties() from
2712      * OutputProperties. Eventually this method should go away and a call
2713      * to setCdataSectionElements(Vector v) should be made directly.
2714      */
2715     private void setCdataSectionElements(String key, Properties props)
2716     {
2717 
2718         String s = props.getProperty(key);
2719 
2720         if (null != s)
2721         {
2722             // Vector of URI/LocalName pairs
2723             Vector v = new Vector();
2724             int l = s.length();
2725             boolean inCurly = false;
2726             StringBuffer buf = new StringBuffer();
2727 
2728             // parse through string, breaking on whitespaces.  I do this instead
2729             // of a tokenizer so I can track whitespace inside of curly brackets,
2730             // which theoretically shouldn't happen if they contain legal URLs.
2731             for (int i = 0; i < l; i++)
2732             {
2733                 char c = s.charAt(i);
2734 
2735                 if (Character.isWhitespace(c))
2736                 {
2737                     if (!inCurly)
2738                     {
2739                         if (buf.length() > 0)
2740                         {
2741                             addCdataSectionElement(buf.toString(), v);
2742                             buf.setLength(0);
2743                         }
2744                         continue;
2745                     }
2746                 }
2747                 else if ('{' == c)
2748                     inCurly = true;
2749                 else if ('}' == c)
2750                     inCurly = false;
2751 
2752                 buf.append(c);
2753             }
2754 
2755             if (buf.length() > 0)
2756             {
2757                 addCdataSectionElement(buf.toString(), v);
2758                 buf.setLength(0);
2759             }
2760             // call the official, public method to set the collected names
2761             setCdataSectionElements(v);
2762         }
2763 
2764     }
2765 
2766     /**
2767      * Adds a URI/LocalName pair of strings to the list.
2768      *
2769      * @param URI_and_localName String of the form "{uri}local" or "local"
2770      *
2771      * @return a QName object
2772      */
2773     private void addCdataSectionElement(String URI_and_localName, Vector v)
2774     {
2775 
2776         StringTokenizer tokenizer =
2777             new StringTokenizer(URI_and_localName, "{}", false);
2778         String s1 = tokenizer.nextToken();
2779         String s2 = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
2780 
2781         if (null == s2)
2782         {
2783             // add null URI and the local name
2784             v.addElement(null);
2785             v.addElement(s1);
2786         }
2787         else
2788         {
2789             // add URI, then local name
2790             v.addElement(s1);
2791             v.addElement(s2);
2792         }
2793     }
2794 
2795     /**
2796      * Remembers the cdata sections specified in the cdata-section-elements.
2797      * The "official way to set URI and localName pairs.
2798      * This method should be used by both Xalan and XSLTC.
2799      *
2800      * @param URI_and_localNames a vector of pairs of Strings (URI/local)
2801      */
2802     public void setCdataSectionElements(Vector URI_and_localNames)
2803     {
2804         m_cdataSectionElements = URI_and_localNames;
2805     }
2806 
2807     /**
2808      * Makes sure that the namespace URI for the given qualified attribute name
2809      * is declared.
2810      * @param ns the namespace URI
2811      * @param rawName the qualified name
2812      * @return returns null if no action is taken, otherwise it returns the
2813      * prefix used in declaring the namespace.
2814      * @throws SAXException
2815      */
2816     protected String ensureAttributesNamespaceIsDeclared(
2817         String ns,
2818         String localName,
2819         String rawName)
2820         throws org.xml.sax.SAXException
2821     {
2822 
2823         if (ns != null && ns.length() > 0)
2824         {
2825 
2826             // extract the prefix in front of the raw name
2827             int index = 0;
2828             String prefixFromRawName =
2829                 (index = rawName.indexOf(":")) < 0
2830                     ? ""
2831                     : rawName.substring(0, index);
2832 
2833             if (index > 0)
2834             {
2835                 // we have a prefix, lets see if it maps to a namespace
2836                 String uri = m_prefixMap.lookupNamespace(prefixFromRawName);
2837                 if (uri != null && uri.equals(ns))
2838                 {
2839                     // the prefix in the raw name is already maps to the given namespace uri
2840                     // so we don't need to do anything
2841                     return null;
2842                 }
2843                 else
2844                 {
2845                     // The uri does not map to the prefix in the raw name,
2846                     // so lets make the mapping.
2847                     this.startPrefixMapping(prefixFromRawName, ns, false);
2848                     this.addAttribute(
2849                         "http://www.w3.org/2000/xmlns/",
2850                         prefixFromRawName,
2851                         "xmlns:" + prefixFromRawName,
2852                         "CDATA",
2853                         ns, false);
2854                     return prefixFromRawName;
2855                 }
2856             }
2857             else
2858             {
2859                 // we don't have a prefix in the raw name.
2860                 // Does the URI map to a prefix already?
2861                 String prefix = m_prefixMap.lookupPrefix(ns);
2862                 if (prefix == null)
2863                 {
2864                     // uri is not associated with a prefix,
2865                     // so lets generate a new prefix to use
2866                     prefix = m_prefixMap.generateNextPrefix();
2867                     this.startPrefixMapping(prefix, ns, false);
2868                     this.addAttribute(
2869                         "http://www.w3.org/2000/xmlns/",
2870                         prefix,
2871                         "xmlns:" + prefix,
2872                         "CDATA",
2873                         ns, false);
2874                 }
2875 
2876                 return prefix;
2877 
2878             }
2879         }
2880         return null;
2881     }
2882 
2883     void ensurePrefixIsDeclared(String ns, String rawName)
2884         throws org.xml.sax.SAXException
2885     {
2886 
2887         if (ns != null && ns.length() > 0)
2888         {
2889             int index;
2890             final boolean no_prefix = ((index = rawName.indexOf(":")) < 0);
2891             String prefix = (no_prefix) ? "" : rawName.substring(0, index);
2892 
2893             if (null != prefix)
2894             {
2895                 String foundURI = m_prefixMap.lookupNamespace(prefix);
2896 
2897                 if ((null == foundURI) || !foundURI.equals(ns))
2898                 {
2899                     this.startPrefixMapping(prefix, ns);
2900 
2901                     // Bugzilla1133: Generate attribute as well as namespace event.
2902                     // SAX does expect both.
2903 
2904                     this.addAttributeAlways(
2905                         "http://www.w3.org/2000/xmlns/",
2906                         no_prefix ? "xmlns" : prefix,  // local name
2907                         no_prefix ? "xmlns" : ("xmlns:"+ prefix), // qname
2908                         "CDATA",
2909                         ns,
2910                         false);
2911                 }
2912 
2913             }
2914         }
2915     }
2916 
2917     /**
2918      * This method flushes any pending events, which can be startDocument()
2919      * closing the opening tag of an element, or closing an open CDATA section.
2920      */
2921     public void flushPending() throws SAXException
2922     {
2923             if (m_needToCallStartDocument)
2924             {
2925                 startDocumentInternal();
2926                 m_needToCallStartDocument = false;
2927             }
2928             if (m_elemContext.m_startTagOpen)
2929             {
2930                 closeStartTag();
2931                 m_elemContext.m_startTagOpen = false;
2932             }
2933 
2934             if (m_cdataTagOpen)
2935             {
2936                 closeCDATA();
2937                 m_cdataTagOpen = false;
2938             }
2939             if (m_writer != null) {
2940                 try {
2941                     m_writer.flush();
2942     }
2943                 catch(IOException e) {
2944                     // what? me worry?
2945                 }
2946             }
2947     }
2948 
2949     public void setContentHandler(ContentHandler ch)
2950     {
2951         // this method is really only useful in the ToSAXHandler classes but it is
2952         // in the interface.  If the method defined here is ever called
2953         // we are probably in trouble.
2954     }
2955 
2956     /**
2957      * Adds the given attribute to the set of attributes, even if there is
2958      * no currently open element. This is useful if a SAX startPrefixMapping()
2959      * should need to add an attribute before the element name is seen.
2960      *
2961      * This method is a copy of its super classes method, except that some
2962      * tracing of events is done.  This is so the tracing is only done for
2963      * stream serializers, not for SAX ones.
2964      *
2965      * @param uri the URI of the attribute
2966      * @param localName the local name of the attribute
2967      * @param rawName   the qualified name of the attribute
2968      * @param type the type of the attribute (probably CDATA)
2969      * @param value the value of the attribute
2970      * @param xslAttribute true if this attribute is coming from an xsl:attribute element.
2971      * @return true if the attribute value was added,
2972      * false if the attribute already existed and the value was
2973      * replaced with the new value.
2974      */
2975     public boolean addAttributeAlways(
2976         String uri,
2977         String localName,
2978         String rawName,
2979         String type,
2980         String value,
2981         boolean xslAttribute)
2982     {
2983         boolean was_added;
2984         int index;
2985         //if (uri == null || localName == null || uri.length() == 0)
2986             index = m_attributes.getIndex(rawName);
2987         // Don't use 'localName' as it gives incorrect value, rely only on 'rawName'
2988         /*else {
2989             index = m_attributes.getIndex(uri, localName);
2990         }*/
2991         if (index >= 0)
2992         {
2993             String old_value = null;
2994             if (m_tracer != null)
2995             {
2996                 old_value = m_attributes.getValue(index);
2997                 if (value.equals(old_value))
2998                     old_value = null;
2999             }
3000 
3001             /* We've seen the attribute before.
3002              * We may have a null uri or localName, but all we really
3003              * want to re-set is the value anyway.
3004              */
3005             m_attributes.setValue(index, value);
3006             was_added = false;
3007             if (old_value != null){
3008                 firePseudoAttributes();
3009             }
3010 
3011         }
3012         else
3013         {
3014             // the attribute doesn't exist yet, create it
3015             if (xslAttribute)
3016             {
3017                 /*
3018                  * This attribute is from an xsl:attribute element so we take some care in
3019                  * adding it, e.g.
3020                  *   <elem1  foo:attr1="1" xmlns:foo="uri1">
3021                  *       <xsl:attribute name="foo:attr2">2</xsl:attribute>
3022                  *   </elem1>
3023                  *
3024                  * We are adding attr1 and attr2 both as attributes of elem1,
3025                  * and this code is adding attr2 (the xsl:attribute ).
3026                  * We could have a collision with the prefix like in the example above.
3027                  */
3028 
3029                 // In the example above, is there a prefix like foo ?
3030                 final int colonIndex = rawName.indexOf(':');
3031                 if (colonIndex > 0)
3032                 {
3033                     String prefix = rawName.substring(0,colonIndex);
3034                     NamespaceMappings.MappingRecord existing_mapping = m_prefixMap.getMappingFromPrefix(prefix);
3035 
3036                     /* Before adding this attribute (foo:attr2),
3037                      * is the prefix for it (foo) already mapped at the current depth?
3038                      */
3039                     if (existing_mapping != null
3040                     && existing_mapping.m_declarationDepth == m_elemContext.m_currentElemDepth
3041                     && !existing_mapping.m_uri.equals(uri))
3042                     {
3043                         /*
3044                          * There is an existing mapping of this prefix,
3045                          * it differs from the one we need,
3046                          * and unfortunately it is at the current depth so we
3047                          * can not over-ride it.
3048                          */
3049 
3050                         /*
3051                          * Are we lucky enough that an existing other prefix maps to this URI ?
3052                          */
3053                         prefix = m_prefixMap.lookupPrefix(uri);
3054                         if (prefix == null)
3055                         {
3056                             /* Unfortunately there is no existing prefix that happens to map to ours,
3057                              * so to avoid a prefix collision we must generated a new prefix to use.
3058                              * This is OK because the prefix URI mapping
3059                              * defined in the xsl:attribute is short in scope,
3060                              * just the xsl:attribute element itself,
3061                              * and at this point in serialization the body of the
3062                              * xsl:attribute, if any, is just a String. Right?
3063                              *   . . . I sure hope so - Brian M.
3064                              */
3065                             prefix = m_prefixMap.generateNextPrefix();
3066                         }
3067 
3068                         rawName = prefix + ':' + localName;
3069                     }
3070                 }
3071 
3072                 try
3073                 {
3074                     /* This is our last chance to make sure the namespace for this
3075                      * attribute is declared, especially if we just generated an alternate
3076                      * prefix to avoid a collision (the new prefix/rawName will go out of scope
3077                      * soon and be lost ...  last chance here.
3078                      */
3079                     String prefixUsed =
3080                         ensureAttributesNamespaceIsDeclared(
3081                             uri,
3082                             localName,
3083                             rawName);
3084                 }
3085                 catch (SAXException e)
3086                 {
3087                     // TODO Auto-generated catch block
3088                     e.printStackTrace();
3089                 }
3090             }
3091             m_attributes.addAttribute(uri, localName, rawName, type, value);
3092             was_added = true;
3093             if (m_tracer != null){
3094                 firePseudoAttributes();
3095             }
3096         }
3097         return was_added;
3098     }
3099 
3100     /**
3101      * To fire off the pseudo characters of attributes, as they currently
3102      * exist. This method should be called everytime an attribute is added,
3103      * or when an attribute value is changed, or an element is created.
3104      */
3105 
3106     protected void firePseudoAttributes()
3107     {
3108         if (m_tracer != null)
3109         {
3110             try
3111             {
3112                 // flush out the "<elemName" if not already flushed
3113                 m_writer.flush();
3114 
3115                 // make a StringBuffer to write the name="value" pairs to.
3116                 StringBuffer sb = new StringBuffer();
3117                 int nAttrs = m_attributes.getLength();
3118                 if (nAttrs > 0)
3119                 {
3120                     // make a writer that internally appends to the same
3121                     // StringBuffer
3122                     java.io.Writer writer =
3123                         new ToStream.WritertoStringBuffer(sb);
3124 
3125                     processAttributes(writer, nAttrs);
3126                     // Don't clear the attributes!
3127                     // We only want to see what would be written out
3128                     // at this point, we don't want to loose them.
3129                 }
3130                 sb.append('>');  // the potential > after the attributes.
3131                 // convert the StringBuffer to a char array and
3132                 // emit the trace event that these characters "might"
3133                 // be written
3134                 char ch[] = sb.toString().toCharArray();
3135                 m_tracer.fireGenerateEvent(
3136                     SerializerTrace.EVENTTYPE_OUTPUT_PSEUDO_CHARACTERS,
3137                     ch,
3138                     0,
3139                     ch.length);
3140             }
3141             catch (IOException ioe)
3142             {
3143                 // ignore ?
3144             }
3145             catch (SAXException se)
3146             {
3147                 // ignore ?
3148             }
3149         }
3150     }
3151 
3152     /**
3153      * This inner class is used only to collect attribute values
3154      * written by the method writeAttrString() into a string buffer.
3155      * In this manner trace events, and the real writing of attributes will use
3156      * the same code.
3157      */
3158     private class WritertoStringBuffer extends java.io.Writer
3159     {
3160         final private StringBuffer m_stringbuf;
3161         /**
3162          * @see java.io.Writer#write(char[], int, int)
3163          */
3164         WritertoStringBuffer(StringBuffer sb)
3165         {
3166             m_stringbuf = sb;
3167         }
3168 
3169         public void write(char[] arg0, int arg1, int arg2) throws IOException
3170         {
3171             m_stringbuf.append(arg0, arg1, arg2);
3172         }
3173         /**
3174          * @see java.io.Writer#flush()
3175          */
3176         public void flush() throws IOException
3177         {
3178         }
3179         /**
3180          * @see java.io.Writer#close()
3181          */
3182         public void close() throws IOException
3183         {
3184         }
3185 
3186         public void write(int i)
3187         {
3188             m_stringbuf.append((char) i);
3189         }
3190 
3191         public void write(String s)
3192         {
3193             m_stringbuf.append(s);
3194         }
3195     }
3196 
3197     /**
3198      * @see SerializationHandler#setTransformer(Transformer)
3199      */
3200     public void setTransformer(Transformer transformer) {
3201         super.setTransformer(transformer);
3202         if (m_tracer != null
3203          && !(m_writer instanceof SerializerTraceWriter)  )
3204             m_writer = new SerializerTraceWriter(m_writer, m_tracer);
3205 
3206 
3207     }
3208     /**
3209      * Try's to reset the super class and reset this class for
3210      * re-use, so that you don't need to create a new serializer
3211      * (mostly for performance reasons).
3212      *
3213      * @return true if the class was successfuly reset.
3214      */
3215     public boolean reset()
3216     {
3217         boolean wasReset = false;
3218         if (super.reset())
3219         {
3220             resetToStream();
3221             wasReset = true;
3222         }
3223         return wasReset;
3224     }
3225 
3226     /**
3227      * Reset all of the fields owned by ToStream class
3228      *
3229      */
3230     private void resetToStream()
3231     {
3232          this.m_cdataStartCalled = false;
3233          /* The stream is being reset. It is one of
3234           * ToXMLStream, ToHTMLStream ... and this type can't be changed
3235           * so neither should m_charInfo which is associated with the
3236           * type of Stream. Just leave m_charInfo as-is for the next re-use.
3237           *
3238           */
3239          // this.m_charInfo = null; // don't set to null
3240 
3241          this.m_disableOutputEscapingStates.clear();
3242 
3243          this.m_escaping = true;
3244          // Leave m_format alone for now - Brian M.
3245          // this.m_format = null;
3246          this.m_inDoctype = false;
3247          this.m_ispreserve = false;
3248          this.m_ispreserve = false;
3249          this.m_isprevtext = false;
3250          this.m_isUTF8 = false; //  ?? used anywhere ??
3251          this.m_preserves.clear();
3252          this.m_shouldFlush = true;
3253          this.m_spaceBeforeClose = false;
3254          this.m_startNewLine = false;
3255          this.m_lineSepUse = true;
3256          // DON'T SET THE WRITER TO NULL, IT MAY BE REUSED !!
3257          // this.m_writer = null;
3258          this.m_expandDTDEntities = true;
3259 
3260     }
3261 
3262     /**
3263       * Sets the character encoding coming from the xsl:output encoding stylesheet attribute.
3264       * @param encoding the character encoding
3265       */
3266      public void setEncoding(String encoding)
3267      {
3268          String old = getEncoding();
3269          super.setEncoding(encoding);
3270          if (old == null || !old.equals(encoding)) {
3271             // If we have changed the setting of the
3272             m_encodingInfo = Encodings.getEncodingInfo(encoding);
3273 
3274             if (encoding != null && m_encodingInfo.name == null) {
3275                 // We tried to get an EncodingInfo for Object for the given
3276                 // encoding, but it came back with an internall null name
3277                 // so the encoding is not supported by the JDK, issue a message.
3278                 String msg = Utils.messages.createMessage(
3279                                 MsgKey.ER_ENCODING_NOT_SUPPORTED,new Object[]{ encoding });
3280                 try
3281                 {
3282                         // Prepare to issue the warning message
3283                         Transformer tran = super.getTransformer();
3284                         if (tran != null) {
3285                                 ErrorListener errHandler = tran.getErrorListener();
3286                                 // Issue the warning message
3287                                 if (null != errHandler && m_sourceLocator != null)
3288                                         errHandler.warning(new TransformerException(msg, m_sourceLocator));
3289                                 else
3290                                         System.out.println(msg);
3291                     }
3292                         else
3293                                 System.out.println(msg);
3294                 }
3295                 catch (Exception e){}
3296             }
3297          }
3298          return;
3299      }
3300 
3301     /**
3302      * Simple stack for boolean values.
3303      *
3304      * This class is a copy of the one in com.sun.org.apache.xml.internal.utils.
3305      * It exists to cut the serializers dependancy on that package.
3306      * A minor changes from that package are:
3307      * doesn't implement Clonable
3308      *
3309      * @xsl.usage internal
3310      */
3311     static final class BoolStack
3312     {
3313 
3314       /** Array of boolean values          */
3315       private boolean m_values[];
3316 
3317       /** Array size allocated           */
3318       private int m_allocatedSize;
3319 
3320       /** Index into the array of booleans          */
3321       private int m_index;
3322 
3323       /**
3324        * Default constructor.  Note that the default
3325        * block size is very small, for small lists.
3326        */
3327       public BoolStack()
3328       {
3329         this(32);
3330       }
3331 
3332       /**
3333        * Construct a IntVector, using the given block size.
3334        *
3335        * @param size array size to allocate
3336        */
3337       public BoolStack(int size)
3338       {
3339 
3340         m_allocatedSize = size;
3341         m_values = new boolean[size];
3342         m_index = -1;
3343       }
3344 
3345       /**
3346        * Get the length of the list.
3347        *
3348        * @return Current length of the list
3349        */
3350       public final int size()
3351       {
3352         return m_index + 1;
3353       }
3354 
3355       /**
3356        * Clears the stack.
3357        *
3358        */
3359       public final void clear()
3360       {
3361         m_index = -1;
3362       }
3363 
3364       /**
3365        * Pushes an item onto the top of this stack.
3366        *
3367        *
3368        * @param val the boolean to be pushed onto this stack.
3369        * @return  the <code>item</code> argument.
3370        */
3371       public final boolean push(boolean val)
3372       {
3373 
3374         if (m_index == m_allocatedSize - 1)
3375           grow();
3376 
3377         return (m_values[++m_index] = val);
3378       }
3379 
3380       /**
3381        * Removes the object at the top of this stack and returns that
3382        * object as the value of this function.
3383        *
3384        * @return     The object at the top of this stack.
3385        * @throws  EmptyStackException  if this stack is empty.
3386        */
3387       public final boolean pop()
3388       {
3389         return m_values[m_index--];
3390       }
3391 
3392       /**
3393        * Removes the object at the top of this stack and returns the
3394        * next object at the top as the value of this function.
3395        *
3396        *
3397        * @return Next object to the top or false if none there
3398        */
3399       public final boolean popAndTop()
3400       {
3401 
3402         m_index--;
3403 
3404         return (m_index >= 0) ? m_values[m_index] : false;
3405       }
3406 
3407       /**
3408        * Set the item at the top of this stack
3409        *
3410        *
3411        * @param b Object to set at the top of this stack
3412        */
3413       public final void setTop(boolean b)
3414       {
3415         m_values[m_index] = b;
3416       }
3417 
3418       /**
3419        * Looks at the object at the top of this stack without removing it
3420        * from the stack.
3421        *
3422        * @return     the object at the top of this stack.
3423        * @throws  EmptyStackException  if this stack is empty.
3424        */
3425       public final boolean peek()
3426       {
3427         return m_values[m_index];
3428       }
3429 
3430       /**
3431        * Looks at the object at the top of this stack without removing it
3432        * from the stack.  If the stack is empty, it returns false.
3433        *
3434        * @return     the object at the top of this stack.
3435        */
3436       public final boolean peekOrFalse()
3437       {
3438         return (m_index > -1) ? m_values[m_index] : false;
3439       }
3440 
3441       /**
3442        * Looks at the object at the top of this stack without removing it
3443        * from the stack.  If the stack is empty, it returns true.
3444        *
3445        * @return     the object at the top of this stack.
3446        */
3447       public final boolean peekOrTrue()
3448       {
3449         return (m_index > -1) ? m_values[m_index] : true;
3450       }
3451 
3452       /**
3453        * Tests if this stack is empty.
3454        *
3455        * @return  <code>true</code> if this stack is empty;
3456        *          <code>false</code> otherwise.
3457        */
3458       public boolean isEmpty()
3459       {
3460         return (m_index == -1);
3461       }
3462 
3463       /**
3464        * Grows the size of the stack
3465        *
3466        */
3467       private void grow()
3468       {
3469 
3470         m_allocatedSize *= 2;
3471 
3472         boolean newVector[] = new boolean[m_allocatedSize];
3473 
3474         System.arraycopy(m_values, 0, newVector, 0, m_index + 1);
3475 
3476         m_values = newVector;
3477       }
3478     }
3479 
3480     // Implement DTDHandler
3481     /**
3482      * If this method is called, the serializer is used as a
3483      * DTDHandler, which changes behavior how the serializer
3484      * handles document entities.
3485      * @see org.xml.sax.DTDHandler#notationDecl(java.lang.String, java.lang.String, java.lang.String)
3486      */
3487     public void notationDecl(String name, String pubID, String sysID) throws SAXException {
3488         // TODO Auto-generated method stub
3489         try {
3490             DTDprolog();
3491 
3492             m_writer.write("<!NOTATION ");
3493             m_writer.write(name);
3494             if (pubID != null) {
3495                 m_writer.write(" PUBLIC \"");
3496                 m_writer.write(pubID);
3497 
3498             }
3499             else {
3500                 m_writer.write(" SYSTEM \"");
3501                 m_writer.write(sysID);
3502             }
3503             m_writer.write("\" >");
3504             m_writer.write(m_lineSep, 0, m_lineSepLen);
3505         } catch (IOException e) {
3506             // TODO Auto-generated catch block
3507             e.printStackTrace();
3508         }
3509     }
3510 
3511     /**
3512      * If this method is called, the serializer is used as a
3513      * DTDHandler, which changes behavior how the serializer
3514      * handles document entities.
3515      * @see org.xml.sax.DTDHandler#unparsedEntityDecl(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
3516      */
3517     public void unparsedEntityDecl(String name, String pubID, String sysID, String notationName) throws SAXException {
3518         // TODO Auto-generated method stub
3519         try {
3520             DTDprolog();
3521 
3522             m_writer.write("<!ENTITY ");
3523             m_writer.write(name);
3524             if (pubID != null) {
3525                 m_writer.write(" PUBLIC \"");
3526                 m_writer.write(pubID);
3527 
3528             }
3529             else {
3530                 m_writer.write(" SYSTEM \"");
3531                 m_writer.write(sysID);
3532             }
3533             m_writer.write("\" NDATA ");
3534             m_writer.write(notationName);
3535             m_writer.write(" >");
3536             m_writer.write(m_lineSep, 0, m_lineSepLen);
3537         } catch (IOException e) {
3538             // TODO Auto-generated catch block
3539             e.printStackTrace();
3540         }
3541     }
3542 
3543     /**
3544      * A private helper method to output the
3545      * @throws SAXException
3546      * @throws IOException
3547      */
3548     private void DTDprolog() throws SAXException, IOException {
3549         final java.io.Writer writer = m_writer;
3550         if (m_needToOutputDocTypeDecl)
3551         {
3552             outputDocTypeDecl(m_elemContext.m_elementName, false);
3553             m_needToOutputDocTypeDecl = false;
3554         }
3555         if (m_inDoctype)
3556         {
3557             writer.write(" [");
3558             writer.write(m_lineSep, 0, m_lineSepLen);
3559             m_inDoctype = false;
3560         }
3561     }
3562 
3563     /**
3564      * If set to false the serializer does not expand DTD entities,
3565      * but leaves them as is, the default value is true;
3566      */
3567     public void setDTDEntityExpansion(boolean expand) {
3568         m_expandDTDEntities = expand;
3569     }
3570 }