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: SerializerBase.java 471981 2006-11-07 04:28:00Z minchau $
020     */
021    package org.apache.xml.serializer;
022    
023    import java.io.IOException;
024    import java.util.HashMap;
025    import java.util.Set;
026    
027    import javax.xml.transform.OutputKeys;
028    import javax.xml.transform.SourceLocator;
029    import javax.xml.transform.Transformer;
030    
031    import org.apache.xml.serializer.utils.MsgKey;
032    import org.apache.xml.serializer.utils.Utils;
033    import org.xml.sax.Attributes;
034    import org.xml.sax.ContentHandler;
035    import org.xml.sax.Locator;
036    import org.xml.sax.SAXException;
037    import org.xml.sax.SAXParseException;
038    
039    
040    /**
041     * This class acts as a base class for the XML "serializers"
042     * and the stream serializers.
043     * It contains a number of common fields and methods.
044     * 
045     * @xsl.usage internal
046     */
047    public abstract class SerializerBase
048        implements SerializationHandler, SerializerConstants
049    {
050        SerializerBase() {
051            return;
052        }
053        
054        /**
055         * The name of the package that this class is in.
056         * <p>
057         * Not a public API.
058         */
059        public static final String PKG_NAME;
060    
061        /**
062         * The same as the name of the package that this class is in
063         * except that '.' are replaced with '/'.
064         * <p>
065         * Not a public API.
066         */
067        public static final String PKG_PATH;
068    
069        static {
070            String fullyQualifiedName = SerializerBase.class.getName();
071            int lastDot = fullyQualifiedName.lastIndexOf('.');
072            if (lastDot < 0) {
073                PKG_NAME = "";
074            } else {
075                PKG_NAME = fullyQualifiedName.substring(0, lastDot);
076            }
077    
078            StringBuffer sb = new StringBuffer();
079            for (int i = 0; i < PKG_NAME.length(); i++) {
080                char ch = PKG_NAME.charAt(i);
081                if (ch == '.')
082                    sb.append('/');
083                else
084                    sb.append(ch);
085            }
086            PKG_PATH = sb.toString();
087        }
088    
089        
090    
091        /**
092         * To fire off the end element trace event
093         * @param name Name of element
094         */
095        protected void fireEndElem(String name)
096            throws org.xml.sax.SAXException
097        {
098            if (m_tracer != null)
099            {
100                flushMyWriter();
101                m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_ENDELEMENT,name, (Attributes)null);
102            }                               
103        }
104    
105        /**
106         * Report the characters trace event
107         * @param chars  content of characters
108         * @param start  starting index of characters to output
109         * @param length  number of characters to output
110         */
111        protected void fireCharEvent(char[] chars, int start, int length)
112            throws org.xml.sax.SAXException
113        {
114            if (m_tracer != null)
115            {
116                flushMyWriter();
117                m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_CHARACTERS, chars, start,length);
118            }                               
119        }
120    
121        /**
122         * true if we still need to call startDocumentInternal() 
123             */
124        protected boolean m_needToCallStartDocument = true; 
125    
126        /** True if a trailing "]]>" still needs to be written to be
127         * written out. Used to merge adjacent CDATA sections
128         */
129        protected boolean m_cdataTagOpen = false;
130    
131        /**
132         * All the attributes of the current element, collected from
133         * startPrefixMapping() calls, or addAddtribute() calls, or 
134         * from the SAX attributes in a startElement() call.
135         */
136        protected AttributesImplSerializer m_attributes = new AttributesImplSerializer();
137    
138        /**
139         * Tells if we're in an EntityRef event.
140         */
141        protected boolean m_inEntityRef = false;
142    
143        /** This flag is set while receiving events from the external DTD */
144        protected boolean m_inExternalDTD = false;
145    
146        /**
147         * The System ID for the doc type.
148         */
149        protected String m_doctypeSystem;
150    
151        /**
152         * The public ID for the doc type.
153         */
154        protected String m_doctypePublic;
155    
156        /**
157         * Flag to tell that we need to add the doctype decl, which we can't do
158         * until the first element is encountered.
159         */
160        boolean m_needToOutputDocTypeDecl = true;
161    
162        /**
163         * Tells if we should write the XML declaration.
164         */
165        protected boolean m_shouldNotWriteXMLHeader = false;
166    
167        /**
168         * The standalone value for the doctype.
169         */
170        private String m_standalone;
171    
172        /**
173         * True if standalone was specified.
174         */
175        protected boolean m_standaloneWasSpecified = false;
176    
177        /**
178         * Flag to tell if indenting (pretty-printing) is on.
179         */
180        protected boolean m_doIndent = false;
181        /**
182         * Amount to indent.
183         */
184        protected int m_indentAmount = 0;
185    
186        /**
187         * Tells the XML version, for writing out to the XML decl.
188         */
189        protected String m_version = null;
190    
191        /**
192         * The mediatype.  Not used right now.
193         */
194        protected String m_mediatype;
195    
196        /**
197         * The transformer that was around when this output handler was created (if
198         * any).
199         */
200        private Transformer m_transformer;
201    
202        /**
203         * Namespace support, that keeps track of currently defined 
204         * prefix/uri mappings. As processed elements come and go, so do
205         * the associated mappings for that element.
206         */
207        protected NamespaceMappings m_prefixMap;
208        
209        /**
210         * Handle for firing generate events.  This interface may be implemented
211         * by the referenced transformer object.
212         */
213        protected SerializerTrace m_tracer;
214        
215        protected SourceLocator m_sourceLocator;
216        
217    
218        /**
219         * The writer to send output to. This field is only used in the ToStream
220         * serializers, but exists here just so that the fireStartDoc() and
221         * other fire... methods can flush this writer when tracing.
222         */
223        protected java.io.Writer m_writer = null;
224        
225        /**
226         * A reference to "stack frame" corresponding to
227         * the current element. Such a frame is pushed at a startElement()
228         * and popped at an endElement(). This frame contains information about
229         * the element, such as its namespace URI. 
230         */
231        protected ElemContext m_elemContext = new ElemContext();
232        
233        /**
234         * A utility buffer for converting Strings passed to
235         * character() methods to character arrays.
236         * Reusing this buffer means not creating a new character array
237         * everytime and it runs faster.
238         */
239        protected char[] m_charsBuff = new char[60];
240        
241        /**
242         * A utility buffer for converting Strings passed to
243         * attribute methods to character arrays.
244         * Reusing this buffer means not creating a new character array
245         * everytime and it runs faster.
246         */
247        protected char[] m_attrBuff = new char[30];    
248    
249        /**
250         * Receive notification of a comment.
251         * 
252         * @see ExtendedLexicalHandler#comment(String)
253         */
254        public void comment(String data) throws SAXException
255        {
256            m_docIsEmpty = false;
257            
258            final int length = data.length();
259            if (length > m_charsBuff.length)
260            {
261                m_charsBuff = new char[length * 2 + 1];
262            }
263            data.getChars(0, length, m_charsBuff, 0);
264            comment(m_charsBuff, 0, length);
265        }
266    
267        /**
268         * If at runtime, when the qname of the attribute is
269         * known, another prefix is specified for the attribute, then we can
270         * patch or hack the name with this method. For
271         * a qname of the form "ns?:otherprefix:name", this function patches the
272         * qname by simply ignoring "otherprefix".
273         * TODO: This method is a HACK! We do not have access to the
274         * XML file, it sometimes generates a NS prefix of the form "ns?" for
275         * an attribute.
276         */
277        protected String patchName(String qname)
278        {
279    
280            
281            final int lastColon = qname.lastIndexOf(':');
282    
283            if (lastColon > 0) {
284                final int firstColon = qname.indexOf(':');
285                final String prefix = qname.substring(0, firstColon);
286                final String localName = qname.substring(lastColon + 1);
287    
288            // If uri is "" then ignore prefix
289                final String uri = m_prefixMap.lookupNamespace(prefix);
290                if (uri != null && uri.length() == 0) {
291                    return localName;
292                }
293                else if (firstColon != lastColon) {
294                    return prefix + ':' + localName;
295                }
296            }
297            return qname;        
298        }
299    
300        /**
301         * Returns the local name of a qualified name. If the name has no prefix,
302         * then it works as the identity (SAX2).
303         * @param qname the qualified name 
304         * @return the name, but excluding any prefix and colon.
305         */
306        protected static String getLocalName(String qname)
307        {
308            final int col = qname.lastIndexOf(':');
309            return (col > 0) ? qname.substring(col + 1) : qname;
310        }
311    
312        /**
313         * Receive an object for locating the origin of SAX document events.
314         *
315         * @param locator An object that can return the location of any SAX document
316         * event.
317         * 
318         * Receive an object for locating the origin of SAX document events.
319         *
320         * <p>SAX parsers are strongly encouraged (though not absolutely
321         * required) to supply a locator: if it does so, it must supply
322         * the locator to the application by invoking this method before
323         * invoking any of the other methods in the DocumentHandler
324         * interface.</p>
325         *
326         * <p>The locator allows the application to determine the end
327         * position of any document-related event, even if the parser is
328         * not reporting an error.  Typically, the application will
329         * use this information for reporting its own errors (such as
330         * character content that does not match an application's
331         * business rules).  The information returned by the locator
332         * is probably not sufficient for use with a search engine.</p>
333         *
334         * <p>Note that the locator will return correct information only
335         * during the invocation of the events in this interface.  The
336         * application should not attempt to use it at any other time.</p>
337         */
338        public void setDocumentLocator(Locator locator)
339        {
340            return;
341    
342            // I don't do anything with this yet.
343        }
344    
345        /**
346         * Adds the given attribute to the set of collected attributes , but only if
347         * there is a currently open element.
348         * 
349         * An element is currently open if a startElement() notification has
350         * occured but the start of the element has not yet been written to the
351         * output.  In the stream case this means that we have not yet been forced
352         * to close the elements opening tag by another notification, such as a
353         * character notification.
354         * 
355         * @param uri the URI of the attribute
356         * @param localName the local name of the attribute
357         * @param rawName    the qualified name of the attribute
358         * @param type the type of the attribute (probably CDATA)
359         * @param value the value of the attribute
360         * @param XSLAttribute true if this attribute is coming from an xsl:attriute element
361         * @see ExtendedContentHandler#addAttribute(String, String, String, String, String)
362         */
363        public void addAttribute(
364            String uri,
365            String localName,
366            String rawName,
367            String type,
368            String value,
369            boolean XSLAttribute)
370            throws SAXException
371        {
372            if (m_elemContext.m_startTagOpen)
373            {
374                addAttributeAlways(uri, localName, rawName, type, value, XSLAttribute);
375            }
376    
377        }
378        
379        /**
380         * Adds the given attribute to the set of attributes, even if there is
381         * no currently open element. This is useful if a SAX startPrefixMapping()
382         * should need to add an attribute before the element name is seen.
383         * 
384         * @param uri the URI of the attribute
385         * @param localName the local name of the attribute
386         * @param rawName   the qualified name of the attribute
387         * @param type the type of the attribute (probably CDATA)
388         * @param value the value of the attribute
389         * @param XSLAttribute true if this attribute is coming from an xsl:attribute element
390         * @return true if the attribute was added, 
391         * false if an existing value was replaced.
392         */
393        public boolean addAttributeAlways(
394            String uri,
395            String localName,
396            String rawName,
397            String type,
398            String value,
399            boolean XSLAttribute)
400        {
401            boolean was_added;
402    //            final int index =
403    //                (localName == null || uri == null) ?
404    //                m_attributes.getIndex(rawName):m_attributes.getIndex(uri, localName);        
405                int index;
406    //            if (localName == null || uri == null){
407    //                index = m_attributes.getIndex(rawName);
408    //            }
409    //            else {
410    //                index = m_attributes.getIndex(uri, localName);
411    //            }
412                if (localName == null || uri == null || uri.length() == 0)
413                    index = m_attributes.getIndex(rawName);
414                else {
415                    index = m_attributes.getIndex(uri,localName);
416                }
417                if (index >= 0)
418                {
419                    /* We've seen the attribute before.
420                     * We may have a null uri or localName, but all
421                     * we really want to re-set is the value anyway.
422                     */
423                    m_attributes.setValue(index,value);
424                    was_added = false;
425                }
426                else
427                {
428                    // the attribute doesn't exist yet, create it
429                    m_attributes.addAttribute(uri, localName, rawName, type, value);
430                    was_added = true;
431                }
432                return was_added;
433            
434        }
435      
436    
437        /**
438         *  Adds  the given attribute to the set of collected attributes, 
439         * but only if there is a currently open element.
440         *
441         * @param name the attribute's qualified name
442         * @param value the value of the attribute
443         */
444        public void addAttribute(String name, final String value)
445        {
446            if (m_elemContext.m_startTagOpen)
447            {
448                final String patchedName = patchName(name);
449                final String localName = getLocalName(patchedName);
450                final String uri = getNamespaceURI(patchedName, false);
451    
452                addAttributeAlways(uri,localName, patchedName, "CDATA", value, false);
453             }
454        }    
455    
456        /**
457         * Adds the given xsl:attribute to the set of collected attributes, 
458         * but only if there is a currently open element.
459         *
460         * @param name the attribute's qualified name (prefix:localName)
461         * @param value the value of the attribute
462         * @param uri the URI that the prefix of the name points to
463         */
464        public void addXSLAttribute(String name, final String value, final String uri)
465        {
466            if (m_elemContext.m_startTagOpen)
467            {
468                final String patchedName = patchName(name);
469                final String localName = getLocalName(patchedName);
470    
471                addAttributeAlways(uri,localName, patchedName, "CDATA", value, true);
472             }
473        } 
474    
475        /**
476         * Add the given attributes to the currently collected ones. These
477         * attributes are always added, regardless of whether on not an element
478         * is currently open.
479         * @param atts List of attributes to add to this list
480         */
481        public void addAttributes(Attributes atts) throws SAXException
482        {
483    
484            int nAtts = atts.getLength();
485    
486            for (int i = 0; i < nAtts; i++)
487            {
488                String uri = atts.getURI(i);
489    
490                if (null == uri)
491                    uri = "";
492    
493                addAttributeAlways(
494                    uri,
495                    atts.getLocalName(i),
496                    atts.getQName(i),
497                    atts.getType(i),
498                    atts.getValue(i),
499                    false);
500    
501            }
502        }
503    
504        /**
505         * Return a {@link ContentHandler} interface into this serializer.
506         * If the serializer does not support the {@link ContentHandler}
507         * interface, it should return null.
508         *
509         * @return A {@link ContentHandler} interface into this serializer,
510         *  or null if the serializer is not SAX 2 capable
511         * @throws IOException An I/O exception occured
512         */
513        public ContentHandler asContentHandler() throws IOException
514        {
515            return this;
516        }
517    
518        /**
519         * Report the end of an entity.
520         *
521         * @param name The name of the entity that is ending.
522         * @throws org.xml.sax.SAXException The application may raise an exception.
523         * @see #startEntity
524         */
525        public void endEntity(String name) throws org.xml.sax.SAXException
526        {
527            if (name.equals("[dtd]"))
528                m_inExternalDTD = false;
529            m_inEntityRef = false;
530    
531            if (m_tracer != null)
532                this.fireEndEntity(name);        
533        }
534    
535        /**
536         * Flush and close the underlying java.io.Writer. This method applies to
537         * ToStream serializers, not ToSAXHandler serializers.
538         * @see ToStream
539         */
540        public void close()
541        {
542            // do nothing (base behavior)
543        }
544    
545        /**
546         * Initialize global variables
547         */
548        protected void initCDATA()
549        {
550            // CDATA stack
551            //        _cdataStack = new Stack();
552            //        _cdataStack.push(new Integer(-1)); // push dummy value
553        }
554    
555        /**
556         * Returns the character encoding to be used in the output document.
557         * @return the character encoding to be used in the output document.
558         */
559        public String getEncoding()
560        {
561            return getOutputProperty(OutputKeys.ENCODING);
562        }
563    
564       /**
565         * Sets the character encoding coming from the xsl:output encoding stylesheet attribute.
566         * @param m_encoding the character encoding
567         */
568        public void setEncoding(String encoding)
569        {
570            setOutputProperty(OutputKeys.ENCODING,encoding);
571        }
572    
573        /**
574         * Sets the value coming from the xsl:output omit-xml-declaration stylesheet attribute
575         * @param b true if the XML declaration is to be omitted from the output
576         * document.
577         */
578        public void setOmitXMLDeclaration(boolean b)
579        {
580            String val = b ? "yes":"no";
581            setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,val);
582        }
583    
584    
585        /**
586         * @return true if the XML declaration is to be omitted from the output
587         * document.
588         */
589        public boolean getOmitXMLDeclaration()
590        {
591            return m_shouldNotWriteXMLHeader;
592        }
593    
594        /**
595         * Returns the previously set value of the value to be used as the public
596         * identifier in the document type declaration (DTD).
597         * 
598         *@return the public identifier to be used in the DOCTYPE declaration in the
599         * output document.
600         */    
601        public String getDoctypePublic()
602        {
603            return m_doctypePublic;
604        }
605    
606        /** Set the value coming from the xsl:output doctype-public stylesheet attribute.
607          * @param doctypePublic the public identifier to be used in the DOCTYPE
608          * declaration in the output document.
609          */
610        public void setDoctypePublic(String doctypePublic)
611        {
612            setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, doctypePublic);
613        }
614    
615    
616        /**
617         * Returns the previously set value of the value to be used
618         * as the system identifier in the document type declaration (DTD).
619             * @return the system identifier to be used in the DOCTYPE declaration in
620             * the output document.
621         *
622         */    
623        public String getDoctypeSystem()
624        {
625            return m_doctypeSystem;
626        }
627    
628        /** Set the value coming from the xsl:output doctype-system stylesheet attribute.
629          * @param doctypeSystem the system identifier to be used in the DOCTYPE
630          * declaration in the output document.
631          */
632        public void setDoctypeSystem(String doctypeSystem)
633        {
634            setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doctypeSystem);
635        }
636    
637        /** Set the value coming from the xsl:output doctype-public and doctype-system stylesheet properties
638         * @param doctypeSystem the system identifier to be used in the DOCTYPE
639         * declaration in the output document.
640         * @param doctypePublic the public identifier to be used in the DOCTYPE
641         * declaration in the output document.
642         */
643        public void setDoctype(String doctypeSystem, String doctypePublic)
644        {
645            setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doctypeSystem);
646            setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, doctypePublic);
647        }
648    
649        /**
650         * Sets the value coming from the xsl:output standalone stylesheet attribute.
651         * @param standalone a value of "yes" indicates that the
652         * <code>standalone</code> delaration is to be included in the output
653         * document. This method remembers if the value was explicitly set using
654         * this method, verses if the value is the default value.
655         */
656        public void setStandalone(String standalone)
657        {
658            setOutputProperty(OutputKeys.STANDALONE, standalone);
659        }
660        /**
661         * Sets the XSL standalone attribute, but does not remember if this is a
662         * default or explicite setting.
663         * @param standalone "yes" | "no"
664         */    
665        protected void setStandaloneInternal(String standalone)
666        {
667            if ("yes".equals(standalone))
668                m_standalone = "yes";
669            else
670                m_standalone = "no";
671            
672        }
673    
674        /**
675         * Gets the XSL standalone attribute
676         * @return a value of "yes" if the <code>standalone</code> delaration is to
677         * be included in the output document.
678         *  @see XSLOutputAttributes#getStandalone()
679         */
680        public String getStandalone()
681        {
682            return m_standalone;
683        }
684    
685        /**
686         * @return true if the output document should be indented to visually
687         * indicate its structure.
688         */
689        public boolean getIndent()
690        {
691            return m_doIndent;
692        }
693        /**
694         * Gets the mediatype the media-type or MIME type associated with the output
695         * document.
696         * @return the mediatype the media-type or MIME type associated with the
697         * output document.
698         */
699        public String getMediaType()
700        {
701            return m_mediatype;
702        }
703    
704        /**
705         * Gets the version of the output format.
706         * @return the version of the output format.
707         */
708        public String getVersion()
709        {
710            return m_version;
711        }
712    
713        /**
714         * Sets the value coming from the xsl:output version attribute.
715         * @param version the version of the output format.
716         * @see SerializationHandler#setVersion(String)
717         */
718        public void setVersion(String version)
719        {
720            setOutputProperty(OutputKeys.VERSION, version);
721        }
722    
723        /**
724         * Sets the value coming from the xsl:output media-type stylesheet attribute.
725         * @param mediaType the non-null media-type or MIME type associated with the
726         * output document.
727         * @see javax.xml.transform.OutputKeys#MEDIA_TYPE
728         * @see SerializationHandler#setMediaType(String)
729         */
730        public void setMediaType(String mediaType)
731        {
732            setOutputProperty(OutputKeys.MEDIA_TYPE,mediaType);
733        }
734    
735        /**
736         * @return the number of spaces to indent for each indentation level.
737         */
738        public int getIndentAmount()
739        {
740            return m_indentAmount;
741        }
742    
743        /**
744         * Sets the indentation amount.
745         * @param m_indentAmount The m_indentAmount to set
746         */
747        public void setIndentAmount(int m_indentAmount)
748        {
749            this.m_indentAmount = m_indentAmount;
750        }
751    
752        /**
753         * Sets the value coming from the xsl:output indent stylesheet
754         * attribute.
755         * @param doIndent true if the output document should be indented to
756         * visually indicate its structure.
757         * @see XSLOutputAttributes#setIndent(boolean)
758         */
759        public void setIndent(boolean doIndent)
760        {
761            String val = doIndent ? "yes":"no";
762            setOutputProperty(OutputKeys.INDENT,val);
763        }
764    
765        /**
766         * This method is used when a prefix/uri namespace mapping
767         * is indicated after the element was started with a 
768         * startElement() and before and endElement().
769         * startPrefixMapping(prefix,uri) would be used before the
770         * startElement() call.
771         * @param uri the URI of the namespace
772         * @param prefix the prefix associated with the given URI.
773         * 
774         * @see ExtendedContentHandler#namespaceAfterStartElement(String, String)
775         */
776        public void namespaceAfterStartElement(String uri, String prefix)
777            throws SAXException
778        {
779            // default behavior is to do nothing
780        }
781    
782        /**
783         * Return a {@link DOMSerializer} interface into this serializer. If the
784         * serializer does not support the {@link DOMSerializer} interface, it should
785         * return null.
786         *
787         * @return A {@link DOMSerializer} interface into this serializer,  or null
788         * if the serializer is not DOM capable
789         * @throws IOException An I/O exception occured
790         * @see Serializer#asDOMSerializer()
791         */
792        public DOMSerializer asDOMSerializer() throws IOException
793        { 
794            return this;
795        }
796    
797        /**
798         * Tell if two strings are equal, without worry if the first string is null.
799         *
800         * @param p String reference, which may be null.
801         * @param t String reference, which may be null.
802         *
803         * @return true if strings are equal.
804         */
805        private static final boolean subPartMatch(String p, String t)
806        {
807            return (p == t) || ((null != p) && (p.equals(t)));
808        }
809    
810        /**
811         * Returns the local name of a qualified name. 
812         * If the name has no prefix,
813         * then it works as the identity (SAX2). 
814         * 
815         * @param qname a qualified name
816         * @return returns the prefix of the qualified name,
817         * or null if there is no prefix.
818         */
819        protected static final String getPrefixPart(String qname)
820        {
821            final int col = qname.indexOf(':');
822            return (col > 0) ? qname.substring(0, col) : null;
823            //return (col > 0) ? qname.substring(0,col) : "";
824        }
825    
826        /**
827         * Some users of the serializer may need the current namespace mappings
828         * @return the current namespace mappings (prefix/uri)
829         * @see ExtendedContentHandler#getNamespaceMappings()
830         */
831        public NamespaceMappings getNamespaceMappings()
832        {
833            return m_prefixMap;
834        }
835    
836        /**
837         * Returns the prefix currently pointing to the given URI (if any).
838         * @param namespaceURI the uri of the namespace in question
839         * @return a prefix pointing to the given URI (if any).
840         * @see ExtendedContentHandler#getPrefix(String)
841         */
842        public String getPrefix(String namespaceURI)
843        {
844            String prefix = m_prefixMap.lookupPrefix(namespaceURI);
845            return prefix;
846        }
847    
848        /**
849         * Returns the URI of an element or attribute. Note that default namespaces
850         * do not apply directly to attributes.
851         * @param qname a qualified name
852         * @param isElement true if the qualified name is the name of 
853         * an element.
854         * @return returns the namespace URI associated with the qualified name.
855         */
856        public String getNamespaceURI(String qname, boolean isElement)
857        {
858            String uri = EMPTYSTRING;
859            int col = qname.lastIndexOf(':');
860            final String prefix = (col > 0) ? qname.substring(0, col) : EMPTYSTRING;
861    
862            if (!EMPTYSTRING.equals(prefix) || isElement)
863            {
864                if (m_prefixMap != null)
865                {
866                    uri = m_prefixMap.lookupNamespace(prefix);
867                    if (uri == null && !prefix.equals(XMLNS_PREFIX))
868                    {
869                        throw new RuntimeException(
870                            Utils.messages.createMessage(
871                                MsgKey.ER_NAMESPACE_PREFIX,
872                                new Object[] { qname.substring(0, col) }  ));
873                    }
874                }
875            }
876            return uri;
877        }
878    
879        /**
880         * Returns the URI of prefix (if any)
881         * 
882             * @param prefix the prefix whose URI is searched for
883         * @return the namespace URI currently associated with the
884         * prefix, null if the prefix is undefined.
885         */
886        public String getNamespaceURIFromPrefix(String prefix)
887        {
888            String uri = null;
889            if (m_prefixMap != null)
890                uri = m_prefixMap.lookupNamespace(prefix);
891            return uri;
892        }
893    
894        /**
895         * Entity reference event.
896         *
897         * @param name Name of entity
898         *
899         * @throws org.xml.sax.SAXException
900         */
901        public void entityReference(String name) throws org.xml.sax.SAXException
902        {
903    
904            flushPending();
905    
906            startEntity(name);
907            endEntity(name);
908    
909            if (m_tracer != null)
910                        fireEntityReference(name);
911        }
912    
913        /**
914         * Sets the transformer associated with this serializer
915         * @param t the transformer associated with this serializer.
916         * @see SerializationHandler#setTransformer(Transformer)
917         */
918        public void setTransformer(Transformer t)
919        {
920            m_transformer = t;
921            
922            // If this transformer object implements the SerializerTrace interface
923            // then assign m_tracer to the transformer object so it can be used
924            // to fire trace events.
925            if ((m_transformer instanceof SerializerTrace) &&
926                (((SerializerTrace) m_transformer).hasTraceListeners())) {
927               m_tracer = (SerializerTrace) m_transformer;
928            } else {
929               m_tracer = null;
930            }
931        }
932        /**
933         * Gets the transformer associated with this serializer
934         * @return returns the transformer associated with this serializer.
935         * @see SerializationHandler#getTransformer()
936         */
937        public Transformer getTransformer()
938        {
939            return m_transformer;
940        }
941        
942        /**
943         * This method gets the nodes value as a String and uses that String as if
944         * it were an input character notification.
945         * @param node the Node to serialize
946         * @throws org.xml.sax.SAXException
947         */
948        public void characters(org.w3c.dom.Node node)
949            throws org.xml.sax.SAXException
950        {
951            flushPending();
952            String data = node.getNodeValue();
953            if (data != null)
954            {
955                final int length = data.length();
956                if (length > m_charsBuff.length)
957                {
958                    m_charsBuff = new char[length * 2 + 1];
959                }
960                data.getChars(0, length, m_charsBuff, 0);
961                characters(m_charsBuff, 0, length);
962            }
963        }
964        
965    
966        /**
967         * @see org.xml.sax.ErrorHandler#error(SAXParseException)
968         */
969        public void error(SAXParseException exc) throws SAXException {
970        }
971    
972        /**
973         * @see org.xml.sax.ErrorHandler#fatalError(SAXParseException)
974         */
975        public void fatalError(SAXParseException exc) throws SAXException {
976            
977          m_elemContext.m_startTagOpen = false;
978    
979        }
980    
981        /**
982         * @see org.xml.sax.ErrorHandler#warning(SAXParseException)
983         */
984        public void warning(SAXParseException exc) throws SAXException 
985        {
986        }
987    
988        /**
989         * To fire off start entity trace event
990         * @param name Name of entity
991         */
992        protected void fireStartEntity(String name)
993            throws org.xml.sax.SAXException
994        {        
995            if (m_tracer != null)
996            {
997                flushMyWriter();
998                m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_ENTITYREF, name);
999            }                               
1000        }
1001    
1002        /**
1003         * Report the characters event
1004         * @param chars  content of characters
1005         * @param start  starting index of characters to output
1006         * @param length  number of characters to output
1007         */
1008    //    protected void fireCharEvent(char[] chars, int start, int length)
1009    //        throws org.xml.sax.SAXException
1010    //    {
1011    //        if (m_tracer != null)
1012    //            m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_CHARACTERS, chars, start,length);                                    
1013    //    }
1014    //        
1015    
1016        /**
1017         * This method is only used internally when flushing the writer from the
1018         * various fire...() trace events.  Due to the writer being wrapped with 
1019         * SerializerTraceWriter it may cause the flush of these trace events:
1020         * EVENTTYPE_OUTPUT_PSEUDO_CHARACTERS 
1021         * EVENTTYPE_OUTPUT_CHARACTERS
1022         * which trace the output written to the output stream.
1023         * 
1024         */
1025        private void flushMyWriter()
1026        {
1027            if (m_writer != null)
1028            {
1029                try
1030                {
1031                    m_writer.flush();
1032                }
1033                catch(IOException ioe)
1034                {
1035                
1036                }
1037            }
1038        }
1039        /**
1040         * Report the CDATA trace event
1041         * @param chars  content of CDATA
1042         * @param start  starting index of characters to output
1043         * @param length  number of characters to output
1044         */
1045        protected void fireCDATAEvent(char[] chars, int start, int length)
1046            throws org.xml.sax.SAXException
1047        {
1048                    if (m_tracer != null)
1049            {
1050                flushMyWriter();
1051                            m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_CDATA, chars, start,length);
1052            }                               
1053        }
1054    
1055        /**
1056         * Report the comment trace event
1057         * @param chars  content of comment
1058         * @param start  starting index of comment to output
1059         * @param length  number of characters to output
1060         */
1061        protected void fireCommentEvent(char[] chars, int start, int length)
1062            throws org.xml.sax.SAXException
1063        {
1064                    if (m_tracer != null)
1065            {
1066                flushMyWriter();
1067                            m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_COMMENT, new String(chars, start, length));
1068            }                               
1069        }
1070    
1071    
1072        /**
1073         * To fire off end entity trace event
1074         * @param name Name of entity
1075         */
1076        public void fireEndEntity(String name)
1077            throws org.xml.sax.SAXException
1078        {
1079            if (m_tracer != null)
1080                flushMyWriter();
1081            // we do not need to handle this.
1082        }    
1083    
1084        /**
1085         * To fire off start document trace  event
1086         */
1087         protected void fireStartDoc()
1088            throws org.xml.sax.SAXException
1089        {
1090            if (m_tracer != null)
1091            {
1092                flushMyWriter();
1093                m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_STARTDOCUMENT);
1094            }           
1095        }    
1096    
1097    
1098        /**
1099         * To fire off end document trace event
1100         */
1101        protected void fireEndDoc()
1102            throws org.xml.sax.SAXException
1103        {
1104            if (m_tracer != null)
1105            {
1106                flushMyWriter();
1107                m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_ENDDOCUMENT);
1108            }                       
1109        }    
1110        
1111        /**
1112         * Report the start element trace event. This trace method needs to be
1113         * called just before the attributes are cleared.
1114         * 
1115         * @param elemName the qualified name of the element
1116         * 
1117         */
1118        protected void fireStartElem(String elemName)
1119            throws org.xml.sax.SAXException
1120        {        
1121            if (m_tracer != null)
1122            {
1123                flushMyWriter();
1124                m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_STARTELEMENT,
1125                    elemName, m_attributes);         
1126            }               
1127        }    
1128    
1129    
1130        /**
1131         * To fire off the end element event
1132         * @param name Name of element
1133         */
1134    //    protected void fireEndElem(String name)
1135    //        throws org.xml.sax.SAXException
1136    //    {
1137    //        if (m_tracer != null)
1138    //            m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_ENDELEMENT,name, (Attributes)null);                                  
1139    //    }    
1140    
1141    
1142        /**
1143         * To fire off the PI trace event
1144         * @param name Name of PI
1145         */
1146        protected void fireEscapingEvent(String name, String data)
1147            throws org.xml.sax.SAXException
1148        {
1149    
1150            if (m_tracer != null)
1151            {
1152                flushMyWriter();
1153                m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_PI,name, data);
1154            }                               
1155        }    
1156    
1157    
1158        /**
1159         * To fire off the entity reference trace event
1160         * @param name Name of entity reference
1161         */
1162        protected void fireEntityReference(String name)
1163            throws org.xml.sax.SAXException
1164        {
1165            if (m_tracer != null)
1166            {
1167                flushMyWriter();
1168                m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_ENTITYREF,name, (Attributes)null);
1169            }                               
1170        }    
1171    
1172        /**
1173         * Receive notification of the beginning of a document.
1174         * This method is never a self generated call, 
1175         * but only called externally.
1176         *
1177         * <p>The SAX parser will invoke this method only once, before any
1178         * other methods in this interface or in DTDHandler (except for
1179         * setDocumentLocator).</p>
1180         *
1181         * @throws org.xml.sax.SAXException Any SAX exception, possibly
1182         *            wrapping another exception.
1183         *
1184         * @throws org.xml.sax.SAXException
1185         */
1186        public void startDocument() throws org.xml.sax.SAXException
1187        {
1188    
1189            // if we do get called with startDocument(), handle it right away       
1190            startDocumentInternal();
1191            m_needToCallStartDocument = false;
1192            return;
1193        }   
1194        
1195        /**
1196         * This method handles what needs to be done at a startDocument() call,
1197         * whether from an external caller, or internally called in the 
1198         * serializer.  For historical reasons the serializer is flexible to
1199         * startDocument() not always being called.
1200         * Even if no external call is
1201         * made into startDocument() this method will always be called as a self
1202         * generated internal startDocument, it handles what needs to be done at a
1203         * startDocument() call.
1204         * 
1205         * This method exists just to make sure that startDocument() is only ever
1206         * called from an external caller, which in principle is just a matter of
1207         * style.
1208         * 
1209         * @throws SAXException
1210         */
1211        protected void startDocumentInternal() throws org.xml.sax.SAXException
1212        {
1213            if (m_tracer != null)
1214                this.fireStartDoc();
1215        } 
1216        /**
1217         * This method is used to set the source locator, which might be used to
1218         * generated an error message.
1219         * @param locator the source locator
1220         *
1221         * @see ExtendedContentHandler#setSourceLocator(javax.xml.transform.SourceLocator)
1222         */
1223        public void setSourceLocator(SourceLocator locator)
1224        {
1225            m_sourceLocator = locator;    
1226        }
1227    
1228        
1229        /** 
1230         * Used only by TransformerSnapshotImpl to restore the serialization 
1231         * to a previous state. 
1232         * 
1233         * @param mappings NamespaceMappings
1234         */
1235        public void setNamespaceMappings(NamespaceMappings mappings) {
1236            m_prefixMap = mappings;
1237        }
1238        
1239        public boolean reset()
1240        {
1241            resetSerializerBase();
1242            return true;
1243        }
1244        
1245        /**
1246         * Reset all of the fields owned by SerializerBase
1247         *
1248         */
1249        private void resetSerializerBase()
1250        {
1251            this.m_attributes.clear();
1252            this.m_CdataElems = null;
1253            this.m_cdataTagOpen = false;
1254            this.m_docIsEmpty = true;
1255            this.m_doctypePublic = null;
1256            this.m_doctypeSystem = null;
1257            this.m_doIndent = false;
1258            this.m_elemContext = new ElemContext();
1259            this.m_indentAmount = 0;
1260            this.m_inEntityRef = false;
1261            this.m_inExternalDTD = false;
1262            this.m_mediatype = null;
1263            this.m_needToCallStartDocument = true;
1264            this.m_needToOutputDocTypeDecl = false;
1265            if (m_OutputProps != null)
1266                this.m_OutputProps.clear();
1267            if (m_OutputPropsDefault != null)
1268                this.m_OutputPropsDefault.clear();
1269            if (this.m_prefixMap != null)
1270                this.m_prefixMap.reset();
1271            this.m_shouldNotWriteXMLHeader = false;
1272            this.m_sourceLocator = null;
1273            this.m_standalone = null;
1274            this.m_standaloneWasSpecified = false;
1275            this.m_StringOfCDATASections = null;
1276            this.m_tracer = null;
1277            this.m_transformer = null;
1278            this.m_version = null;
1279            // don't set writer to null, so that it might be re-used
1280            //this.m_writer = null;
1281        }
1282        
1283        /**
1284         * Returns true if the serializer is used for temporary output rather than
1285         * final output.
1286         * 
1287         * This concept is made clear in the XSLT 2.0 draft.
1288         */
1289        final boolean inTemporaryOutputState() 
1290        {
1291            /* This is a hack. We should really be letting the serializer know
1292             * that it is in temporary output state with an explicit call, but
1293             * from a pragmatic point of view (for now anyways) having no output
1294             * encoding at all, not even the default UTF-8 indicates that the serializer
1295             * is being used for temporary RTF.
1296             */ 
1297            return (getEncoding() == null);
1298            
1299        }
1300        
1301        /**
1302         * This method adds an attribute the the current element,
1303         * but should not be used for an xsl:attribute child.
1304         * @see ExtendedContentHandler#addAttribute(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String)
1305         */
1306        public void addAttribute(String uri, String localName, String rawName, String type, String value) throws SAXException 
1307        {
1308            if (m_elemContext.m_startTagOpen)
1309            {
1310                addAttributeAlways(uri, localName, rawName, type, value, false);
1311            }
1312        }
1313        
1314        /**
1315         * @see org.xml.sax.DTDHandler#notationDecl(java.lang.String, java.lang.String, java.lang.String)
1316         */
1317        public void notationDecl(String arg0, String arg1, String arg2)
1318            throws SAXException {
1319            // This method just provides a definition to satisfy the interface
1320            // A particular sub-class of SerializerBase provides the implementation (if desired)        
1321        }
1322    
1323        /**
1324         * @see org.xml.sax.DTDHandler#unparsedEntityDecl(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
1325         */
1326        public void unparsedEntityDecl(
1327            String arg0,
1328            String arg1,
1329            String arg2,
1330            String arg3)
1331            throws SAXException {
1332            // This method just provides a definition to satisfy the interface
1333            // A particular sub-class of SerializerBase provides the implementation (if desired)        
1334        }
1335    
1336        /**
1337         * If set to false the serializer does not expand DTD entities,
1338         * but leaves them as is, the default value is true.
1339         */
1340        public void setDTDEntityExpansion(boolean expand) {
1341            // This method just provides a definition to satisfy the interface
1342            // A particular sub-class of SerializerBase provides the implementation (if desired)        
1343        }
1344     
1345    
1346        /** 
1347         * The CDATA section names stored in a whitespace separateed list with
1348         * each element being a word of the form "{uri}localName" This list
1349         * comes from the cdata-section-elements attribute.
1350         * 
1351         * This field replaces m_cdataSectionElements Vector.
1352         */
1353        protected String m_StringOfCDATASections = null; 
1354        
1355        boolean m_docIsEmpty = true;
1356        void initCdataElems(String s)
1357        {
1358            if (s != null)
1359            {            
1360                int max = s.length();
1361    
1362                // true if we are in the middle of a pair of curly braces that delimit a URI
1363                boolean inCurly = false;
1364    
1365                // true if we found a URI but haven't yet processed the local name 
1366                boolean foundURI = false;
1367    
1368                StringBuffer buf = new StringBuffer();
1369                String uri = null;
1370                String localName = null;
1371    
1372                // parse through string, breaking on whitespaces.  I do this instead
1373                // of a tokenizer so I can track whitespace inside of curly brackets,
1374                // which theoretically shouldn't happen if they contain legal URLs.
1375    
1376    
1377                for (int i = 0; i < max; i++)
1378                {
1379    
1380                    char c = s.charAt(i);
1381    
1382                    if (Character.isWhitespace(c))
1383                    {
1384                        if (!inCurly)
1385                        {
1386                            if (buf.length() > 0)
1387                            {
1388                                localName = buf.toString();
1389                                if (!foundURI)
1390                                    uri = "";
1391                                addCDATAElement(uri,localName);
1392                                buf.setLength(0);
1393                                foundURI = false;
1394                            }
1395                            continue;
1396                        }
1397                        else
1398                            buf.append(c); // add whitespace to the URI
1399                    }
1400                    else if ('{' == c) // starting a URI
1401                        inCurly = true;
1402                    else if ('}' == c)
1403                    {
1404                        // we just ended a URI, add the URI to the vector
1405                        foundURI = true;
1406                        uri = buf.toString();
1407                        buf.setLength(0);
1408                        inCurly = false;
1409                    }
1410                    else
1411                    {
1412                        // append non-whitespace, non-curly to current URI or localName being gathered.                    
1413                        buf.append(c);
1414                    }
1415    
1416                }
1417    
1418                if (buf.length() > 0)
1419                {
1420                    // We have one last localName to process.
1421                    localName = buf.toString();
1422                    if (!foundURI)
1423                        uri = "";
1424                    addCDATAElement(uri,localName);
1425                }
1426            }
1427        }
1428        protected java.util.Hashtable m_CdataElems = null;
1429        private void addCDATAElement(String uri, String localName) 
1430        {
1431            if (m_CdataElems == null) {
1432                m_CdataElems = new java.util.Hashtable();
1433            }
1434            
1435            java.util.Hashtable h = (java.util.Hashtable) m_CdataElems.get(localName);
1436            if (h == null) {
1437                h = new java.util.Hashtable();
1438                m_CdataElems.put(localName,h);
1439            }
1440            h.put(uri,uri);
1441            
1442        }
1443        
1444        
1445        /**
1446         * Return true if nothing has been sent to this result tree yet.
1447         * <p>
1448         * This is not a public API.
1449         * 
1450         * @xsl.usage internal
1451         */
1452        public boolean documentIsEmpty() {
1453            // If we haven't called startDocument() yet, then this document is empty
1454            return m_docIsEmpty && (m_elemContext.m_currentElemDepth == 0);
1455        }    
1456        
1457        /**
1458         * Return true if the current element in m_elemContext
1459         * is a CDATA section.
1460         * CDATA sections are specified in the <xsl:output> attribute
1461         * cdata-section-names or in the JAXP equivalent property.
1462         * In any case the format of the value of such a property is:
1463         * <pre>
1464         * "{uri1}localName1 {uri2}localName2 . . . "
1465         * </pre>
1466         * 
1467         * <p>
1468         * This method is not a public API, but is only used internally by the serializer.
1469         */
1470        protected boolean isCdataSection()
1471        {
1472    
1473            boolean b = false;
1474    
1475            if (null != m_StringOfCDATASections)
1476            {
1477                if (m_elemContext.m_elementLocalName == null) 
1478                {
1479                    String localName =  getLocalName(m_elemContext.m_elementName); 
1480                    m_elemContext.m_elementLocalName = localName;                   
1481                }
1482                
1483                if ( m_elemContext.m_elementURI == null) {
1484                    
1485                    m_elemContext.m_elementURI = getElementURI();
1486                }
1487                else if ( m_elemContext.m_elementURI.length() == 0) {
1488                    if ( m_elemContext.m_elementName == null) {
1489                        m_elemContext.m_elementName = m_elemContext.m_elementLocalName;    
1490                        // leave URI as "", meaning in no namespace
1491                    }
1492                    else if (m_elemContext.m_elementLocalName.length() < m_elemContext.m_elementName.length()){
1493                        // We were told the URI was "", yet the name has a prefix since the name is longer than the localname.
1494                        // So we will fix that incorrect information here.
1495                        m_elemContext.m_elementURI = getElementURI();  
1496                    }
1497                }
1498    
1499                java.util.Hashtable h = (java.util.Hashtable) m_CdataElems.get(m_elemContext.m_elementLocalName);
1500                if (h != null) 
1501                {
1502                    Object obj = h.get(m_elemContext.m_elementURI);
1503                    if (obj != null)
1504                        b = true; 
1505                }
1506    
1507            }
1508            return b;
1509        }
1510        
1511        /**
1512         * Before this call m_elementContext.m_elementURI is null,
1513         * which means it is not yet known. After this call it
1514         * is non-null, but possibly "" meaning that it is in the
1515         * default namespace.
1516         * 
1517         * @return The URI of the element, never null, but possibly "".
1518         */
1519        private String getElementURI() {
1520            String uri = null;
1521            // At this point in processing we have received all the
1522            // namespace mappings
1523            // As we still don't know the elements namespace,
1524            // we now figure it out.
1525    
1526            String prefix = getPrefixPart(m_elemContext.m_elementName);
1527    
1528            if (prefix == null) {
1529                // no prefix so lookup the URI of the default namespace
1530                uri = m_prefixMap.lookupNamespace("");
1531            } else {
1532                uri = m_prefixMap.lookupNamespace(prefix);
1533            }
1534            if (uri == null) {
1535                // We didn't find the namespace for the
1536                // prefix ... ouch, that shouldn't happen.
1537                // This is a hack, we really don't know
1538                // the namespace
1539                uri = EMPTYSTRING;
1540            }
1541    
1542            return uri;
1543        }
1544        
1545    
1546        /**
1547         * Get the value of an output property,
1548         * the explicit value, if any, otherwise the
1549         * default value, if any, otherwise null.
1550         */
1551        public String getOutputProperty(String name) {
1552            String val = getOutputPropertyNonDefault(name);
1553            // If no explicit value, try to get the default value
1554            if (val == null)
1555                val = getOutputPropertyDefault(name);
1556            return val;
1557            
1558        }
1559        /**
1560         * Get the value of an output property, 
1561         * not the default value. If there is a default
1562         * value, but no non-default value this method
1563         * will return null.
1564         * <p>
1565         * 
1566         */
1567        public String getOutputPropertyNonDefault(String name )
1568        {
1569            return getProp(name,false);
1570        }
1571    
1572        /**
1573         * Return a {@link DOM3Serializer} interface into this serializer. If the
1574         * serializer does not support the {@link DOM3Serializer} interface, it should
1575         * return null.
1576         *
1577         * @return A {@link DOM3Serializer} interface into this serializer,  or null
1578         * if the serializer is not DOM capable
1579         * @throws IOException An I/O exception occured
1580         * @see org.apache.xml.serializer.Serializer#asDOM3Serializer()
1581         */
1582        public Object asDOM3Serializer() throws IOException
1583        {
1584            return new org.apache.xml.serializer.dom3.DOM3SerializerImpl(this);
1585        }
1586        /**
1587         * Get the default value of an xsl:output property,
1588         * which would be null only if no default value exists
1589         * for the property.
1590         */
1591        public String getOutputPropertyDefault(String name) {
1592            return getProp(name, true);
1593        } 
1594        
1595        /**
1596         * Set the value for the output property, typically from
1597         * an xsl:output element, but this does not change what
1598         * the default value is.
1599         */
1600        public void   setOutputProperty(String name, String val) {
1601            setProp(name,val,false);
1602            
1603        }
1604        
1605        /**
1606         * Set the default value for an output property, but this does
1607         * not impact any explicitly set value.
1608         */
1609        public void   setOutputPropertyDefault(String name, String val) {
1610            setProp(name,val,true);
1611            
1612        }
1613        
1614        /**
1615         * A mapping of keys to explicitly set values, for example if 
1616         * and <xsl:output/> has an "encoding" attribute, this
1617         * map will have what that attribute maps to.
1618         */
1619        private HashMap m_OutputProps;
1620        /**
1621         * A mapping of keys to default values, for example if
1622         * the default value of the encoding is "UTF-8" then this
1623         * map will have that "encoding" maps to "UTF-8".
1624         */
1625        private HashMap m_OutputPropsDefault;
1626        
1627        Set getOutputPropDefaultKeys() {
1628            return m_OutputPropsDefault.keySet();
1629        }
1630        Set getOutputPropKeys() {
1631            return m_OutputProps.keySet();
1632        }
1633        
1634        private String getProp(String name, boolean defaultVal) {
1635            if (m_OutputProps == null) {
1636                m_OutputProps = new HashMap();
1637                m_OutputPropsDefault = new HashMap();
1638            }
1639            
1640            String val;
1641            if (defaultVal)
1642                val = (String) m_OutputPropsDefault.get(name);
1643            else
1644                val = (String) m_OutputProps.get(name);
1645            
1646            return val;
1647            
1648        }
1649        /**
1650         * 
1651         * @param name The name of the property, e.g. "{http://myprop}indent-tabs" or "indent".
1652         * @param val The value of the property, e.g. "4"
1653         * @param defaultVal true if this is a default value being set for the property as 
1654         * opposed to a user define on, set say explicitly in the stylesheet or via JAXP
1655         */
1656        void setProp(String name, String val, boolean defaultVal) {
1657            if (m_OutputProps == null) {
1658                m_OutputProps = new HashMap();
1659                m_OutputPropsDefault = new HashMap();
1660            }
1661            
1662            if (defaultVal)
1663                m_OutputPropsDefault.put(name,val);
1664            else {
1665                if (OutputKeys.CDATA_SECTION_ELEMENTS.equals(name) && val != null) {
1666                    initCdataElems(val);
1667                    String oldVal = (String) m_OutputProps.get(name);
1668                    String newVal;
1669                    if (oldVal == null)
1670                        newVal = oldVal + ' ' + val;
1671                    else
1672                        newVal = val;
1673                    m_OutputProps.put(name,newVal);
1674                }
1675                else {
1676                    m_OutputProps.put(name,val);
1677                }
1678            }
1679            
1680    
1681        }
1682    
1683        /**
1684         * Get the first char of the local name
1685         * @param name Either a local name, or a local name
1686         * preceeded by a uri enclosed in curly braces.
1687         */
1688        static char getFirstCharLocName(String name) {
1689            final char first;
1690            int i = name.indexOf('}');
1691            if (i < 0)
1692                first = name.charAt(0);
1693            else
1694                first = name.charAt(i+1);
1695            return first;
1696        }
1697    }
1698        
1699