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  */
21 package org.apache.xml.serializer;
22 
23 import java.io.IOException;
24 import java.util.HashMap;
25 import java.util.Set;
26 
27 import javax.xml.transform.OutputKeys;
28 import javax.xml.transform.SourceLocator;
29 import javax.xml.transform.Transformer;
30 
31 import org.apache.xml.serializer.utils.MsgKey;
32 import org.apache.xml.serializer.utils.Utils;
33 import org.xml.sax.Attributes;
34 import org.xml.sax.ContentHandler;
35 import org.xml.sax.Locator;
36 import org.xml.sax.SAXException;
37 import 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  */
47 public abstract class SerializerBase
48     implements SerializationHandler, SerializerConstants
49 {
SerializerBase()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      */
fireEndElem(String name)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      */
fireCharEvent(char[] chars, int start, int length)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      */
comment(String data)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      */
patchName(String qname)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      */
getLocalName(String qname)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      */
setDocumentLocator(Locator locator)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      */
addAttribute( String uri, String localName, String rawName, String type, String value, boolean XSLAttribute)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      */
addAttributeAlways( String uri, String localName, String rawName, String type, String value, boolean XSLAttribute)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      */
addAttribute(String name, final String value)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      */
addXSLAttribute(String name, final String value, final String uri)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      */
addAttributes(Attributes atts)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      */
asContentHandler()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      */
endEntity(String name)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      */
close()540     public void close()
541     {
542         // do nothing (base behavior)
543     }
544 
545     /**
546      * Initialize global variables
547      */
initCDATA()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      */
getEncoding()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      */
setEncoding(String encoding)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      */
setOmitXMLDeclaration(boolean b)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      */
getOmitXMLDeclaration()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      */
getDoctypePublic()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       */
setDoctypePublic(String doctypePublic)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      */
getDoctypeSystem()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       */
setDoctypeSystem(String doctypeSystem)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      */
setDoctype(String doctypeSystem, String doctypePublic)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      */
setStandalone(String standalone)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      */
setStandaloneInternal(String standalone)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      */
getStandalone()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      */
getIndent()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      */
getMediaType()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      */
getVersion()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      */
setVersion(String version)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      */
setMediaType(String mediaType)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      */
getIndentAmount()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      */
setIndentAmount(int m_indentAmount)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      */
setIndent(boolean doIndent)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      */
namespaceAfterStartElement(String uri, String prefix)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      */
asDOMSerializer()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      */
subPartMatch(String p, String t)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      */
getPrefixPart(String qname)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      */
getNamespaceMappings()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      */
getPrefix(String namespaceURI)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      */
getNamespaceURI(String qname, boolean isElement)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      */
getNamespaceURIFromPrefix(String prefix)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      */
entityReference(String name)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      */
setTransformer(Transformer t)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      */
getTransformer()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      */
characters(org.w3c.dom.Node node)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      */
error(SAXParseException exc)969     public void error(SAXParseException exc) throws SAXException {
970     }
971 
972     /**
973      * @see org.xml.sax.ErrorHandler#fatalError(SAXParseException)
974      */
fatalError(SAXParseException exc)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      */
warning(SAXParseException exc)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      */
fireStartEntity(String name)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      */
flushMyWriter()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      */
fireCDATAEvent(char[] chars, int start, int length)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      */
fireCommentEvent(char[] chars, int start, int length)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      */
fireEndEntity(String name)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      */
fireStartDoc()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      */
fireEndDoc()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      */
fireStartElem(String elemName)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      */
fireEscapingEvent(String name, String data)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      */
fireEntityReference(String name)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      */
startDocument()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      */
startDocumentInternal()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      */
setSourceLocator(SourceLocator locator)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      */
setNamespaceMappings(NamespaceMappings mappings)1235     public void setNamespaceMappings(NamespaceMappings mappings) {
1236         m_prefixMap = mappings;
1237     }
1238 
reset()1239     public boolean reset()
1240     {
1241     	resetSerializerBase();
1242     	return true;
1243     }
1244 
1245     /**
1246      * Reset all of the fields owned by SerializerBase
1247      *
1248      */
resetSerializerBase()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      */
inTemporaryOutputState()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      */
addAttribute(String uri, String localName, String rawName, String type, String value)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      */
notationDecl(String arg0, String arg1, String arg2)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      */
unparsedEntityDecl( String arg0, String arg1, String arg2, String arg3)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      */
setDTDEntityExpansion(boolean expand)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;
initCdataElems(String s)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;
addCDATAElement(String uri, String localName)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      */
documentIsEmpty()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      */
isCdataSection()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      */
getElementURI()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      */
getOutputProperty(String name)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      */
getOutputPropertyNonDefault(String name )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      */
asDOM3Serializer()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      */
getOutputPropertyDefault(String name)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      */
setOutputProperty(String name, String val)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      */
setOutputPropertyDefault(String name, String val)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 
getOutputPropDefaultKeys()1627     Set getOutputPropDefaultKeys() {
1628         return m_OutputPropsDefault.keySet();
1629     }
getOutputPropKeys()1630     Set getOutputPropKeys() {
1631         return m_OutputProps.keySet();
1632     }
1633 
getProp(String name, boolean defaultVal)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      */
setProp(String name, String val, boolean defaultVal)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      */
getFirstCharLocName(String name)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