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 }