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