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