SerializerBase.java revision 1055:fc5ce112ac45
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 21package com.sun.org.apache.xml.internal.serializer; 22 23import com.sun.org.apache.xml.internal.serializer.utils.MsgKey; 24import com.sun.org.apache.xml.internal.serializer.utils.Utils; 25import java.io.IOException; 26import java.util.HashMap; 27import java.util.Set; 28import java.util.ArrayList; 29import javax.xml.transform.OutputKeys; 30import javax.xml.transform.SourceLocator; 31import javax.xml.transform.Transformer; 32import org.xml.sax.Attributes; 33import org.xml.sax.ContentHandler; 34import org.xml.sax.Locator; 35import org.xml.sax.SAXException; 36import org.xml.sax.SAXParseException; 37import 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 */ 46public 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 return m_inEntityRef > 0; 464 } 465 466 /** 467 * Flush and close the underlying java.io.Writer. This method applies to 468 * ToStream serializers, not ToSAXHandler serializers. 469 * @see ToStream 470 */ 471 public void close() { 472 // do nothing (base behavior) 473 } 474 475 /** 476 * Initialize global variables 477 */ 478 protected void initCDATA() { 479 // CDATA stack 480 // _cdataStack = new Stack(); 481 // _cdataStack.push(new Integer(-1)); // push dummy value 482 } 483 484 /** 485 * Returns the character encoding to be used in the output document. 486 * @return the character encoding to be used in the output document. 487 */ 488 public String getEncoding() { 489 return getOutputProperty(OutputKeys.ENCODING); 490 } 491 492 /** 493 * Sets the character encoding coming from the xsl:output encoding stylesheet attribute. 494 * @param m_encoding the character encoding 495 */ 496 public void setEncoding(String encoding) { 497 setOutputProperty(OutputKeys.ENCODING,encoding); 498 } 499 500 /** 501 * Sets the value coming from the xsl:output omit-xml-declaration stylesheet attribute 502 * @param b true if the XML declaration is to be omitted from the output 503 * document. 504 */ 505 public void setOmitXMLDeclaration(boolean b) { 506 String val = b ? "yes":"no"; 507 setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,val); 508 } 509 510 /** 511 * @return true if the XML declaration is to be omitted from the output 512 * document. 513 */ 514 public boolean getOmitXMLDeclaration() { 515 return m_shouldNotWriteXMLHeader; 516 } 517 518 /** 519 * Returns the previously set value of the value to be used as the public 520 * identifier in the document type declaration (DTD). 521 * 522 *@return the public identifier to be used in the DOCTYPE declaration in the 523 * output document. 524 */ 525 public String getDoctypePublic() 526 { 527 return m_doctypePublic; 528 } 529 530 /** Set the value coming from the xsl:output doctype-public stylesheet attribute. 531 * @param doctypePublic the public identifier to be used in the DOCTYPE 532 * declaration in the output document. 533 */ 534 public void setDoctypePublic(String doctypePublic) 535 { 536 setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, doctypePublic); 537 } 538 539 540 /** 541 * Returns the previously set value of the value to be used 542 * as the system identifier in the document type declaration (DTD). 543 * @return the system identifier to be used in the DOCTYPE declaration in 544 * the output document. 545 * 546 */ 547 public String getDoctypeSystem() 548 { 549 return m_doctypeSystem; 550 } 551 552 /** Set the value coming from the xsl:output doctype-system stylesheet attribute. 553 * @param doctypeSystem the system identifier to be used in the DOCTYPE 554 * declaration in the output document. 555 */ 556 public void setDoctypeSystem(String doctypeSystem) 557 { 558 setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doctypeSystem); 559 } 560 561 /** Set the value coming from the xsl:output doctype-public and doctype-system stylesheet properties 562 * @param doctypeSystem the system identifier to be used in the DOCTYPE 563 * declaration in the output document. 564 * @param doctypePublic the public identifier to be used in the DOCTYPE 565 * declaration in the output document. 566 */ 567 public void setDoctype(String doctypeSystem, String doctypePublic) 568 { 569 setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doctypeSystem); 570 setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, doctypePublic); 571 } 572 573 /** 574 * Sets the value coming from the xsl:output standalone stylesheet attribute. 575 * @param standalone a value of "yes" indicates that the 576 * <code>standalone</code> delaration is to be included in the output 577 * document. This method remembers if the value was explicitly set using 578 * this method, verses if the value is the default value. 579 */ 580 public void setStandalone(String standalone) { 581 setOutputProperty(OutputKeys.STANDALONE, standalone); 582 } 583 584 /** 585 * Sets the XSL standalone attribute, but does not remember if this is a 586 * default or explicite setting. 587 * @param standalone "yes" | "no" 588 */ 589 protected void setStandaloneInternal(String standalone) { 590 if ("yes".equals(standalone)) 591 m_standalone = "yes"; 592 else 593 m_standalone = "no"; 594 595 } 596 597 /** 598 * Gets the XSL standalone attribute 599 * @return a value of "yes" if the <code>standalone</code> delaration is to 600 * be included in the output document. 601 * @see XSLOutputAttributes#getStandalone() 602 */ 603 public String getStandalone() { 604 return m_standalone; 605 } 606 607 /** 608 * @return true if the output document should be indented to visually 609 * indicate its structure. 610 */ 611 public boolean getIndent() { 612 return m_doIndent; 613 } 614 /** 615 * Gets the mediatype the media-type or MIME type associated with the output 616 * document. 617 * @return the mediatype the media-type or MIME type associated with the 618 * output document. 619 */ 620 public String getMediaType() { 621 return m_mediatype; 622 } 623 624 /** 625 * Gets the version of the output format. 626 * @return the version of the output format. 627 */ 628 public String getVersion() { 629 return m_version; 630 } 631 632 /** 633 * Sets the value coming from the xsl:output version attribute. 634 * @param version the version of the output format. 635 * @see SerializationHandler#setVersion(String) 636 */ 637 public void setVersion(String version) { 638 setOutputProperty(OutputKeys.VERSION, version); 639 } 640 641 /** 642 * Sets the value coming from the xsl:output media-type stylesheet attribute. 643 * @param mediaType the non-null media-type or MIME type associated with the 644 * output document. 645 * @see javax.xml.transform.OutputKeys#MEDIA_TYPE 646 * @see SerializationHandler#setMediaType(String) 647 */ 648 public void setMediaType(String mediaType) { 649 setOutputProperty(OutputKeys.MEDIA_TYPE,mediaType); 650 } 651 652 /** 653 * @return the number of spaces to indent for each indentation level. 654 */ 655 public int getIndentAmount() { 656 return m_indentAmount; 657 } 658 659 /** 660 * Sets the indentation amount. 661 * @param m_indentAmount The m_indentAmount to set 662 */ 663 public void setIndentAmount(int m_indentAmount) { 664 this.m_indentAmount = m_indentAmount; 665 } 666 667 /** 668 * Sets the value coming from the xsl:output indent stylesheet 669 * attribute. 670 * @param doIndent true if the output document should be indented to 671 * visually indicate its structure. 672 * @see XSLOutputAttributes#setIndent(boolean) 673 */ 674 public void setIndent(boolean doIndent) { 675 String val = doIndent ? "yes":"no"; 676 setOutputProperty(OutputKeys.INDENT,val); 677 } 678 679 /** 680 * Sets the isStandalone property 681 * @param isStandalone true if the ORACLE_IS_STANDALONE is set to yes 682 * @see OutputPropertiesFactory ORACLE_IS_STANDALONE 683 */ 684 public void setIsStandalone(boolean isStandalone) { 685 m_isStandalone = isStandalone; 686 } 687 688 /** 689 * This method is used when a prefix/uri namespace mapping 690 * is indicated after the element was started with a 691 * startElement() and before and endElement(). 692 * startPrefixMapping(prefix,uri) would be used before the 693 * startElement() call. 694 * @param uri the URI of the namespace 695 * @param prefix the prefix associated with the given URI. 696 * 697 * @see ExtendedContentHandler#namespaceAfterStartElement(String, String) 698 */ 699 public void namespaceAfterStartElement(String uri, String prefix) 700 throws SAXException 701 { 702 // default behavior is to do nothing 703 } 704 705 /** 706 * Return a {@link DOMSerializer} interface into this serializer. If the 707 * serializer does not support the {@link DOMSerializer} interface, it should 708 * return null. 709 * 710 * @return A {@link DOMSerializer} interface into this serializer, or null 711 * if the serializer is not DOM capable 712 * @throws IOException An I/O exception occured 713 * @see Serializer#asDOMSerializer() 714 */ 715 public DOMSerializer asDOMSerializer() throws IOException { 716 return this; 717 } 718 719 /** 720 * Tell if two strings are equal, without worry if the first string is null. 721 * 722 * @param p String reference, which may be null. 723 * @param t String reference, which may be null. 724 * 725 * @return true if strings are equal. 726 */ 727 private static final boolean subPartMatch(String p, String t) { 728 return (p == t) || ((null != p) && (p.equals(t))); 729 } 730 731 /** 732 * Returns the local name of a qualified name. 733 * If the name has no prefix, 734 * then it works as the identity (SAX2). 735 * 736 * @param qname a qualified name 737 * @return returns the prefix of the qualified name, 738 * or null if there is no prefix. 739 */ 740 protected static final String getPrefixPart(String qname) { 741 final int col = qname.indexOf(':'); 742 return (col > 0) ? qname.substring(0, col) : null; 743 //return (col > 0) ? qname.substring(0,col) : ""; 744 } 745 746 /** 747 * Some users of the serializer may need the current namespace mappings 748 * @return the current namespace mappings (prefix/uri) 749 * @see ExtendedContentHandler#getNamespaceMappings() 750 */ 751 public NamespaceMappings getNamespaceMappings() { 752 return m_prefixMap; 753 } 754 755 /** 756 * Returns the prefix currently pointing to the given URI (if any). 757 * @param namespaceURI the uri of the namespace in question 758 * @return a prefix pointing to the given URI (if any). 759 * @see ExtendedContentHandler#getPrefix(String) 760 */ 761 public String getPrefix(String namespaceURI) { 762 String prefix = m_prefixMap.lookupPrefix(namespaceURI); 763 return prefix; 764 } 765 766 /** 767 * Returns the URI of an element or attribute. Note that default namespaces 768 * do not apply directly to attributes. 769 * @param qname a qualified name 770 * @param isElement true if the qualified name is the name of 771 * an element. 772 * @return returns the namespace URI associated with the qualified name. 773 */ 774 public String getNamespaceURI(String qname, boolean isElement) { 775 String uri = EMPTYSTRING; 776 int col = qname.lastIndexOf(':'); 777 final String prefix = (col > 0) ? qname.substring(0, col) : EMPTYSTRING; 778 779 if (!EMPTYSTRING.equals(prefix) || isElement) { 780 if (m_prefixMap != null) { 781 uri = m_prefixMap.lookupNamespace(prefix); 782 if (uri == null && !prefix.equals(XMLNS_PREFIX)) { 783 throw new RuntimeException( 784 Utils.messages.createMessage( 785 MsgKey.ER_NAMESPACE_PREFIX, 786 new Object[] { qname.substring(0, col) } )); 787 } 788 } 789 } 790 return uri; 791 } 792 793 /** 794 * Returns the URI of prefix (if any) 795 * 796 * @param prefix the prefix whose URI is searched for 797 * @return the namespace URI currently associated with the 798 * prefix, null if the prefix is undefined. 799 */ 800 public String getNamespaceURIFromPrefix(String prefix) { 801 String uri = null; 802 if (m_prefixMap != null) 803 uri = m_prefixMap.lookupNamespace(prefix); 804 return uri; 805 } 806 807 /** 808 * Entity reference event. 809 * 810 * @param name Name of entity 811 * 812 * @throws org.xml.sax.SAXException 813 */ 814 public void entityReference(String name) throws org.xml.sax.SAXException { 815 flushPending(); 816 817 startEntity(name); 818 endEntity(name); 819 820 if (m_tracer != null) 821 fireEntityReference(name); 822 } 823 824 /** 825 * Sets the transformer associated with this serializer 826 * @param t the transformer associated with this serializer. 827 * @see SerializationHandler#setTransformer(Transformer) 828 */ 829 public void setTransformer(Transformer t) { 830 m_transformer = t; 831 832 // If this transformer object implements the SerializerTrace interface 833 // then assign m_tracer to the transformer object so it can be used 834 // to fire trace events. 835 if ((m_transformer instanceof SerializerTrace) && 836 (((SerializerTrace) m_transformer).hasTraceListeners())) { 837 m_tracer = (SerializerTrace) m_transformer; 838 } else { 839 m_tracer = null; 840 } 841 } 842 843 /** 844 * Gets the transformer associated with this serializer 845 * @return returns the transformer associated with this serializer. 846 * @see SerializationHandler#getTransformer() 847 */ 848 public Transformer getTransformer() { 849 return m_transformer; 850 } 851 852 /** 853 * This method gets the nodes value as a String and uses that String as if 854 * it were an input character notification. 855 * @param node the Node to serialize 856 * @throws org.xml.sax.SAXException 857 */ 858 public void characters(org.w3c.dom.Node node) 859 throws org.xml.sax.SAXException 860 { 861 flushPending(); 862 String data = node.getNodeValue(); 863 if (data != null) { 864 final int length = data.length(); 865 if (length > m_charsBuff.length) { 866 m_charsBuff = new char[length * 2 + 1]; 867 } 868 data.getChars(0, length, m_charsBuff, 0); 869 characters(m_charsBuff, 0, length); 870 } 871 } 872 873 874 /** 875 * @see org.xml.sax.ErrorHandler#error(SAXParseException) 876 */ 877 public void error(SAXParseException exc) throws SAXException { 878 } 879 880 /** 881 * @see org.xml.sax.ErrorHandler#fatalError(SAXParseException) 882 */ 883 public void fatalError(SAXParseException exc) throws SAXException { 884 m_elemContext.m_startTagOpen = false; 885 } 886 887 /** 888 * @see org.xml.sax.ErrorHandler#warning(SAXParseException) 889 */ 890 public void warning(SAXParseException exc) throws SAXException { 891 } 892 893 /** 894 * To fire off start entity trace event 895 * @param name Name of entity 896 */ 897 protected void fireStartEntity(String name) 898 throws org.xml.sax.SAXException 899 { 900 if (m_tracer != null) 901 { 902 flushMyWriter(); 903 m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_ENTITYREF, name); 904 } 905 } 906 907 /** 908 * This method is only used internally when flushing the writer from the 909 * various fire...() trace events. Due to the writer being wrapped with 910 * SerializerTraceWriter it may cause the flush of these trace events: 911 * EVENTTYPE_OUTPUT_PSEUDO_CHARACTERS 912 * EVENTTYPE_OUTPUT_CHARACTERS 913 * which trace the output written to the output stream. 914 * 915 */ 916 private void flushMyWriter() { 917 if (m_writer != null) { 918 try { 919 m_writer.flush(); 920 } catch(IOException ioe) { 921 } 922 } 923 } 924 925 /** 926 * Report the CDATA trace event 927 * @param chars content of CDATA 928 * @param start starting index of characters to output 929 * @param length number of characters to output 930 */ 931 protected void fireCDATAEvent(char[] chars, int start, int length) 932 throws org.xml.sax.SAXException 933 { 934 if (m_tracer != null) { 935 flushMyWriter(); 936 m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_CDATA, chars, start,length); 937 } 938 } 939 940 /** 941 * Report the comment trace event 942 * @param chars content of comment 943 * @param start starting index of comment to output 944 * @param length number of characters to output 945 */ 946 protected void fireCommentEvent(char[] chars, int start, int length) 947 throws org.xml.sax.SAXException 948 { 949 if (m_tracer != null) { 950 flushMyWriter(); 951 m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_COMMENT, new String(chars, start, length)); 952 } 953 } 954 955 956 /** 957 * To fire off end entity trace event 958 * @param name Name of entity 959 */ 960 public void fireEndEntity(String name) 961 throws org.xml.sax.SAXException 962 { 963 if (m_tracer != null) 964 flushMyWriter(); 965 // we do not need to handle this. 966 } 967 968 /** 969 * To fire off start document trace event 970 */ 971 protected void fireStartDoc() 972 throws org.xml.sax.SAXException 973 { 974 if (m_tracer != null) 975 { 976 flushMyWriter(); 977 m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_STARTDOCUMENT); 978 } 979 } 980 981 982 /** 983 * To fire off end document trace event 984 */ 985 protected void fireEndDoc() 986 throws org.xml.sax.SAXException 987 { 988 if (m_tracer != null) 989 { 990 flushMyWriter(); 991 m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_ENDDOCUMENT); 992 } 993 } 994 995 /** 996 * Report the start element trace event. This trace method needs to be 997 * called just before the attributes are cleared. 998 * 999 * @param elemName the qualified name of the element 1000 * 1001 */ 1002 protected void fireStartElem(String elemName) 1003 throws org.xml.sax.SAXException 1004 { 1005 if (m_tracer != null) 1006 { 1007 flushMyWriter(); 1008 m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_STARTELEMENT, 1009 elemName, m_attributes); 1010 } 1011 } 1012 1013 1014 /** 1015 * To fire off the end element event 1016 * @param name Name of element 1017 */ 1018// protected void fireEndElem(String name) 1019// throws org.xml.sax.SAXException 1020// { 1021// if (m_tracer != null) 1022// m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_ENDELEMENT,name, (Attributes)null); 1023// } 1024 1025 1026 /** 1027 * To fire off the PI trace event 1028 * @param name Name of PI 1029 */ 1030 protected void fireEscapingEvent(String name, String data) 1031 throws org.xml.sax.SAXException 1032 { 1033 1034 if (m_tracer != null) 1035 { 1036 flushMyWriter(); 1037 m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_PI,name, data); 1038 } 1039 } 1040 1041 1042 /** 1043 * To fire off the entity reference trace event 1044 * @param name Name of entity reference 1045 */ 1046 protected void fireEntityReference(String name) 1047 throws org.xml.sax.SAXException 1048 { 1049 if (m_tracer != null) 1050 { 1051 flushMyWriter(); 1052 m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_ENTITYREF,name, (Attributes)null); 1053 } 1054 } 1055 1056 /** 1057 * Receive notification of the beginning of a document. 1058 * This method is never a self generated call, 1059 * but only called externally. 1060 * 1061 * <p>The SAX parser will invoke this method only once, before any 1062 * other methods in this interface or in DTDHandler (except for 1063 * setDocumentLocator).</p> 1064 * 1065 * @throws org.xml.sax.SAXException Any SAX exception, possibly 1066 * wrapping another exception. 1067 * 1068 * @throws org.xml.sax.SAXException 1069 */ 1070 public void startDocument() throws org.xml.sax.SAXException 1071 { 1072 1073 // if we do get called with startDocument(), handle it right away 1074 startDocumentInternal(); 1075 m_needToCallStartDocument = false; 1076 return; 1077 } 1078 1079 /** 1080 * This method handles what needs to be done at a startDocument() call, 1081 * whether from an external caller, or internally called in the 1082 * serializer. For historical reasons the serializer is flexible to 1083 * startDocument() not always being called. 1084 * Even if no external call is 1085 * made into startDocument() this method will always be called as a self 1086 * generated internal startDocument, it handles what needs to be done at a 1087 * startDocument() call. 1088 * 1089 * This method exists just to make sure that startDocument() is only ever 1090 * called from an external caller, which in principle is just a matter of 1091 * style. 1092 * 1093 * @throws SAXException 1094 */ 1095 protected void startDocumentInternal() throws org.xml.sax.SAXException 1096 { 1097 if (m_tracer != null) 1098 this.fireStartDoc(); 1099 1100 } 1101 1102 /* This method extracts version and encoding information from SAX events. 1103 */ 1104 protected void setDocumentInfo() { 1105 if (m_locator == null) 1106 return; 1107 try{ 1108 String strVersion = ((Locator2)m_locator).getXMLVersion(); 1109 if (strVersion != null) 1110 setVersion(strVersion); 1111 /*String strEncoding = ((Locator2)m_locator).getEncoding(); 1112 if (strEncoding != null) 1113 setEncoding(strEncoding); */ 1114 1115 }catch(ClassCastException e){} 1116 } 1117 1118 /** 1119 * This method is used to set the source locator, which might be used to 1120 * generated an error message. 1121 * @param locator the source locator 1122 * 1123 * @see ExtendedContentHandler#setSourceLocator(javax.xml.transform.SourceLocator) 1124 */ 1125 public void setSourceLocator(SourceLocator locator) { 1126 m_sourceLocator = locator; 1127 } 1128 1129 /** 1130 * Used only by TransformerSnapshotImpl to restore the serialization 1131 * to a previous state. 1132 * 1133 * @param mappings NamespaceMappings 1134 */ 1135 public void setNamespaceMappings(NamespaceMappings mappings) { 1136 m_prefixMap = mappings; 1137 } 1138 1139 public boolean reset() { 1140 resetSerializerBase(); 1141 return true; 1142 } 1143 1144 /** 1145 * Reset all of the fields owned by SerializerBase 1146 * 1147 */ 1148 private void resetSerializerBase() { 1149 this.m_attributes.clear(); 1150 this.m_StringOfCDATASections = null; 1151 this.m_elemContext = new ElemContext(); 1152 this.m_doctypePublic = null; 1153 this.m_doctypeSystem = null; 1154 this.m_doIndent = false; 1155 this.m_indentAmount = 4; 1156 this.m_inEntityRef = 0; 1157 this.m_inExternalDTD = false; 1158 this.m_mediatype = null; 1159 this.m_needToCallStartDocument = true; 1160 this.m_needToOutputDocTypeDecl = false; 1161 if (this.m_prefixMap != null) 1162 this.m_prefixMap.reset(); 1163 this.m_shouldNotWriteXMLHeader = false; 1164 this.m_sourceLocator = null; 1165 this.m_standalone = null; 1166 this.m_standaloneWasSpecified = false; 1167 this.m_tracer = null; 1168 this.m_transformer = null; 1169 this.m_version = null; 1170 // don't set writer to null, so that it might be re-used 1171 //this.m_writer = null; 1172 } 1173 1174 /** 1175 * Returns true if the serializer is used for temporary output rather than 1176 * final output. 1177 * 1178 * This concept is made clear in the XSLT 2.0 draft. 1179 */ 1180 final boolean inTemporaryOutputState() { 1181 /* This is a hack. We should really be letting the serializer know 1182 * that it is in temporary output state with an explicit call, but 1183 * from a pragmatic point of view (for now anyways) having no output 1184 * encoding at all, not even the default UTF-8 indicates that the 1185 * serializer is being used for temporary RTF. 1186 */ 1187 return (getEncoding() == null); 1188 1189 } 1190 1191 /** 1192 * This method adds an attribute the the current element, 1193 * but should not be used for an xsl:attribute child. 1194 * @see ExtendedContentHandler#addAttribute(java.lang.String, java.lang.String, 1195 * java.lang.String, java.lang.String, java.lang.String) 1196 */ 1197 public void addAttribute(String uri, String localName, String rawName, 1198 String type, String value) throws SAXException 1199 { 1200 if (m_elemContext.m_startTagOpen) { 1201 addAttributeAlways(uri, localName, rawName, type, value, false); 1202 } 1203 } 1204 1205 /** 1206 * @see org.xml.sax.DTDHandler#notationDecl(java.lang.String, 1207 * java.lang.String, java.lang.String) 1208 */ 1209 public void notationDecl(String arg0, String arg1, String arg2) 1210 throws SAXException 1211 { 1212 // This method just provides a definition to satisfy the interface 1213 // A particular sub-class of SerializerBase provides the implementation 1214 // (if desired) 1215 } 1216 1217 /** 1218 * @see org.xml.sax.DTDHandler#unparsedEntityDecl(java.lang.String, 1219 * java.lang.String, java.lang.String, java.lang.String) 1220 */ 1221 public void unparsedEntityDecl(String arg0, String arg1, String arg2, 1222 String arg3) throws SAXException { 1223 // This method just provides a definition to satisfy the interface 1224 // A particular sub-class of SerializerBase provides the implementation 1225 // (if desired) 1226 } 1227 1228 /** 1229 * If set to false the serializer does not expand DTD entities, 1230 * but leaves them as is, the default value is true. 1231 */ 1232 public void setDTDEntityExpansion(boolean expand) { 1233 // This method just provides a definition to satisfy the interface 1234 // A particular sub-class of SerializerBase provides the implementation (if desired) 1235 } 1236 1237 1238 /** 1239 * The CDATA section names stored in a whitespace separateed list with 1240 * each element being a word of the form "{uri}localName" This list 1241 * comes from the cdata-section-elements attribute. 1242 * 1243 * This field replaces m_cdataSectionElements Vector. 1244 */ 1245 protected String m_StringOfCDATASections = null; 1246 1247 boolean m_docIsEmpty = true; 1248 void initCdataElems(String s) 1249 { 1250 if (s != null) 1251 { 1252 int max = s.length(); 1253 1254 // true if we are in the middle of a pair of curly braces that delimit a URI 1255 boolean inCurly = false; 1256 1257 // true if we found a URI but haven't yet processed the local name 1258 boolean foundURI = false; 1259 1260 StringBuilder buf = new StringBuilder(); 1261 String uri = null; 1262 String localName = null; 1263 1264 // parse through string, breaking on whitespaces. I do this instead 1265 // of a tokenizer so I can track whitespace inside of curly brackets, 1266 // which theoretically shouldn't happen if they contain legal URLs. 1267 for (int i = 0; i < max; i++) 1268 { 1269 char c = s.charAt(i); 1270 1271 if (Character.isWhitespace(c)) 1272 { 1273 if (!inCurly) 1274 { 1275 if (buf.length() > 0) 1276 { 1277 localName = buf.toString(); 1278 if (!foundURI) 1279 uri = ""; 1280 addCDATAElement(uri,localName); 1281 buf.setLength(0); 1282 foundURI = false; 1283 } 1284 continue; 1285 } 1286 else 1287 buf.append(c); // add whitespace to the URI 1288 } 1289 else if ('{' == c) // starting a URI 1290 inCurly = true; 1291 else if ('}' == c) 1292 { 1293 // we just ended a URI 1294 foundURI = true; 1295 uri = buf.toString(); 1296 buf.setLength(0); 1297 inCurly = false; 1298 } 1299 else 1300 { 1301 // append non-whitespace, non-curly to current URI or localName being gathered. 1302 buf.append(c); 1303 } 1304 1305 } 1306 1307 if (buf.length() > 0) 1308 { 1309 // We have one last localName to process. 1310 localName = buf.toString(); 1311 if (!foundURI) 1312 uri = ""; 1313 addCDATAElement(uri,localName); 1314 } 1315 } 1316 } 1317 1318 protected java.util.HashMap<String, HashMap<String, String>> m_CdataElems = null; 1319 private void addCDATAElement(String uri, String localName) 1320 { 1321 if (m_CdataElems == null) { 1322 m_CdataElems = new java.util.HashMap<>(); 1323 } 1324 1325 HashMap<String,String> h = m_CdataElems.get(localName); 1326 if (h == null) { 1327 h = new HashMap<>(); 1328 m_CdataElems.put(localName,h); 1329 } 1330 h.put(uri,uri); 1331 1332 } 1333 1334 1335 /** 1336 * Return true if nothing has been sent to this result tree yet. 1337 * <p> 1338 * This is not a public API. 1339 * 1340 * @xsl.usage internal 1341 */ 1342 public boolean documentIsEmpty() { 1343 // If we haven't called startDocument() yet, then this document is empty 1344 return m_docIsEmpty && (m_elemContext.m_currentElemDepth == 0); 1345 } 1346 1347 /** 1348 * Return true if the current element in m_elemContext 1349 * is a CDATA section. 1350 * CDATA sections are specified in the <xsl:output> attribute 1351 * cdata-section-names or in the JAXP equivalent property. 1352 * In any case the format of the value of such a property is: 1353 * <pre> 1354 * "{uri1}localName1 {uri2}localName2 . . . " 1355 * </pre> 1356 * 1357 * <p> 1358 * This method is not a public API, but is only used internally by the serializer. 1359 */ 1360 protected boolean isCdataSection() { 1361 boolean b = false; 1362 1363 if (null != m_StringOfCDATASections) { 1364 if (m_elemContext.m_elementLocalName == null) { 1365 String localName = getLocalName(m_elemContext.m_elementName); 1366 m_elemContext.m_elementLocalName = localName; 1367 } 1368 1369 if ( m_elemContext.m_elementURI == null) { 1370 1371 m_elemContext.m_elementURI = getElementURI(); 1372 } 1373 else if ( m_elemContext.m_elementURI.length() == 0) { 1374 if ( m_elemContext.m_elementName == null) { 1375 m_elemContext.m_elementName = m_elemContext.m_elementLocalName; 1376 // leave URI as "", meaning in no namespace 1377 } 1378 else if (m_elemContext.m_elementLocalName.length() < m_elemContext.m_elementName.length()){ 1379 // We were told the URI was "", yet the name has a prefix since the name is longer than the localname. 1380 // So we will fix that incorrect information here. 1381 m_elemContext.m_elementURI = getElementURI(); 1382 } 1383 } 1384 1385 HashMap<String, String> h = null; 1386 if (m_CdataElems != null) { 1387 h = m_CdataElems.get(m_elemContext.m_elementLocalName); 1388 } 1389 if (h != null) { 1390 Object obj = h.get(m_elemContext.m_elementURI); 1391 if (obj != null) 1392 b = true; 1393 } 1394 1395 } 1396 return b; 1397 } 1398 1399 /** 1400 * Before this call m_elementContext.m_elementURI is null, 1401 * which means it is not yet known. After this call it 1402 * is non-null, but possibly "" meaning that it is in the 1403 * default namespace. 1404 * 1405 * @return The URI of the element, never null, but possibly "". 1406 */ 1407 private String getElementURI() { 1408 String uri = null; 1409 // At this point in processing we have received all the 1410 // namespace mappings 1411 // As we still don't know the elements namespace, 1412 // we now figure it out. 1413 1414 String prefix = getPrefixPart(m_elemContext.m_elementName); 1415 1416 if (prefix == null) { 1417 // no prefix so lookup the URI of the default namespace 1418 uri = m_prefixMap.lookupNamespace(""); 1419 } else { 1420 uri = m_prefixMap.lookupNamespace(prefix); 1421 } 1422 if (uri == null) { 1423 // We didn't find the namespace for the 1424 // prefix ... ouch, that shouldn't happen. 1425 // This is a hack, we really don't know 1426 // the namespace 1427 uri = EMPTYSTRING; 1428 } 1429 1430 return uri; 1431 } 1432 1433 1434 /** 1435 * Get the value of an output property, 1436 * the explicit value, if any, otherwise the 1437 * default value, if any, otherwise null. 1438 */ 1439 public String getOutputProperty(String name) { 1440 String val = getOutputPropertyNonDefault(name); 1441 // If no explicit value, try to get the default value 1442 if (val == null) 1443 val = getOutputPropertyDefault(name); 1444 return val; 1445 1446 } 1447 /** 1448 * Get the value of an output property, 1449 * not the default value. If there is a default 1450 * value, but no non-default value this method 1451 * will return null. 1452 * <p> 1453 * 1454 */ 1455 public String getOutputPropertyNonDefault(String name) { 1456 return getProp(name,false); 1457 } 1458 1459 /** 1460 * Return a {@link DOM3Serializer} interface into this serializer. If the 1461 * serializer does not support the {@link DOM3Serializer} interface, it should 1462 * return null. 1463 * 1464 * @return A {@link DOM3Serializer} interface into this serializer, or null 1465 * if the serializer is not DOM capable 1466 * @throws IOException An I/O exception occured 1467 * @see org.apache.xml.serializer.Serializer#asDOM3Serializer() 1468 */ 1469 public Object asDOM3Serializer() throws IOException 1470 { 1471 return new com.sun.org.apache.xml.internal.serializer.dom3.DOM3SerializerImpl(this); 1472 } 1473 1474 /** 1475 * Get the default value of an xsl:output property, 1476 * which would be null only if no default value exists 1477 * for the property. 1478 */ 1479 public String getOutputPropertyDefault(String name) { 1480 return getProp(name, true); 1481 } 1482 1483 /** 1484 * Set the value for the output property, typically from 1485 * an xsl:output element, but this does not change what 1486 * the default value is. 1487 */ 1488 public void setOutputProperty(String name, String val) { 1489 setProp(name,val,false); 1490 } 1491 1492 /** 1493 * Set the default value for an output property, but this does 1494 * not impact any explicitly set value. 1495 */ 1496 public void setOutputPropertyDefault(String name, String val) { 1497 setProp(name,val,true); 1498 1499 } 1500 1501 /** 1502 * A mapping of keys to explicitly set values, for example if 1503 * and <xsl:output/> has an "encoding" attribute, this 1504 * map will have what that attribute maps to. 1505 */ 1506 private HashMap<String, String> m_OutputProps; 1507 1508 /** 1509 * A mapping of keys to default values, for example if 1510 * the default value of the encoding is "UTF-8" then this 1511 * map will have that "encoding" maps to "UTF-8". 1512 */ 1513 private HashMap<String, String> m_OutputPropsDefault; 1514 1515 Set<String> getOutputPropDefaultKeys() { 1516 return m_OutputPropsDefault.keySet(); 1517 } 1518 1519 Set<String> getOutputPropKeys() { 1520 return m_OutputProps.keySet(); 1521 } 1522 1523 private String getProp(String name, boolean defaultVal) { 1524 if (m_OutputProps == null) { 1525 m_OutputProps = new HashMap<>(); 1526 m_OutputPropsDefault = new HashMap<>(); 1527 } 1528 1529 String val; 1530 if (defaultVal) 1531 val = m_OutputPropsDefault.get(name); 1532 else 1533 val = m_OutputProps.get(name); 1534 1535 return val; 1536 } 1537 1538 /** 1539 * 1540 * @param name The name of the property, e.g. "{http://myprop}indent-tabs" or "indent". 1541 * @param val The value of the property, e.g. "4" 1542 * @param defaultVal true if this is a default value being set for the property as 1543 * opposed to a user define on, set say explicitly in the stylesheet or via JAXP 1544 */ 1545 void setProp(String name, String val, boolean defaultVal) { 1546 if (m_OutputProps == null) { 1547 m_OutputProps = new HashMap<>(); 1548 m_OutputPropsDefault = new HashMap<>(); 1549 } 1550 1551 if (defaultVal) 1552 m_OutputPropsDefault.put(name,val); 1553 else { 1554 if (OutputKeys.CDATA_SECTION_ELEMENTS.equals(name) && val != null) { 1555 initCdataElems(val); 1556 String oldVal = m_OutputProps.get(name); 1557 String newVal; 1558 if (oldVal == null) 1559 newVal = oldVal + ' ' + val; 1560 else 1561 newVal = val; 1562 m_OutputProps.put(name,newVal); 1563 } 1564 else { 1565 m_OutputProps.put(name,val); 1566 } 1567 } 1568 } 1569 1570 /** 1571 * Get the first char of the local name 1572 * @param name Either a local name, or a local name 1573 * preceeded by a uri enclosed in curly braces. 1574 */ 1575 static char getFirstCharLocName(String name) { 1576 final char first; 1577 int i = name.indexOf('}'); 1578 if (i < 0) 1579 first = name.charAt(0); 1580 else 1581 first = name.charAt(i+1); 1582 return first; 1583 } 1584} 1585