1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the  "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18/*
19 * $Id: SerializerBase.java 471981 2006-11-07 04:28:00Z minchau $
20 */
21package org.apache.xml.serializer;
22
23import java.io.IOException;
24import java.util.HashMap;
25import java.util.Set;
26
27import javax.xml.transform.OutputKeys;
28import javax.xml.transform.SourceLocator;
29import javax.xml.transform.Transformer;
30
31import org.apache.xml.serializer.utils.MsgKey;
32import org.apache.xml.serializer.utils.Utils;
33import org.xml.sax.Attributes;
34import org.xml.sax.ContentHandler;
35import org.xml.sax.Locator;
36import org.xml.sax.SAXException;
37import org.xml.sax.SAXParseException;
38
39
40/**
41 * This class acts as a base class for the XML "serializers"
42 * and the stream serializers.
43 * It contains a number of common fields and methods.
44 *
45 * @xsl.usage internal
46 */
47public abstract class SerializerBase
48    implements SerializationHandler, SerializerConstants
49{
50    SerializerBase() {
51        return;
52    }
53
54    /**
55     * The name of the package that this class is in.
56     * <p>
57     * Not a public API.
58     */
59    public static final String PKG_NAME;
60
61    /**
62     * The same as the name of the package that this class is in
63     * except that '.' are replaced with '/'.
64     * <p>
65     * Not a public API.
66     */
67    public static final String PKG_PATH;
68
69    static {
70        String fullyQualifiedName = SerializerBase.class.getName();
71        int lastDot = fullyQualifiedName.lastIndexOf('.');
72        if (lastDot < 0) {
73            PKG_NAME = "";
74        } else {
75            PKG_NAME = fullyQualifiedName.substring(0, lastDot);
76        }
77
78        StringBuffer sb = new StringBuffer();
79        for (int i = 0; i < PKG_NAME.length(); i++) {
80            char ch = PKG_NAME.charAt(i);
81            if (ch == '.')
82                sb.append('/');
83            else
84                sb.append(ch);
85        }
86        PKG_PATH = sb.toString();
87    }
88
89
90
91    /**
92     * To fire off the end element trace event
93     * @param name Name of element
94     */
95    protected void fireEndElem(String name)
96        throws org.xml.sax.SAXException
97    {
98        if (m_tracer != null)
99        {
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
1700