Save This Page
Home » openjdk-7 » com.sun.org.apache.xml.internal » serializer » [javadoc | source]
    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 >= 0x20 || (0x0A == ch || 0x0D == ch || 0x09 == ch))
  923                   ret= true;
  924               else
  925                   ret = false;
  926           }
  927           else {
  928               ret = m_encodingInfo.isInEncoding(ch);
  929           }
  930           return ret;
  931       }
  932   
  933       /**
  934        * Once a surrogate has been detected, write out the pair of
  935        * characters if it is in the encoding, or if there is no
  936        * encoding, otherwise write out an entity reference
  937        * of the value of the unicode code point of the character
  938        * represented by the high/low surrogate pair.
  939        * <p>
  940        * An exception is thrown if there is no low surrogate in the pair,
  941        * because the array ends unexpectely, or if the low char is there
  942        * but its value is such that it is not a low surrogate.
  943        *
  944        * @param c the first (high) part of the surrogate, which
  945        * must be confirmed before calling this method.
  946        * @param ch Character array.
  947        * @param i position Where the surrogate was detected.
  948        * @param end The end index of the significant characters.
  949        * @return 0 if the pair of characters was written out as-is,
  950        * the unicode code point of the character represented by
  951        * the surrogate pair if an entity reference with that value
  952        * was written out.
  953        *
  954        * @throws IOException
  955        * @throws org.xml.sax.SAXException if invalid UTF-16 surrogate detected.
  956        */
  957       protected int writeUTF16Surrogate(char c, char ch[], int i, int end)
  958           throws IOException
  959       {
  960           int codePoint = 0;
  961           if (i + 1 >= end)
  962           {
  963               throw new IOException(
  964                   Utils.messages.createMessage(
  965                       MsgKey.ER_INVALID_UTF16_SURROGATE,
  966                       new Object[] { Integer.toHexString((int) c)}));
  967           }
  968   
  969           final char high = c;
  970           final char low = ch[i+1];
  971           if (!Encodings.isLowUTF16Surrogate(low)) {
  972               throw new IOException(
  973                   Utils.messages.createMessage(
  974                       MsgKey.ER_INVALID_UTF16_SURROGATE,
  975                       new Object[] {
  976                           Integer.toHexString((int) c)
  977                               + " "
  978                               + Integer.toHexString(low)}));
  979           }
  980   
  981           final java.io.Writer writer = m_writer;
  982   
  983           // If we make it to here we have a valid high, low surrogate pair
  984           if (m_encodingInfo.isInEncoding(c,low)) {
  985               // If the character formed by the surrogate pair
  986               // is in the encoding, so just write it out
  987               writer.write(ch,i,2);
  988           }
  989           else {
  990               // Don't know what to do with this char, it is
  991               // not in the encoding and not a high char in
  992               // a surrogate pair, so write out as an entity ref
  993               final String encoding = getEncoding();
  994               if (encoding != null) {
  995                   /* The output encoding is known,
  996                    * so somthing is wrong.
  997                     */
  998                   codePoint = Encodings.toCodePoint(high, low);
  999                   // not in the encoding, so write out a character reference
 1000                   writer.write('&');
 1001                   writer.write('#');
 1002                   writer.write(Integer.toString(codePoint));
 1003                   writer.write(';');
 1004               } else {
 1005                   /* The output encoding is not known,
 1006                    * so just write it out as-is.
 1007                    */
 1008                   writer.write(ch, i, 2);
 1009               }
 1010           }
 1011           // non-zero only if character reference was written out.
 1012           return codePoint;
 1013       }
 1014   
 1015       /**
 1016        * Handle one of the default entities, return false if it
 1017        * is not a default entity.
 1018        *
 1019        * @param ch character to be escaped.
 1020        * @param i index into character array.
 1021        * @param chars non-null reference to character array.
 1022        * @param len length of chars.
 1023        * @param fromTextNode true if the characters being processed
 1024        * are from a text node, false if they are from an attribute value
 1025        * @param escLF true if the linefeed should be escaped.
 1026        *
 1027        * @return i+1 if the character was written, else i.
 1028        *
 1029        * @throws java.io.IOException
 1030        */
 1031       protected int accumDefaultEntity(
 1032           java.io.Writer writer,
 1033           char ch,
 1034           int i,
 1035           char[] chars,
 1036           int len,
 1037           boolean fromTextNode,
 1038           boolean escLF)
 1039           throws IOException
 1040       {
 1041   
 1042           if (!escLF && CharInfo.S_LINEFEED == ch)
 1043           {
 1044               writer.write(m_lineSep, 0, m_lineSepLen);
 1045           }
 1046           else
 1047           {
 1048               // if this is text node character and a special one of those,
 1049               // or if this is a character from attribute value and a special one of those
 1050               if ((fromTextNode && m_charInfo.isSpecialTextChar(ch)) || (!fromTextNode && m_charInfo.isSpecialAttrChar(ch)))
 1051               {
 1052                   String outputStringForChar = m_charInfo.getOutputStringForChar(ch);
 1053   
 1054                   if (null != outputStringForChar)
 1055                   {
 1056                       writer.write(outputStringForChar);
 1057                   }
 1058                   else
 1059                       return i;
 1060               }
 1061               else
 1062                   return i;
 1063           }
 1064   
 1065           return i + 1;
 1066   
 1067       }
 1068       /**
 1069        * Normalize the characters, but don't escape.
 1070        *
 1071        * @param ch The characters from the XML document.
 1072        * @param start The start position in the array.
 1073        * @param length The number of characters to read from the array.
 1074        * @param isCData true if a CDATA block should be built around the characters.
 1075        * @param useSystemLineSeparator true if the operating systems
 1076        * end-of-line separator should be output rather than a new-line character.
 1077        *
 1078        * @throws IOException
 1079        * @throws org.xml.sax.SAXException
 1080        */
 1081       void writeNormalizedChars(
 1082           char ch[],
 1083           int start,
 1084           int length,
 1085           boolean isCData,
 1086           boolean useSystemLineSeparator)
 1087           throws IOException, org.xml.sax.SAXException
 1088       {
 1089           final java.io.Writer writer = m_writer;
 1090           int end = start + length;
 1091   
 1092           for (int i = start; i < end; i++)
 1093           {
 1094               char c = ch[i];
 1095   
 1096               if (CharInfo.S_LINEFEED == c && useSystemLineSeparator)
 1097               {
 1098                   writer.write(m_lineSep, 0, m_lineSepLen);
 1099               }
 1100               else if (isCData && (!escapingNotNeeded(c)))
 1101               {
 1102                   //                if (i != 0)
 1103                   if (m_cdataTagOpen)
 1104                       closeCDATA();
 1105   
 1106                   // This needs to go into a function...
 1107                   if (Encodings.isHighUTF16Surrogate(c))
 1108                   {
 1109                       writeUTF16Surrogate(c, ch, i, end);
 1110                       i++ ; // process two input characters
 1111                   }
 1112                   else
 1113                   {
 1114                       writer.write("&#");
 1115   
 1116                       String intStr = Integer.toString((int) c);
 1117   
 1118                       writer.write(intStr);
 1119                       writer.write(';');
 1120                   }
 1121   
 1122                   //                if ((i != 0) && (i < (end - 1)))
 1123                   //                if (!m_cdataTagOpen && (i < (end - 1)))
 1124                   //                {
 1125                   //                    writer.write(CDATA_DELIMITER_OPEN);
 1126                   //                    m_cdataTagOpen = true;
 1127                   //                }
 1128               }
 1129               else if (
 1130                   isCData
 1131                       && ((i < (end - 2))
 1132                           && (']' == c)
 1133                           && (']' == ch[i + 1])
 1134                           && ('>' == ch[i + 2])))
 1135               {
 1136                   writer.write(CDATA_CONTINUE);
 1137   
 1138                   i += 2;
 1139               }
 1140               else
 1141               {
 1142                   if (escapingNotNeeded(c))
 1143                   {
 1144                       if (isCData && !m_cdataTagOpen)
 1145                       {
 1146                           writer.write(CDATA_DELIMITER_OPEN);
 1147                           m_cdataTagOpen = true;
 1148                       }
 1149                       writer.write(c);
 1150                   }
 1151   
 1152                   // This needs to go into a function...
 1153                   else if (Encodings.isHighUTF16Surrogate(c))
 1154                   {
 1155                       if (m_cdataTagOpen)
 1156                           closeCDATA();
 1157                       writeUTF16Surrogate(c, ch, i, end);
 1158                       i++; // process two input characters
 1159                   }
 1160                   else
 1161                   {
 1162                       if (m_cdataTagOpen)
 1163                           closeCDATA();
 1164                       writer.write("&#");
 1165   
 1166                       String intStr = Integer.toString((int) c);
 1167   
 1168                       writer.write(intStr);
 1169                       writer.write(';');
 1170                   }
 1171               }
 1172           }
 1173   
 1174       }
 1175   
 1176       /**
 1177        * Ends an un-escaping section.
 1178        *
 1179        * @see #startNonEscaping
 1180        *
 1181        * @throws org.xml.sax.SAXException
 1182        */
 1183       public void endNonEscaping() throws org.xml.sax.SAXException
 1184       {
 1185           m_disableOutputEscapingStates.pop();
 1186       }
 1187   
 1188       /**
 1189        * Starts an un-escaping section. All characters printed within an un-
 1190        * escaping section are printed as is, without escaping special characters
 1191        * into entity references. Only XML and HTML serializers need to support
 1192        * this method.
 1193        * <p> The contents of the un-escaping section will be delivered through the
 1194        * regular <tt>characters</tt> event.
 1195        *
 1196        * @throws org.xml.sax.SAXException
 1197        */
 1198       public void startNonEscaping() throws org.xml.sax.SAXException
 1199       {
 1200           m_disableOutputEscapingStates.push(true);
 1201       }
 1202   
 1203       /**
 1204        * Receive notification of cdata.
 1205        *
 1206        * <p>The Parser will call this method to report each chunk of
 1207        * character data.  SAX parsers may return all contiguous character
 1208        * data in a single chunk, or they may split it into several
 1209        * chunks; however, all of the characters in any single event
 1210        * must come from the same external entity, so that the Locator
 1211        * provides useful information.</p>
 1212        *
 1213        * <p>The application must not attempt to read from the array
 1214        * outside of the specified range.</p>
 1215        *
 1216        * <p>Note that some parsers will report whitespace using the
 1217        * ignorableWhitespace() method rather than this one (validating
 1218        * parsers must do so).</p>
 1219        *
 1220        * @param ch The characters from the XML document.
 1221        * @param start The start position in the array.
 1222        * @param length The number of characters to read from the array.
 1223        * @throws org.xml.sax.SAXException Any SAX exception, possibly
 1224        *            wrapping another exception.
 1225        * @see #ignorableWhitespace
 1226        * @see org.xml.sax.Locator
 1227        *
 1228        * @throws org.xml.sax.SAXException
 1229        */
 1230       protected void cdata(char ch[], int start, final int length)
 1231           throws org.xml.sax.SAXException
 1232       {
 1233   
 1234           try
 1235           {
 1236               final int old_start = start;
 1237               if (m_elemContext.m_startTagOpen)
 1238               {
 1239                   closeStartTag();
 1240                   m_elemContext.m_startTagOpen = false;
 1241               }
 1242               m_ispreserve = true;
 1243   
 1244               if (shouldIndent())
 1245                   indent();
 1246   
 1247               boolean writeCDataBrackets =
 1248                   (((length >= 1) && escapingNotNeeded(ch[start])));
 1249   
 1250               /* Write out the CDATA opening delimiter only if
 1251                * we are supposed to, and if we are not already in
 1252                * the middle of a CDATA section
 1253                */
 1254               if (writeCDataBrackets && !m_cdataTagOpen)
 1255               {
 1256                   m_writer.write(CDATA_DELIMITER_OPEN);
 1257                   m_cdataTagOpen = true;
 1258               }
 1259   
 1260               // writer.write(ch, start, length);
 1261               if (isEscapingDisabled())
 1262               {
 1263                   charactersRaw(ch, start, length);
 1264               }
 1265               else
 1266                   writeNormalizedChars(ch, start, length, true, m_lineSepUse);
 1267   
 1268               /* used to always write out CDATA closing delimiter here,
 1269                * but now we delay, so that we can merge CDATA sections on output.
 1270                * need to write closing delimiter later
 1271                */
 1272               if (writeCDataBrackets)
 1273               {
 1274                   /* if the CDATA section ends with ] don't leave it open
 1275                    * as there is a chance that an adjacent CDATA sections
 1276                    * starts with ]>.
 1277                    * We don't want to merge ]] with > , or ] with ]>
 1278                    */
 1279                   if (ch[start + length - 1] == ']')
 1280                       closeCDATA();
 1281               }
 1282   
 1283               // time to fire off CDATA event
 1284               if (m_tracer != null)
 1285                   super.fireCDATAEvent(ch, old_start, length);
 1286           }
 1287           catch (IOException ioe)
 1288           {
 1289               throw new org.xml.sax.SAXException(
 1290                   Utils.messages.createMessage(
 1291                       MsgKey.ER_OIERROR,
 1292                       null),
 1293                   ioe);
 1294               //"IO error", ioe);
 1295           }
 1296       }
 1297   
 1298       /**
 1299        * Tell if the character escaping should be disabled for the current state.
 1300        *
 1301        * @return true if the character escaping should be disabled.
 1302        */
 1303       private boolean isEscapingDisabled()
 1304       {
 1305           return m_disableOutputEscapingStates.peekOrFalse();
 1306       }
 1307   
 1308       /**
 1309        * If available, when the disable-output-escaping attribute is used,
 1310        * output raw text without escaping.
 1311        *
 1312        * @param ch The characters from the XML document.
 1313        * @param start The start position in the array.
 1314        * @param length The number of characters to read from the array.
 1315        *
 1316        * @throws org.xml.sax.SAXException
 1317        */
 1318       protected void charactersRaw(char ch[], int start, int length)
 1319           throws org.xml.sax.SAXException
 1320       {
 1321   
 1322           if (m_inEntityRef)
 1323               return;
 1324           try
 1325           {
 1326               if (m_elemContext.m_startTagOpen)
 1327               {
 1328                   closeStartTag();
 1329                   m_elemContext.m_startTagOpen = false;
 1330               }
 1331   
 1332               m_ispreserve = true;
 1333   
 1334               m_writer.write(ch, start, length);
 1335           }
 1336           catch (IOException e)
 1337           {
 1338               throw new SAXException(e);
 1339           }
 1340   
 1341       }
 1342   
 1343       /**
 1344        * Receive notification of character data.
 1345        *
 1346        * <p>The Parser will call this method to report each chunk of
 1347        * character data.  SAX parsers may return all contiguous character
 1348        * data in a single chunk, or they may split it into several
 1349        * chunks; however, all of the characters in any single event
 1350        * must come from the same external entity, so that the Locator
 1351        * provides useful information.</p>
 1352        *
 1353        * <p>The application must not attempt to read from the array
 1354        * outside of the specified range.</p>
 1355        *
 1356        * <p>Note that some parsers will report whitespace using the
 1357        * ignorableWhitespace() method rather than this one (validating
 1358        * parsers must do so).</p>
 1359        *
 1360        * @param chars The characters from the XML document.
 1361        * @param start The start position in the array.
 1362        * @param length The number of characters to read from the array.
 1363        * @throws org.xml.sax.SAXException Any SAX exception, possibly
 1364        *            wrapping another exception.
 1365        * @see #ignorableWhitespace
 1366        * @see org.xml.sax.Locator
 1367        *
 1368        * @throws org.xml.sax.SAXException
 1369        */
 1370       public void characters(final char chars[], final int start, final int length)
 1371           throws org.xml.sax.SAXException
 1372       {
 1373           // It does not make sense to continue with rest of the method if the number of
 1374           // characters to read from array is 0.
 1375           // Section 7.6.1 of XSLT 1.0 (http://www.w3.org/TR/xslt#value-of) suggest no text node
 1376           // is created if string is empty.
 1377           if (length == 0 || (m_inEntityRef && !m_expandDTDEntities))
 1378               return;
 1379           if (m_elemContext.m_startTagOpen)
 1380           {
 1381               closeStartTag();
 1382               m_elemContext.m_startTagOpen = false;
 1383           }
 1384           else if (m_needToCallStartDocument)
 1385           {
 1386               startDocumentInternal();
 1387           }
 1388   
 1389           if (m_cdataStartCalled || m_elemContext.m_isCdataSection)
 1390           {
 1391               /* either due to startCDATA() being called or due to
 1392                * cdata-section-elements atribute, we need this as cdata
 1393                */
 1394               cdata(chars, start, length);
 1395   
 1396               return;
 1397           }
 1398   
 1399           if (m_cdataTagOpen)
 1400               closeCDATA();
 1401           // the check with _escaping is a bit of a hack for XLSTC
 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               char ch1;
 1425               int startClean;
 1426   
 1427               // skip any leading whitspace
 1428               // don't go off the end and use a hand inlined version
 1429               // of isWhitespace(ch)
 1430               final int end = start + length;
 1431               int lastDirty = start - 1; // last character that needed processing
 1432               for (i = start;
 1433                   ((i < end)
 1434                       && ((ch1 = chars[i]) == 0x20
 1435                           || (ch1 == 0xA && m_lineSepUse)
 1436                           || ch1 == 0xD
 1437                           || ch1 == 0x09));
 1438                   i++)
 1439               {
 1440                   /*
 1441                    * We are processing leading whitespace, but are doing the same
 1442                    * processing for dirty characters here as for non-whitespace.
 1443                    *
 1444                    */
 1445                   if (!m_charInfo.isTextASCIIClean(ch1))
 1446                   {
 1447                       lastDirty = processDirty(chars,end, i,ch1, lastDirty, true);
 1448                       i = lastDirty;
 1449                   }
 1450               }
 1451               /* If there is some non-whitespace, mark that we may need
 1452                * to preserve this. This is only important if we have indentation on.
 1453                */
 1454               if (i < end)
 1455                   m_ispreserve = true;
 1456   
 1457   
 1458   //            int lengthClean;    // number of clean characters in a row
 1459   //            final boolean[] isAsciiClean = m_charInfo.getASCIIClean();
 1460   
 1461               final boolean isXML10 = XMLVERSION10.equals(getVersion());
 1462               // we've skipped the leading whitespace, now deal with the rest
 1463               for (; i < end; i++)
 1464               {
 1465                   {
 1466                       // A tight loop to skip over common clean chars
 1467                       // This tight loop makes it easier for the JIT
 1468                       // to optimize.
 1469                       char ch2;
 1470                       while (i<end
 1471                               && ((ch2 = chars[i])<127)
 1472                               && m_charInfo.isTextASCIIClean(ch2))
 1473                               i++;
 1474                       if (i == end)
 1475                           break;
 1476                   }
 1477   
 1478                   final char ch = chars[i];
 1479                   /*  The check for isCharacterInC0orC1Ranger and
 1480                    *  isNELorLSEPCharacter has been added
 1481                    *  to support Control Characters in XML 1.1
 1482                    */
 1483                   if (!isCharacterInC0orC1Range(ch) &&
 1484                       (isXML10 || !isNELorLSEPCharacter(ch)) &&
 1485                       (escapingNotNeeded(ch) && (!m_charInfo.isSpecialTextChar(ch)))
 1486                           || ('"' == ch))
 1487                   {
 1488                       ; // a character needing no special processing
 1489                   }
 1490                   else
 1491                   {
 1492                       lastDirty = processDirty(chars,end, i, ch, lastDirty, true);
 1493                       i = lastDirty;
 1494                   }
 1495               }
 1496   
 1497               // we've reached the end. Any clean characters at the
 1498               // end of the array than need to be written out?
 1499               startClean = lastDirty + 1;
 1500               if (i > startClean)
 1501               {
 1502                   int lengthClean = i - startClean;
 1503                   m_writer.write(chars, startClean, lengthClean);
 1504               }
 1505   
 1506               // For indentation purposes, mark that we've just writen text out
 1507               m_isprevtext = true;
 1508           }
 1509           catch (IOException e)
 1510           {
 1511               throw new SAXException(e);
 1512           }
 1513   
 1514           // time to fire off characters generation event
 1515           if (m_tracer != null)
 1516               super.fireCharEvent(chars, start, length);
 1517       }
 1518       /**
 1519        * This method checks if a given character is between C0 or C1 range
 1520        * of Control characters.
 1521        * This method is added to support Control Characters for XML 1.1
 1522        * If a given character is TAB (0x09), LF (0x0A) or CR (0x0D), this method
 1523        * return false. Since they are whitespace characters, no special processing is needed.
 1524        *
 1525        * @param ch
 1526        * @return boolean
 1527        */
 1528       private static boolean isCharacterInC0orC1Range(char ch)
 1529       {
 1530           if(ch == 0x09 || ch == 0x0A || ch == 0x0D)
 1531                   return false;
 1532           else
 1533                   return (ch >= 0x7F && ch <= 0x9F)|| (ch >= 0x01 && ch <= 0x1F);
 1534       }
 1535       /**
 1536        * This method checks if a given character either NEL (0x85) or LSEP (0x2028)
 1537        * These are new end of line charcters added in XML 1.1.  These characters must be
 1538        * written as Numeric Character References (NCR) in XML 1.1 output document.
 1539        *
 1540        * @param ch
 1541        * @return boolean
 1542        */
 1543       private static boolean isNELorLSEPCharacter(char ch)
 1544       {
 1545           return (ch == 0x85 || ch == 0x2028);
 1546       }
 1547       /**
 1548        * Process a dirty character and any preeceding clean characters
 1549        * that were not yet processed.
 1550        * @param chars array of characters being processed
 1551        * @param end one (1) beyond the last character
 1552        * in chars to be processed
 1553        * @param i the index of the dirty character
 1554        * @param ch the character in chars[i]
 1555        * @param lastDirty the last dirty character previous to i
 1556        * @param fromTextNode true if the characters being processed are
 1557        * from a text node, false if they are from an attribute value.
 1558        * @return the index of the last character processed
 1559        */
 1560       private int processDirty(
 1561           char[] chars,
 1562           int end,
 1563           int i,
 1564           char ch,
 1565           int lastDirty,
 1566           boolean fromTextNode) throws IOException
 1567       {
 1568           int startClean = lastDirty + 1;
 1569           // if we have some clean characters accumulated
 1570           // process them before the dirty one.
 1571           if (i > startClean)
 1572           {
 1573               int lengthClean = i - startClean;
 1574               m_writer.write(chars, startClean, lengthClean);
 1575           }
 1576   
 1577           // process the "dirty" character
 1578           if (CharInfo.S_LINEFEED == ch && fromTextNode)
 1579           {
 1580               m_writer.write(m_lineSep, 0, m_lineSepLen);
 1581           }
 1582           else
 1583           {
 1584               startClean =
 1585                   accumDefaultEscape(
 1586                       m_writer,
 1587                       (char)ch,
 1588                       i,
 1589                       chars,
 1590                       end,
 1591                       fromTextNode,
 1592                       false);
 1593               i = startClean - 1;
 1594           }
 1595           // Return the index of the last character that we just processed
 1596           // which is a dirty character.
 1597           return i;
 1598       }
 1599   
 1600       /**
 1601        * Receive notification of character data.
 1602        *
 1603        * @param s The string of characters to process.
 1604        *
 1605        * @throws org.xml.sax.SAXException
 1606        */
 1607       public void characters(String s) throws org.xml.sax.SAXException
 1608       {
 1609           if (m_inEntityRef && !m_expandDTDEntities)
 1610               return;
 1611           final int length = s.length();
 1612           if (length > m_charsBuff.length)
 1613           {
 1614               m_charsBuff = new char[length * 2 + 1];
 1615           }
 1616           s.getChars(0, length, m_charsBuff, 0);
 1617           characters(m_charsBuff, 0, length);
 1618       }
 1619   
 1620       /**
 1621        * Escape and writer.write a character.
 1622        *
 1623        * @param ch character to be escaped.
 1624        * @param i index into character array.
 1625        * @param chars non-null reference to character array.
 1626        * @param len length of chars.
 1627        * @param fromTextNode true if the characters being processed are
 1628        * from a text node, false if the characters being processed are from
 1629        * an attribute value.
 1630        * @param escLF true if the linefeed should be escaped.
 1631        *
 1632        * @return i+1 if a character was written, i+2 if two characters
 1633        * were written out, else return i.
 1634        *
 1635        * @throws org.xml.sax.SAXException
 1636        */
 1637       protected int accumDefaultEscape(
 1638           Writer writer,
 1639           char ch,
 1640           int i,
 1641           char[] chars,
 1642           int len,
 1643           boolean fromTextNode,
 1644           boolean escLF)
 1645           throws IOException
 1646       {
 1647   
 1648           int pos = accumDefaultEntity(writer, ch, i, chars, len, fromTextNode, escLF);
 1649   
 1650           if (i == pos)
 1651           {
 1652               if (Encodings.isHighUTF16Surrogate(ch))
 1653               {
 1654   
 1655                   // Should be the UTF-16 low surrogate of the hig/low pair.
 1656                   char next;
 1657                   // Unicode code point formed from the high/low pair.
 1658                   int codePoint = 0;
 1659   
 1660                   if (i + 1 >= len)
 1661                   {
 1662                       throw new IOException(
 1663                           Utils.messages.createMessage(
 1664                               MsgKey.ER_INVALID_UTF16_SURROGATE,
 1665                               new Object[] { Integer.toHexString(ch)}));
 1666                       //"Invalid UTF-16 surrogate detected: "
 1667   
 1668                       //+Integer.toHexString(ch)+ " ?");
 1669                   }
 1670                   else
 1671                   {
 1672                       next = chars[++i];
 1673   
 1674                       if (!(Encodings.isLowUTF16Surrogate(next)))
 1675                           throw new IOException(
 1676                               Utils.messages.createMessage(
 1677                                   MsgKey
 1678                                       .ER_INVALID_UTF16_SURROGATE,
 1679                                   new Object[] {
 1680                                       Integer.toHexString(ch)
 1681                                           + " "
 1682                                           + Integer.toHexString(next)}));
 1683                       //"Invalid UTF-16 surrogate detected: "
 1684   
 1685                       //+Integer.toHexString(ch)+" "+Integer.toHexString(next));
 1686                       codePoint = Encodings.toCodePoint(ch,next);
 1687                   }
 1688   
 1689                   writer.write("&#");
 1690                   writer.write(Integer.toString(codePoint));
 1691                   writer.write(';');
 1692                   pos += 2; // count the two characters that went into writing out this entity
 1693               }
 1694               else
 1695               {
 1696                   /*  This if check is added to support control characters in XML 1.1.
 1697                    *  If a character is a Control Character within C0 and C1 range, it is desirable
 1698                    *  to write it out as Numeric Character Reference(NCR) regardless of XML Version
 1699                    *  being used for output document.
 1700                    */
 1701                   if (isCharacterInC0orC1Range(ch) ||
 1702                           (XMLVERSION11.equals(getVersion()) && isNELorLSEPCharacter(ch)))
 1703                   {
 1704                       writer.write("&#");
 1705                       writer.write(Integer.toString(ch));
 1706                       writer.write(';');
 1707                   }
 1708                   else if ((!escapingNotNeeded(ch) ||
 1709                       (  (fromTextNode && m_charInfo.isSpecialTextChar(ch))
 1710                        || (!fromTextNode && m_charInfo.isSpecialAttrChar(ch))))
 1711                   && m_elemContext.m_currentElemDepth > 0)
 1712                   {
 1713                       writer.write("&#");
 1714                       writer.write(Integer.toString(ch));
 1715                       writer.write(';');
 1716                   }
 1717                   else
 1718                   {
 1719                       writer.write(ch);
 1720                   }
 1721                   pos++;  // count the single character that was processed
 1722               }
 1723   
 1724           }
 1725           return pos;
 1726       }
 1727   
 1728       /**
 1729        * Receive notification of the beginning of an element, although this is a
 1730        * SAX method additional namespace or attribute information can occur before
 1731        * or after this call, that is associated with this element.
 1732        *
 1733        *
 1734        * @param namespaceURI The Namespace URI, or the empty string if the
 1735        *        element has no Namespace URI or if Namespace
 1736        *        processing is not being performed.
 1737        * @param localName The local name (without prefix), or the
 1738        *        empty string if Namespace processing is not being
 1739        *        performed.
 1740        * @param name The element type name.
 1741        * @param atts The attributes attached to the element, if any.
 1742        * @throws org.xml.sax.SAXException Any SAX exception, possibly
 1743        *            wrapping another exception.
 1744        * @see org.xml.sax.ContentHandler#startElement
 1745        * @see org.xml.sax.ContentHandler#endElement
 1746        * @see org.xml.sax.AttributeList
 1747        *
 1748        * @throws org.xml.sax.SAXException
 1749        */
 1750       public void startElement(
 1751           String namespaceURI,
 1752           String localName,
 1753           String name,
 1754           Attributes atts)
 1755           throws org.xml.sax.SAXException
 1756       {
 1757           if (m_inEntityRef)
 1758               return;
 1759   
 1760           if (m_needToCallStartDocument)
 1761           {
 1762               startDocumentInternal();
 1763               m_needToCallStartDocument = false;
 1764           }
 1765           else if (m_cdataTagOpen)
 1766               closeCDATA();
 1767           try
 1768           {
 1769               if ((true == m_needToOutputDocTypeDecl)
 1770                   && (null != getDoctypeSystem()))
 1771               {
 1772                   outputDocTypeDecl(name, true);
 1773               }
 1774   
 1775               m_needToOutputDocTypeDecl = false;
 1776   
 1777               /* before we over-write the current elementLocalName etc.
 1778                * lets close out the old one (if we still need to)
 1779                */
 1780               if (m_elemContext.m_startTagOpen)
 1781               {
 1782                   closeStartTag();
 1783                   m_elemContext.m_startTagOpen = false;
 1784               }
 1785   
 1786               if (namespaceURI != null)
 1787                   ensurePrefixIsDeclared(namespaceURI, name);
 1788   
 1789               m_ispreserve = false;
 1790   
 1791               if (shouldIndent() && m_startNewLine)
 1792               {
 1793                   indent();
 1794               }
 1795   
 1796               m_startNewLine = true;
 1797   
 1798               final java.io.Writer writer = m_writer;
 1799               writer.write('<');
 1800               writer.write(name);
 1801           }
 1802           catch (IOException e)
 1803           {
 1804               throw new SAXException(e);
 1805           }
 1806   
 1807           // process the attributes now, because after this SAX call they might be gone
 1808           if (atts != null)
 1809               addAttributes(atts);
 1810   
 1811           m_elemContext = m_elemContext.push(namespaceURI,localName,name);
 1812           m_isprevtext = false;
 1813   
 1814           if (m_tracer != null){
 1815               firePseudoAttributes();
 1816           }
 1817   
 1818       }
 1819   
 1820       /**
 1821         * Receive notification of the beginning of an element, additional
 1822         * namespace or attribute information can occur before or after this call,
 1823         * that is associated with this element.
 1824         *
 1825         *
 1826         * @param elementNamespaceURI The Namespace URI, or the empty string if the
 1827         *        element has no Namespace URI or if Namespace
 1828         *        processing is not being performed.
 1829         * @param elementLocalName The local name (without prefix), or the
 1830         *        empty string if Namespace processing is not being
 1831         *        performed.
 1832         * @param elementName The element type name.
 1833         * @throws org.xml.sax.SAXException Any SAX exception, possibly
 1834         *            wrapping another exception.
 1835         * @see org.xml.sax.ContentHandler#startElement
 1836         * @see org.xml.sax.ContentHandler#endElement
 1837         * @see org.xml.sax.AttributeList
 1838         *
 1839         * @throws org.xml.sax.SAXException
 1840         */
 1841       public void startElement(
 1842           String elementNamespaceURI,
 1843           String elementLocalName,
 1844           String elementName)
 1845           throws SAXException
 1846       {
 1847           startElement(elementNamespaceURI, elementLocalName, elementName, null);
 1848       }
 1849   
 1850       public void startElement(String elementName) throws SAXException
 1851       {
 1852           startElement(null, null, elementName, null);
 1853       }
 1854   
 1855       /**
 1856        * Output the doc type declaration.
 1857        *
 1858        * @param name non-null reference to document type name.
 1859        * NEEDSDOC @param closeDecl
 1860        *
 1861        * @throws java.io.IOException
 1862        */
 1863       void outputDocTypeDecl(String name, boolean closeDecl) throws SAXException
 1864       {
 1865           if (m_cdataTagOpen)
 1866               closeCDATA();
 1867           try
 1868           {
 1869               final java.io.Writer writer = m_writer;
 1870               writer.write("<!DOCTYPE ");
 1871               writer.write(name);
 1872   
 1873               String doctypePublic = getDoctypePublic();
 1874               if (null != doctypePublic)
 1875               {
 1876                   writer.write(" PUBLIC \"");
 1877                   writer.write(doctypePublic);
 1878                   writer.write('\"');
 1879               }
 1880   
 1881               String doctypeSystem = getDoctypeSystem();
 1882               if (null != doctypeSystem)
 1883               {
 1884                   if (null == doctypePublic)
 1885                       writer.write(" SYSTEM \"");
 1886                   else
 1887                       writer.write(" \"");
 1888   
 1889                   writer.write(doctypeSystem);
 1890   
 1891                   if (closeDecl)
 1892                   {
 1893                       writer.write("\">");
 1894                       writer.write(m_lineSep, 0, m_lineSepLen);
 1895                       closeDecl = false; // done closing
 1896                   }
 1897                   else
 1898                       writer.write('\"');
 1899               }
 1900               boolean dothis = false;
 1901               if (dothis)
 1902               {
 1903                   // at one point this code seemed right,
 1904                   // but not anymore - Brian M.
 1905                   if (closeDecl)
 1906                   {
 1907                       writer.write('>');
 1908                       writer.write(m_lineSep, 0, m_lineSepLen);
 1909                   }
 1910               }
 1911           }
 1912           catch (IOException e)
 1913           {
 1914               throw new SAXException(e);
 1915           }
 1916       }
 1917   
 1918       /**
 1919        * Process the attributes, which means to write out the currently
 1920        * collected attributes to the writer. The attributes are not
 1921        * cleared by this method
 1922        *
 1923        * @param writer the writer to write processed attributes to.
 1924        * @param nAttrs the number of attributes in m_attributes
 1925        * to be processed
 1926        *
 1927        * @throws java.io.IOException
 1928        * @throws org.xml.sax.SAXException
 1929        */
 1930       public void processAttributes(java.io.Writer writer, int nAttrs) throws IOException, SAXException
 1931       {
 1932               /* real SAX attributes are not passed in, so process the
 1933                * attributes that were collected after the startElement call.
 1934                * _attribVector is a "cheap" list for Stream serializer output
 1935                * accumulated over a series of calls to attribute(name,value)
 1936                */
 1937               String encoding = getEncoding();
 1938               for (int i = 0; i < nAttrs; i++)
 1939               {
 1940                   // elementAt is JDK 1.1.8
 1941                   final String name = m_attributes.getQName(i);
 1942                   final String value = m_attributes.getValue(i);
 1943                   writer.write(' ');
 1944                   writer.write(name);
 1945                   writer.write("=\"");
 1946                   writeAttrString(writer, value, encoding);
 1947                   writer.write('\"');
 1948               }
 1949       }
 1950   
 1951       /**
 1952        * Returns the specified <var>string</var> after substituting <VAR>specials</VAR>,
 1953        * and UTF-16 surrogates for chracter references <CODE>&amp;#xnn</CODE>.
 1954        *
 1955        * @param   string      String to convert to XML format.
 1956        * @param   encoding    CURRENTLY NOT IMPLEMENTED.
 1957        *
 1958        * @throws java.io.IOException
 1959        */
 1960       public void writeAttrString(
 1961           Writer writer,
 1962           String string,
 1963           String encoding)
 1964           throws IOException
 1965       {
 1966           final int len = string.length();
 1967           if (len > m_attrBuff.length)
 1968           {
 1969              m_attrBuff = new char[len*2 + 1];
 1970           }
 1971           string.getChars(0,len, m_attrBuff, 0);
 1972           final char[] stringChars = m_attrBuff;
 1973   
 1974           for (int i = 0; i < len; )
 1975           {
 1976               char ch = stringChars[i];
 1977               if (escapingNotNeeded(ch) && (!m_charInfo.isSpecialAttrChar(ch)))
 1978               {
 1979                   writer.write(ch);
 1980                   i++;
 1981               }
 1982               else
 1983               { // I guess the parser doesn't normalize cr/lf in attributes. -sb
 1984   //                if ((CharInfo.S_CARRIAGERETURN == ch)
 1985   //                    && ((i + 1) < len)
 1986   //                    && (CharInfo.S_LINEFEED == stringChars[i + 1]))
 1987   //                {
 1988   //                    i++;
 1989   //                    ch = CharInfo.S_LINEFEED;
 1990   //                }
 1991   
 1992                   i = accumDefaultEscape(writer, ch, i, stringChars, len, false, true);
 1993               }
 1994           }
 1995   
 1996       }
 1997   
 1998       /**
 1999        * Receive notification of the end of an element.
 2000        *
 2001        *
 2002        * @param namespaceURI The Namespace URI, or the empty string if the
 2003        *        element has no Namespace URI or if Namespace
 2004        *        processing is not being performed.
 2005        * @param localName The local name (without prefix), or the
 2006        *        empty string if Namespace processing is not being
 2007        *        performed.
 2008        * @param name The element type name
 2009        * @throws org.xml.sax.SAXException Any SAX exception, possibly
 2010        *            wrapping another exception.
 2011        *
 2012        * @throws org.xml.sax.SAXException
 2013        */
 2014       public void endElement(String namespaceURI, String localName, String name)
 2015           throws org.xml.sax.SAXException
 2016       {
 2017   
 2018           if (m_inEntityRef)
 2019               return;
 2020   
 2021           // namespaces declared at the current depth are no longer valid
 2022           // so get rid of them
 2023           m_prefixMap.popNamespaces(m_elemContext.m_currentElemDepth, null);
 2024   
 2025           try
 2026           {
 2027               final java.io.Writer writer = m_writer;
 2028               if (m_elemContext.m_startTagOpen)
 2029               {
 2030                   if (m_tracer != null)
 2031                       super.fireStartElem(m_elemContext.m_elementName);
 2032                   int nAttrs = m_attributes.getLength();
 2033                   if (nAttrs > 0)
 2034                   {
 2035                       processAttributes(m_writer, nAttrs);
 2036                       // clear attributes object for re-use with next element
 2037                       m_attributes.clear();
 2038                   }
 2039                   if (m_spaceBeforeClose)
 2040                       writer.write(" />");
 2041                   else
 2042                       writer.write("/>");
 2043                   /* don't need to pop cdataSectionState because
 2044                    * this element ended so quickly that we didn't get
 2045                    * to push the state.
 2046                    */
 2047   
 2048               }
 2049               else
 2050               {
 2051                   if (m_cdataTagOpen)
 2052                       closeCDATA();
 2053   
 2054                   if (shouldIndent())
 2055                       indent(m_elemContext.m_currentElemDepth - 1);
 2056                   writer.write('<');
 2057                   writer.write('/');
 2058                   writer.write(name);
 2059                   writer.write('>');
 2060               }
 2061           }
 2062           catch (IOException e)
 2063           {
 2064               throw new SAXException(e);
 2065           }
 2066   
 2067           if (!m_elemContext.m_startTagOpen && m_doIndent)
 2068           {
 2069               m_ispreserve = m_preserves.isEmpty() ? false : m_preserves.pop();
 2070           }
 2071   
 2072           m_isprevtext = false;
 2073   
 2074           // fire off the end element event
 2075           if (m_tracer != null)
 2076               super.fireEndElem(name);
 2077           m_elemContext = m_elemContext.m_prev;
 2078       }
 2079   
 2080       /**
 2081        * Receive notification of the end of an element.
 2082        * @param name The element type name
 2083        * @throws org.xml.sax.SAXException Any SAX exception, possibly
 2084        *     wrapping another exception.
 2085        */
 2086       public void endElement(String name) throws org.xml.sax.SAXException
 2087       {
 2088           endElement(null, null, name);
 2089       }
 2090   
 2091       /**
 2092        * Begin the scope of a prefix-URI Namespace mapping
 2093        * just before another element is about to start.
 2094        * This call will close any open tags so that the prefix mapping
 2095        * will not apply to the current element, but the up comming child.
 2096        *
 2097        * @see org.xml.sax.ContentHandler#startPrefixMapping
 2098        *
 2099        * @param prefix The Namespace prefix being declared.
 2100        * @param uri The Namespace URI the prefix is mapped to.
 2101        *
 2102        * @throws org.xml.sax.SAXException The client may throw
 2103        *            an exception during processing.
 2104        *
 2105        */
 2106       public void startPrefixMapping(String prefix, String uri)
 2107           throws org.xml.sax.SAXException
 2108       {
 2109           // the "true" causes the flush of any open tags
 2110           startPrefixMapping(prefix, uri, true);
 2111       }
 2112   
 2113       /**
 2114        * Handle a prefix/uri mapping, which is associated with a startElement()
 2115        * that is soon to follow. Need to close any open start tag to make
 2116        * sure than any name space attributes due to this event are associated wih
 2117        * the up comming element, not the current one.
 2118        * @see ExtendedContentHandler#startPrefixMapping
 2119        *
 2120        * @param prefix The Namespace prefix being declared.
 2121        * @param uri The Namespace URI the prefix is mapped to.
 2122        * @param shouldFlush true if any open tags need to be closed first, this
 2123        * will impact which element the mapping applies to (open parent, or its up
 2124        * comming child)
 2125        * @return returns true if the call made a change to the current
 2126        * namespace information, false if it did not change anything, e.g. if the
 2127        * prefix/namespace mapping was already in scope from before.
 2128        *
 2129        * @throws org.xml.sax.SAXException The client may throw
 2130        *            an exception during processing.
 2131        *
 2132        *
 2133        */
 2134       public boolean startPrefixMapping(
 2135           String prefix,
 2136           String uri,
 2137           boolean shouldFlush)
 2138           throws org.xml.sax.SAXException
 2139       {
 2140   
 2141           /* Remember the mapping, and at what depth it was declared
 2142            * This is one greater than the current depth because these
 2143            * mappings will apply to the next depth. This is in
 2144            * consideration that startElement() will soon be called
 2145            */
 2146   
 2147           boolean pushed;
 2148           int pushDepth;
 2149           if (shouldFlush)
 2150           {
 2151               flushPending();
 2152               // the prefix mapping applies to the child element (one deeper)
 2153               pushDepth = m_elemContext.m_currentElemDepth + 1;
 2154           }
 2155           else
 2156           {
 2157               // the prefix mapping applies to the current element
 2158               pushDepth = m_elemContext.m_currentElemDepth;
 2159           }
 2160           pushed = m_prefixMap.pushNamespace(prefix, uri, pushDepth);
 2161   
 2162           if (pushed)
 2163           {
 2164               /* Brian M.: don't know if we really needto do this. The
 2165                * callers of this object should have injected both
 2166                * startPrefixMapping and the attributes.  We are
 2167                * just covering our butt here.
 2168                */
 2169               String name;
 2170               if (EMPTYSTRING.equals(prefix))
 2171               {
 2172                   name = "xmlns";
 2173                   addAttributeAlways(XMLNS_URI, name, name, "CDATA", uri, false);
 2174               }
 2175               else
 2176               {
 2177                   if (!EMPTYSTRING.equals(uri))
 2178                       // hack for XSLTC attribset16 test
 2179                   { // that maps ns1 prefix to "" URI
 2180                       name = "xmlns:" + prefix;
 2181   
 2182                       /* for something like xmlns:abc="w3.pretend.org"
 2183                        *  the      uri is the value, that is why we pass it in the
 2184                        * value, or 5th slot of addAttributeAlways()
 2185                        */
 2186                       addAttributeAlways(XMLNS_URI, prefix, name, "CDATA", uri, false);
 2187                   }
 2188               }
 2189           }
 2190           return pushed;
 2191       }
 2192   
 2193       /**
 2194        * Receive notification of an XML comment anywhere in the document. This
 2195        * callback will be used for comments inside or outside the document
 2196        * element, including comments in the external DTD subset (if read).
 2197        * @param ch An array holding the characters in the comment.
 2198        * @param start The starting position in the array.
 2199        * @param length The number of characters to use from the array.
 2200        * @throws org.xml.sax.SAXException The application may raise an exception.
 2201        */
 2202       public void comment(char ch[], int start, int length)
 2203           throws org.xml.sax.SAXException
 2204       {
 2205   
 2206           int start_old = start;
 2207           if (m_inEntityRef)
 2208               return;
 2209           if (m_elemContext.m_startTagOpen)
 2210           {
 2211               closeStartTag();
 2212               m_elemContext.m_startTagOpen = false;
 2213           }
 2214           else if (m_needToCallStartDocument)
 2215           {
 2216               startDocumentInternal();
 2217               m_needToCallStartDocument = false;
 2218           }
 2219   
 2220           try
 2221           {
 2222               if (shouldIndent())
 2223                   indent();
 2224   
 2225               final int limit = start + length;
 2226               boolean wasDash = false;
 2227               if (m_cdataTagOpen)
 2228                   closeCDATA();
 2229               final java.io.Writer writer = m_writer;
 2230               writer.write(COMMENT_BEGIN);
 2231               // Detect occurrences of two consecutive dashes, handle as necessary.
 2232               for (int i = start; i < limit; i++)
 2233               {
 2234                   if (wasDash && ch[i] == '-')
 2235                   {
 2236                       writer.write(ch, start, i - start);
 2237                       writer.write(" -");
 2238                       start = i + 1;
 2239                   }
 2240                   wasDash = (ch[i] == '-');
 2241               }
 2242   
 2243               // if we have some chars in the comment
 2244               if (length > 0)
 2245               {
 2246                   // Output the remaining characters (if any)
 2247                   final int remainingChars = (limit - start);
 2248                   if (remainingChars > 0)
 2249                       writer.write(ch, start, remainingChars);
 2250                   // Protect comment end from a single trailing dash
 2251                   if (ch[limit - 1] == '-')
 2252                       writer.write(' ');
 2253               }
 2254               writer.write(COMMENT_END);
 2255           }
 2256           catch (IOException e)
 2257           {
 2258               throw new SAXException(e);
 2259           }
 2260   
 2261           m_startNewLine = true;
 2262           // time to generate comment event
 2263           if (m_tracer != null)
 2264               super.fireCommentEvent(ch, start_old,length);
 2265       }
 2266   
 2267       /**
 2268        * Report the end of a CDATA section.
 2269        * @throws org.xml.sax.SAXException The application may raise an exception.
 2270        *
 2271        *  @see  #startCDATA
 2272        */
 2273       public void endCDATA() throws org.xml.sax.SAXException
 2274       {
 2275           if (m_cdataTagOpen)
 2276               closeCDATA();
 2277           m_cdataStartCalled = false;
 2278       }
 2279   
 2280       /**
 2281        * Report the end of DTD declarations.
 2282        * @throws org.xml.sax.SAXException The application may raise an exception.
 2283        * @see #startDTD
 2284        */
 2285       public void endDTD() throws org.xml.sax.SAXException
 2286       {
 2287           try
 2288           {
 2289               // Don't output doctype declaration until startDocumentInternal
 2290               // has been called. Otherwise, it can appear before XML decl.
 2291               if (m_needToCallStartDocument) {
 2292                   return;
 2293               }
 2294   
 2295               if (m_needToOutputDocTypeDecl)
 2296               {
 2297                   outputDocTypeDecl(m_elemContext.m_elementName, false);
 2298                   m_needToOutputDocTypeDecl = false;
 2299               }
 2300               final java.io.Writer writer = m_writer;
 2301               if (!m_inDoctype)
 2302                   writer.write("]>");
 2303               else
 2304               {
 2305                   writer.write('>');
 2306               }
 2307   
 2308               writer.write(m_lineSep, 0, m_lineSepLen);
 2309           }
 2310           catch (IOException e)
 2311           {
 2312               throw new SAXException(e);
 2313           }
 2314   
 2315       }
 2316   
 2317       /**
 2318        * End the scope of a prefix-URI Namespace mapping.
 2319        * @see org.xml.sax.ContentHandler#endPrefixMapping
 2320        *
 2321        * @param prefix The prefix that was being mapping.
 2322        * @throws org.xml.sax.SAXException The client may throw
 2323        *            an exception during processing.
 2324        */
 2325       public void endPrefixMapping(String prefix) throws org.xml.sax.SAXException
 2326       { // do nothing
 2327       }
 2328   
 2329       /**
 2330        * Receive notification of ignorable whitespace in element content.
 2331        *
 2332        * Not sure how to get this invoked quite yet.
 2333        *
 2334        * @param ch The characters from the XML document.
 2335        * @param start The start position in the array.
 2336        * @param length The number of characters to read from the array.
 2337        * @throws org.xml.sax.SAXException Any SAX exception, possibly
 2338        *            wrapping another exception.
 2339        * @see #characters
 2340        *
 2341        * @throws org.xml.sax.SAXException
 2342        */
 2343       public void ignorableWhitespace(char ch[], int start, int length)
 2344           throws org.xml.sax.SAXException
 2345       {
 2346   
 2347           if (0 == length)
 2348               return;
 2349           characters(ch, start, length);
 2350       }
 2351   
 2352       /**
 2353        * Receive notification of a skipped entity.
 2354        * @see org.xml.sax.ContentHandler#skippedEntity
 2355        *
 2356        * @param name The name of the skipped entity.  If it is a
 2357        *       parameter                   entity, the name will begin with '%',
 2358        * and if it is the external DTD subset, it will be the string
 2359        * "[dtd]".
 2360        * @throws org.xml.sax.SAXException Any SAX exception, possibly wrapping
 2361        * another exception.
 2362        */
 2363       public void skippedEntity(String name) throws org.xml.sax.SAXException
 2364       { // TODO: Should handle
 2365       }
 2366   
 2367       /**
 2368        * Report the start of a CDATA section.
 2369        *
 2370        * @throws org.xml.sax.SAXException The application may raise an exception.
 2371        * @see #endCDATA
 2372        */
 2373       public void startCDATA() throws org.xml.sax.SAXException
 2374       {
 2375           m_cdataStartCalled = true;
 2376       }
 2377   
 2378       /**
 2379        * Report the beginning of an entity.
 2380        *
 2381        * The start and end of the document entity are not reported.
 2382        * The start and end of the external DTD subset are reported
 2383        * using the pseudo-name "[dtd]".  All other events must be
 2384        * properly nested within start/end entity events.
 2385        *
 2386        * @param name The name of the entity.  If it is a parameter
 2387        *        entity, the name will begin with '%'.
 2388        * @throws org.xml.sax.SAXException The application may raise an exception.
 2389        * @see #endEntity
 2390        * @see org.xml.sax.ext.DeclHandler#internalEntityDecl
 2391        * @see org.xml.sax.ext.DeclHandler#externalEntityDecl
 2392        */
 2393       public void startEntity(String name) throws org.xml.sax.SAXException
 2394       {
 2395           if (name.equals("[dtd]"))
 2396               m_inExternalDTD = true;
 2397   
 2398           if (!m_expandDTDEntities && !m_inExternalDTD) {
 2399               /* Only leave the entity as-is if
 2400                * we've been told not to expand them
 2401                * and this is not the magic [dtd] name.
 2402                */
 2403               startNonEscaping();
 2404               characters("&" + name + ';');
 2405               endNonEscaping();
 2406           }
 2407   
 2408           m_inEntityRef = true;
 2409       }
 2410   
 2411       /**
 2412        * For the enclosing elements starting tag write out
 2413        * out any attributes followed by ">"
 2414        *
 2415        * @throws org.xml.sax.SAXException
 2416        */
 2417       protected void closeStartTag() throws SAXException
 2418       {
 2419           if (m_elemContext.m_startTagOpen)
 2420           {
 2421   
 2422               try
 2423               {
 2424                   if (m_tracer != null)
 2425                       super.fireStartElem(m_elemContext.m_elementName);
 2426                   int nAttrs = m_attributes.getLength();
 2427                   if (nAttrs > 0)
 2428                   {
 2429                        processAttributes(m_writer, nAttrs);
 2430                       // clear attributes object for re-use with next element
 2431                       m_attributes.clear();
 2432                   }
 2433                   m_writer.write('>');
 2434               }
 2435               catch (IOException e)
 2436               {
 2437                   throw new SAXException(e);
 2438               }
 2439   
 2440               /* whether Xalan or XSLTC, we have the prefix mappings now, so
 2441                * lets determine if the current element is specified in the cdata-
 2442                * section-elements list.
 2443                */
 2444               if (m_cdataSectionElements != null)
 2445                   m_elemContext.m_isCdataSection = isCdataSection();
 2446   
 2447               if (m_doIndent)
 2448               {
 2449                   m_isprevtext = false;
 2450                   m_preserves.push(m_ispreserve);
 2451               }
 2452           }
 2453   
 2454       }
 2455   
 2456       /**
 2457        * Report the start of DTD declarations, if any.
 2458        *
 2459        * Any declarations are assumed to be in the internal subset unless
 2460        * otherwise indicated.
 2461        *
 2462        * @param name The document type name.
 2463        * @param publicId The declared public identifier for the
 2464        *        external DTD subset, or null if none was declared.
 2465        * @param systemId The declared system identifier for the
 2466        *        external DTD subset, or null if none was declared.
 2467        * @throws org.xml.sax.SAXException The application may raise an
 2468        *            exception.
 2469        * @see #endDTD
 2470        * @see #startEntity
 2471        */
 2472       public void startDTD(String name, String publicId, String systemId)
 2473           throws org.xml.sax.SAXException
 2474       {
 2475           setDoctypeSystem(systemId);
 2476           setDoctypePublic(publicId);
 2477   
 2478           m_elemContext.m_elementName = name;
 2479           m_inDoctype = true;
 2480       }
 2481   
 2482       /**
 2483        * Returns the m_indentAmount.
 2484        * @return int
 2485        */
 2486       public int getIndentAmount()
 2487       {
 2488           return m_indentAmount;
 2489       }
 2490   
 2491       /**
 2492        * Sets the m_indentAmount.
 2493        *
 2494        * @param m_indentAmount The m_indentAmount to set
 2495        */
 2496       public void setIndentAmount(int m_indentAmount)
 2497       {
 2498           this.m_indentAmount = m_indentAmount;
 2499       }
 2500   
 2501       /**
 2502        * Tell if, based on space preservation constraints and the doIndent property,
 2503        * if an indent should occur.
 2504        *
 2505        * @return True if an indent should occur.
 2506        */
 2507       protected boolean shouldIndent()
 2508       {
 2509           return m_doIndent && (!m_ispreserve && !m_isprevtext);
 2510       }
 2511   
 2512       /**
 2513        * Searches for the list of qname properties with the specified key in the
 2514        * property list. If the key is not found in this property list, the default
 2515        * property list, and its defaults, recursively, are then checked. The
 2516        * method returns <code>null</code> if the property is not found.
 2517        *
 2518        * @param   key   the property key.
 2519        * @param props the list of properties to search in.
 2520        *
 2521        * Sets the vector of local-name/URI pairs of the cdata section elements
 2522        * specified in the cdata-section-elements property.
 2523        *
 2524        * This method is essentially a copy of getQNameProperties() from
 2525        * OutputProperties. Eventually this method should go away and a call
 2526        * to setCdataSectionElements(Vector v) should be made directly.
 2527        */
 2528       private void setCdataSectionElements(String key, Properties props)
 2529       {
 2530   
 2531           String s = props.getProperty(key);
 2532   
 2533           if (null != s)
 2534           {
 2535               // Vector of URI/LocalName pairs
 2536               Vector v = new Vector();
 2537               int l = s.length();
 2538               boolean inCurly = false;
 2539               StringBuffer buf = new StringBuffer();
 2540   
 2541               // parse through string, breaking on whitespaces.  I do this instead
 2542               // of a tokenizer so I can track whitespace inside of curly brackets,
 2543               // which theoretically shouldn't happen if they contain legal URLs.
 2544               for (int i = 0; i < l; i++)
 2545               {
 2546                   char c = s.charAt(i);
 2547   
 2548                   if (Character.isWhitespace(c))
 2549                   {
 2550                       if (!inCurly)
 2551                       {
 2552                           if (buf.length() > 0)
 2553                           {
 2554                               addCdataSectionElement(buf.toString(), v);
 2555                               buf.setLength(0);
 2556                           }
 2557                           continue;
 2558                       }
 2559                   }
 2560                   else if ('{' == c)
 2561                       inCurly = true;
 2562                   else if ('}' == c)
 2563                       inCurly = false;
 2564   
 2565                   buf.append(c);
 2566               }
 2567   
 2568               if (buf.length() > 0)
 2569               {
 2570                   addCdataSectionElement(buf.toString(), v);
 2571                   buf.setLength(0);
 2572               }
 2573               // call the official, public method to set the collected names
 2574               setCdataSectionElements(v);
 2575           }
 2576   
 2577       }
 2578   
 2579       /**
 2580        * Adds a URI/LocalName pair of strings to the list.
 2581        *
 2582        * @param URI_and_localName String of the form "{uri}local" or "local"
 2583        *
 2584        * @return a QName object
 2585        */
 2586       private void addCdataSectionElement(String URI_and_localName, Vector v)
 2587       {
 2588   
 2589           StringTokenizer tokenizer =
 2590               new StringTokenizer(URI_and_localName, "{}", false);
 2591           String s1 = tokenizer.nextToken();
 2592           String s2 = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
 2593   
 2594           if (null == s2)
 2595           {
 2596               // add null URI and the local name
 2597               v.addElement(null);
 2598               v.addElement(s1);
 2599           }
 2600           else
 2601           {
 2602               // add URI, then local name
 2603               v.addElement(s1);
 2604               v.addElement(s2);
 2605           }
 2606       }
 2607   
 2608       /**
 2609        * Remembers the cdata sections specified in the cdata-section-elements.
 2610        * The "official way to set URI and localName pairs.
 2611        * This method should be used by both Xalan and XSLTC.
 2612        *
 2613        * @param URI_and_localNames a vector of pairs of Strings (URI/local)
 2614        */
 2615       public void setCdataSectionElements(Vector URI_and_localNames)
 2616       {
 2617           m_cdataSectionElements = URI_and_localNames;
 2618       }
 2619   
 2620       /**
 2621        * Makes sure that the namespace URI for the given qualified attribute name
 2622        * is declared.
 2623        * @param ns the namespace URI
 2624        * @param rawName the qualified name
 2625        * @return returns null if no action is taken, otherwise it returns the
 2626        * prefix used in declaring the namespace.
 2627        * @throws SAXException
 2628        */
 2629       protected String ensureAttributesNamespaceIsDeclared(
 2630           String ns,
 2631           String localName,
 2632           String rawName)
 2633           throws org.xml.sax.SAXException
 2634       {
 2635   
 2636           if (ns != null && ns.length() > 0)
 2637           {
 2638   
 2639               // extract the prefix in front of the raw name
 2640               int index = 0;
 2641               String prefixFromRawName =
 2642                   (index = rawName.indexOf(":")) < 0
 2643                       ? ""
 2644                       : rawName.substring(0, index);
 2645   
 2646               if (index > 0)
 2647               {
 2648                   // we have a prefix, lets see if it maps to a namespace
 2649                   String uri = m_prefixMap.lookupNamespace(prefixFromRawName);
 2650                   if (uri != null && uri.equals(ns))
 2651                   {
 2652                       // the prefix in the raw name is already maps to the given namespace uri
 2653                       // so we don't need to do anything
 2654                       return null;
 2655                   }
 2656                   else
 2657                   {
 2658                       // The uri does not map to the prefix in the raw name,