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