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