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