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