1 /* 2 * Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.xml.internal.stream.writers; 27 28 import com.sun.org.apache.xerces.internal.impl.Constants; 29 import com.sun.org.apache.xerces.internal.impl.PropertyManager; 30 import com.sun.org.apache.xerces.internal.util.NamespaceSupport; 31 import com.sun.org.apache.xerces.internal.util.SymbolTable; 32 import com.sun.org.apache.xerces.internal.xni.QName; 33 import com.sun.xml.internal.stream.util.ReadOnlyIterator; 34 import java.io.FileOutputStream; 35 import java.io.IOException; 36 import java.io.OutputStream; 37 import java.io.OutputStreamWriter; 38 import java.io.Writer; 39 import java.nio.charset.Charset; 40 import java.nio.charset.CharsetEncoder; 41 import java.util.AbstractMap; 42 import java.util.ArrayList; 43 import java.util.HashMap; 44 import java.util.Iterator; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.Objects; 48 import java.util.Random; 49 import java.util.Set; 50 import javax.xml.XMLConstants; 51 import javax.xml.namespace.NamespaceContext; 52 import javax.xml.stream.XMLOutputFactory; 53 import javax.xml.stream.XMLStreamConstants; 54 import javax.xml.stream.XMLStreamException; 55 import javax.xml.transform.stream.StreamResult; 56 import jdk.xml.internal.SecuritySupport; 57 58 /** 59 * This class implements a StAX XMLStreamWriter. It extends 60 * <code>AbstractMap</code> in order to support a getter for 61 * implementation-specific properties. For example, you can get 62 * the underlying <code>OutputStream</code> by casting an instance 63 * of this class to <code>Map</code> and calling 64 * <code>getProperty(OUTPUTSTREAM_PROPERTY)</code>. 65 * 66 * @author Neeraj Bajaj 67 * @author K.Venugopal 68 * @author Santiago Pericas-Geertsen 69 * @author Sunitha Reddy 70 */ 71 public final class XMLStreamWriterImpl extends AbstractMap<Object, Object> 72 implements XMLStreamWriterBase { 73 74 public static final String START_COMMENT = "<!--"; 75 public static final String END_COMMENT = "-->"; 76 public static final String DEFAULT_ENCODING = " encoding=\"utf-8\""; 77 public static final String DEFAULT_XMLDECL = "<?xml version=\"1.0\" ?>"; 78 public static final String DEFAULT_XML_VERSION = "1.0"; 79 public static final char CLOSE_START_TAG = '>'; 80 public static final char OPEN_START_TAG = '<'; 81 public static final String OPEN_END_TAG = "</"; 82 public static final char CLOSE_END_TAG = '>'; 83 public static final String START_CDATA = "<![CDATA["; 84 public static final String END_CDATA = "]]>"; 85 public static final String CLOSE_EMPTY_ELEMENT = "/>"; 86 public static final String SPACE = " "; 87 public static final String UTF_8 = "UTF-8"; 88 89 public static final String OUTPUTSTREAM_PROPERTY = "sjsxp-outputstream"; 90 91 /** 92 * This flag can be used to turn escaping off for content. It does 93 * not apply to attribute content. 94 */ 95 boolean fEscapeCharacters = true; 96 97 /** 98 * Flag for the value of repairNamespace property 99 */ 100 private boolean fIsRepairingNamespace = false; 101 102 /** 103 * Underlying Writer to which characters are written. 104 */ 105 private Writer fWriter; 106 107 /** 108 * Underlying OutputStream to which <code>fWriter</code> 109 * writes to. May be null if unknown. 110 */ 111 private OutputStream fOutputStream = null; 112 113 /** 114 * Collects attributes when the writer is in reparing mode. 115 */ 116 private List<Attribute> fAttributeCache; 117 118 /** 119 * Collects namespace declarations when the writer is in reparing mode. 120 */ 121 private List<QName> fNamespaceDecls; 122 123 /** 124 * Namespace context encapsulating user specified context 125 * and context built by the writer 126 */ 127 private NamespaceContextImpl fNamespaceContext = null; 128 129 private NamespaceSupport fInternalNamespaceContext = null; 130 131 private Random fPrefixGen = null; 132 133 /** 134 * Reference to PropertyManager 135 */ 136 private PropertyManager fPropertyManager = null; 137 138 /** 139 * Flag to track if start tag is opened 140 */ 141 private boolean fStartTagOpened = false; 142 143 /** 144 * Boolean flag to indicate, if instance can be reused 145 */ 146 private boolean fReuse; 147 148 private SymbolTable fSymbolTable = new SymbolTable(); 149 150 private ElementStack fElementStack = new ElementStack(); //Change this .-Venu 151 152 final private String DEFAULT_PREFIX = fSymbolTable.addSymbol(""); 153 154 private final ReadOnlyIterator<String> fReadOnlyIterator = new ReadOnlyIterator<>(); 155 156 /** 157 * In some cases, this charset encoder is used to determine if a char is 158 * encodable by underlying writer. For example, an 8-bit char from the 159 * extended ASCII set is not encodable by 7-bit ASCII encoder. Unencodable 160 * chars are escaped using XML numeric entities. 161 */ 162 private CharsetEncoder fEncoder = null; 163 164 /** 165 * This is used to hold the namespace for attributes which happen to have 166 * the same uri as the default namespace; It's added to avoid changing the 167 * current impl. which has many redundant code for the repair mode 168 */ 169 Map<String, String> fAttrNamespace = null; 170 171 /** 172 * Creates a new instance of XMLStreamWriterImpl. Uses platform's default 173 * encoding. 174 * 175 * @param outputStream Underlying stream to write the bytes to 176 * @param props Properties used by this writer 177 */ XMLStreamWriterImpl(OutputStream outputStream, PropertyManager props)178 public XMLStreamWriterImpl(OutputStream outputStream, PropertyManager props) 179 throws IOException { 180 181 // cannot call this(outputStream, null, props); for constructor, 182 // OutputStreamWriter charsetName cannot be null 183 184 // use default encoding 185 this(new OutputStreamWriter(outputStream), props); 186 } 187 188 /** 189 * Creates a new instance of XMLStreamWriterImpl. 190 * 191 * @param outputStream Underlying stream to write the bytes 192 * @param encoding Encoding used to convert chars into bytes 193 * @param props Properties used by this writer 194 */ XMLStreamWriterImpl(OutputStream outputStream, String encoding, PropertyManager props)195 public XMLStreamWriterImpl(OutputStream outputStream, String encoding, 196 PropertyManager props) throws java.io.IOException { 197 this(new StreamResult(outputStream), encoding, props); 198 } 199 200 /** 201 * Creates a new instance of XMLStreamWriterImpl using a Writer. 202 * 203 * @param writer Underlying writer to which chars are written 204 * @param props Properties used by this writer 205 */ XMLStreamWriterImpl(Writer writer, PropertyManager props)206 public XMLStreamWriterImpl(Writer writer, PropertyManager props) 207 throws java.io.IOException { 208 this(new StreamResult(writer), null, props); 209 } 210 211 /** 212 * Creates a new instance of XMLStreamWriterImpl using a StreamResult. 213 * A StreamResult encasupates an OutputStream, a Writer or a SystemId. 214 * 215 * @param writer Underlying writer to which chars are written 216 * @param props Properties used by this writer 217 */ XMLStreamWriterImpl(StreamResult sr, String encoding, PropertyManager props)218 public XMLStreamWriterImpl(StreamResult sr, String encoding, 219 PropertyManager props) throws java.io.IOException { 220 setOutput(sr, encoding); 221 fPropertyManager = props; 222 init(); 223 } 224 225 /** 226 * Initialize an instance of this XMLStreamWriter. Allocate new instances 227 * for all the data structures. Set internal flags based on property values. 228 */ init()229 private void init() { 230 fReuse = false; 231 fNamespaceDecls = new ArrayList<>(); 232 fPrefixGen = new Random(); 233 fAttributeCache = new ArrayList<>(); 234 fInternalNamespaceContext = new NamespaceSupport(); 235 fInternalNamespaceContext.reset(); 236 fNamespaceContext = new NamespaceContextImpl(); 237 fNamespaceContext.internalContext = fInternalNamespaceContext; 238 239 // Set internal state based on property values 240 Boolean ob = (Boolean) fPropertyManager.getProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES); 241 fIsRepairingNamespace = ob; 242 ob = (Boolean) fPropertyManager.getProperty(Constants.ESCAPE_CHARACTERS); 243 setEscapeCharacters(ob); 244 } 245 246 /** 247 * Reset this instance so that it can be re-used. Do not read properties 248 * again. The method <code>setOutput(StreamResult, encoding)</code> must 249 * be called after this one. 250 */ reset()251 public void reset() { 252 reset(false); 253 } 254 255 /** 256 * Reset this instance so that it can be re-used. Clears but does not 257 * re-allocate internal data structures. 258 * 259 * @param resetProperties Indicates if properties should be read again 260 */ reset(boolean resetProperties)261 void reset(boolean resetProperties) { 262 if (!fReuse) { 263 throw new java.lang.IllegalStateException( 264 "close() Must be called before calling reset()"); 265 } 266 267 fReuse = false; 268 fNamespaceDecls.clear(); 269 fAttributeCache.clear(); 270 271 // reset Element/NamespaceContext stacks 272 fElementStack.clear(); 273 fInternalNamespaceContext.reset(); 274 275 fStartTagOpened = false; 276 fNamespaceContext.userContext = null; 277 278 if (resetProperties) { 279 Boolean ob = (Boolean) fPropertyManager.getProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES); 280 fIsRepairingNamespace = ob; 281 ob = (Boolean) fPropertyManager.getProperty(Constants.ESCAPE_CHARACTERS); 282 setEscapeCharacters(ob); 283 } 284 } 285 286 /** 287 * Use a StreamResult to initialize the output for this XMLStreamWriter. Check 288 * for OutputStream, Writer and then systemId, in that order. 289 * 290 * @param sr StreamResult encapsulating output information 291 * @param encoding Encoding to be used except when a Writer is available 292 */ setOutput(StreamResult sr, String encoding)293 public void setOutput(StreamResult sr, String encoding) 294 throws IOException { 295 296 if (sr.getOutputStream() != null) { 297 setOutputUsingStream(sr.getOutputStream(), encoding); 298 } 299 else if (sr.getWriter() != null) { 300 setOutputUsingWriter(sr.getWriter()); 301 } 302 else if (sr.getSystemId() != null) { 303 setOutputUsingStream(new FileOutputStream(sr.getSystemId()), 304 encoding); 305 } 306 } 307 setOutputUsingWriter(Writer writer)308 private void setOutputUsingWriter(Writer writer) 309 throws IOException 310 { 311 fWriter = writer; 312 313 if (writer instanceof OutputStreamWriter) { 314 String charset = ((OutputStreamWriter) writer).getEncoding(); 315 if (charset != null && !charset.equalsIgnoreCase("utf-8")) { 316 fEncoder = Charset.forName(charset).newEncoder(); 317 } 318 } 319 } 320 321 /** 322 * Utility method to create a writer when passed an OutputStream. Make 323 * sure to wrap an <code>OutputStreamWriter</code> using an 324 * <code>XMLWriter</code> for performance reasons. 325 * 326 * @param os Underlying OutputStream 327 * @param encoding Encoding used to convert chars into bytes 328 */ setOutputUsingStream(OutputStream os, String encoding)329 private void setOutputUsingStream(OutputStream os, String encoding) 330 throws IOException { 331 fOutputStream = os; 332 333 if (encoding != null) { 334 if (encoding.equalsIgnoreCase("utf-8")) { 335 fWriter = new UTF8OutputStreamWriter(os); 336 } 337 else { 338 fWriter = new XMLWriter(new OutputStreamWriter(os, encoding)); 339 fEncoder = Charset.forName(encoding).newEncoder(); 340 } 341 } else { 342 encoding = SecuritySupport.getSystemProperty("file.encoding"); 343 if (encoding != null && encoding.equalsIgnoreCase("utf-8")) { 344 fWriter = new UTF8OutputStreamWriter(os); 345 } else { 346 fWriter = new XMLWriter(new OutputStreamWriter(os)); 347 } 348 } 349 } 350 351 /** Can this instance be reused 352 * 353 * @return boolean boolean value to indicate if this instance can be reused or not 354 */ canReuse()355 public boolean canReuse() { 356 return fReuse; 357 } 358 setEscapeCharacters(boolean escape)359 public void setEscapeCharacters(boolean escape) { 360 fEscapeCharacters = escape; 361 } 362 getEscapeCharacters()363 public boolean getEscapeCharacters() { 364 return fEscapeCharacters; 365 } 366 367 /** 368 * Close this XMLStreamWriter by closing underlying writer. 369 */ 370 @Override close()371 public void close() throws XMLStreamException { 372 if (fWriter != null) { 373 try { 374 //fWriter.close(); 375 fWriter.flush(); 376 } catch (IOException e) { 377 throw new XMLStreamException(e); 378 } 379 } 380 fWriter = null; 381 fOutputStream = null; 382 fNamespaceDecls.clear(); 383 fAttributeCache.clear(); 384 fElementStack.clear(); 385 fInternalNamespaceContext.reset(); 386 fReuse = true; 387 fStartTagOpened = false; 388 fNamespaceContext.userContext = null; 389 } 390 391 /** 392 * Flush this XMLStreamWriter by flushin underlying writer. 393 */ 394 @Override flush()395 public void flush() throws XMLStreamException { 396 try { 397 fWriter.flush(); 398 } catch (IOException e) { 399 throw new XMLStreamException(e); 400 } 401 } 402 403 /** 404 * Return <code>NamespaceContext</code> being used by the writer. 405 * 406 * @return NamespaceContext 407 */ 408 @Override getNamespaceContext()409 public NamespaceContext getNamespaceContext() { 410 return fNamespaceContext; 411 } 412 413 /** 414 * Return a prefix associated with specified uri, or null if the 415 * uri is unknown. 416 * 417 * @param uri The namespace uri 418 * @throws XMLStreamException if uri specified is "" or null 419 */ 420 @Override getPrefix(String uri)421 public String getPrefix(String uri) throws XMLStreamException { 422 return fNamespaceContext.getPrefix(uri); 423 } 424 425 /** 426 * Returns value associated with the specified property name. 427 * 428 * @param str Property name 429 * @throws IllegalArgumentException if the specified property is not supported 430 * @return value associated with the specified property. 431 */ 432 @Override getProperty(String str)433 public Object getProperty(String str) 434 throws IllegalArgumentException { 435 if (str == null) { 436 throw new NullPointerException(); 437 } 438 439 if (!fPropertyManager.containsProperty(str)) { 440 throw new IllegalArgumentException("Property '" + str + 441 "' is not supported"); 442 } 443 444 return fPropertyManager.getProperty(str); 445 } 446 447 /** 448 * Set the specified URI as default namespace in the current namespace context. 449 * 450 * @param uri Namespace URI 451 */ 452 @Override setDefaultNamespace(String uri)453 public void setDefaultNamespace(String uri) throws XMLStreamException { 454 if (uri != null) { 455 uri = fSymbolTable.addSymbol(uri); 456 } 457 458 if (fIsRepairingNamespace) { 459 if (isDefaultNamespace(uri)) { 460 return; 461 } 462 463 QName qname = new QName(); 464 qname.setValues(DEFAULT_PREFIX, "xmlns", null, uri); 465 fNamespaceDecls.add(qname); 466 } else { 467 fInternalNamespaceContext.declarePrefix(DEFAULT_PREFIX, uri); 468 } 469 } 470 471 /** 472 * Sets the current <code>NamespaceContext</code> for prefix and uri bindings. 473 * This context becomes the root namespace context for writing and 474 * will replace the current root namespace context. Subsequent calls 475 * to setPrefix and setDefaultNamespace will bind namespaces using 476 * the context passed to the method as the root context for resolving 477 * namespaces. This method may only be called once at the start of the 478 * document. It does not cause the namespaces to be declared. If a 479 * namespace URI to prefix mapping is found in the namespace context 480 * it is treated as declared and the prefix may be used by the 481 * <code>XMLStreamWriter</code>. 482 * 483 * @param namespaceContext the namespace context to use for this writer, may not be null 484 * @throws XMLStreamException 485 */ 486 @Override setNamespaceContext(NamespaceContext namespaceContext)487 public void setNamespaceContext(NamespaceContext namespaceContext) 488 throws XMLStreamException { 489 fNamespaceContext.userContext = namespaceContext; 490 } 491 492 /** 493 * Sets the prefix the uri is bound to. This prefix is bound in the scope of 494 * the current START_ELEMENT / END_ELEMENT pair. If this method is called before 495 * a START_ELEMENT has been written the prefix is bound in the root scope. 496 * 497 * @param prefix 498 * @param uri 499 * @throws XMLStreamException 500 */ 501 @Override setPrefix(String prefix, String uri)502 public void setPrefix(String prefix, String uri) throws XMLStreamException { 503 504 if (prefix == null) { 505 throw new XMLStreamException("Prefix cannot be null"); 506 } 507 508 if (uri == null) { 509 throw new XMLStreamException("URI cannot be null"); 510 } 511 512 prefix = fSymbolTable.addSymbol(prefix); 513 uri = fSymbolTable.addSymbol(uri); 514 515 if (fIsRepairingNamespace) { 516 String tmpURI = fInternalNamespaceContext.getURI(prefix); 517 518 if ((tmpURI != null) && (tmpURI == uri)) { 519 return; 520 } 521 522 if(checkUserNamespaceContext(prefix,uri)) 523 return; 524 QName qname = new QName(); 525 qname.setValues(prefix,XMLConstants.XMLNS_ATTRIBUTE, null,uri); 526 fNamespaceDecls.add(qname); 527 528 return; 529 } 530 531 fInternalNamespaceContext.declarePrefix(prefix, uri); 532 } 533 534 @Override writeAttribute(String localName, String value)535 public void writeAttribute(String localName, String value) 536 throws XMLStreamException { 537 try { 538 if (!fStartTagOpened) { 539 throw new XMLStreamException( 540 "Attribute not associated with any element"); 541 } 542 543 if (fIsRepairingNamespace) { 544 Attribute attr = new Attribute(value); // Revisit:Dont create new one's. Reuse.-Venu 545 attr.setValues(null, localName, null, null); 546 fAttributeCache.add(attr); 547 548 return; 549 } 550 551 fWriter.write(" "); 552 fWriter.write(localName); 553 fWriter.write("=\""); 554 writeXMLContent( 555 value, 556 true, // true = escapeChars 557 true); // true = escapeDoubleQuotes 558 fWriter.write("\""); 559 } catch (IOException e) { 560 throw new XMLStreamException(e); 561 } 562 } 563 564 @Override writeAttribute(String namespaceURI, String localName, String value)565 public void writeAttribute(String namespaceURI, String localName, 566 String value) throws XMLStreamException { 567 try { 568 if (!fStartTagOpened) { 569 throw new XMLStreamException( 570 "Attribute not associated with any element"); 571 } 572 573 if (namespaceURI == null) { 574 throw new XMLStreamException("NamespaceURI cannot be null"); 575 } 576 577 namespaceURI = fSymbolTable.addSymbol(namespaceURI); 578 579 String prefix = fInternalNamespaceContext.getPrefix(namespaceURI); 580 581 if (!fIsRepairingNamespace) { 582 if (prefix == null) { 583 throw new XMLStreamException("Prefix cannot be null"); 584 } 585 586 writeAttributeWithPrefix(prefix, localName, value); 587 } else { 588 Attribute attr = new Attribute(value); 589 attr.setValues(null, localName, null, namespaceURI); 590 fAttributeCache.add(attr); 591 } 592 } catch (IOException e) { 593 throw new XMLStreamException(e); 594 } 595 } 596 writeAttributeWithPrefix(String prefix, String localName, String value)597 private void writeAttributeWithPrefix(String prefix, String localName, 598 String value) throws IOException { 599 fWriter.write(SPACE); 600 601 if ((prefix != null) && (!prefix.equals(XMLConstants.DEFAULT_NS_PREFIX))) { 602 fWriter.write(prefix); 603 fWriter.write(":"); 604 } 605 606 fWriter.write(localName); 607 fWriter.write("=\""); 608 writeXMLContent(value, 609 true, // true = escapeChars 610 true); // true = escapeDoubleQuotes 611 fWriter.write("\""); 612 } 613 614 @Override writeAttribute(String prefix, String namespaceURI, String localName, String value)615 public void writeAttribute(String prefix, String namespaceURI, 616 String localName, String value) throws XMLStreamException { 617 try { 618 if (!fStartTagOpened) { 619 throw new XMLStreamException( 620 "Attribute not associated with any element"); 621 } 622 623 if (namespaceURI == null) { 624 throw new XMLStreamException("NamespaceURI cannot be null"); 625 } 626 627 if (localName == null) { 628 throw new XMLStreamException("Local name cannot be null"); 629 } 630 631 if (!fIsRepairingNamespace) { 632 if (prefix == null || prefix.isEmpty()){ 633 if (!namespaceURI.isEmpty()) { 634 throw new XMLStreamException("prefix cannot be null or empty"); 635 } else { 636 writeAttributeWithPrefix(null, localName, value); 637 return; 638 } 639 } 640 641 if (!prefix.equals(XMLConstants.XML_NS_PREFIX) || 642 !namespaceURI.equals(XMLConstants.XML_NS_URI)) { 643 644 prefix = fSymbolTable.addSymbol(prefix); 645 namespaceURI = fSymbolTable.addSymbol(namespaceURI); 646 647 if (fInternalNamespaceContext.containsPrefixInCurrentContext(prefix)){ 648 649 String tmpURI = fInternalNamespaceContext.getURI(prefix); 650 651 if (tmpURI != null && tmpURI != namespaceURI){ 652 throw new XMLStreamException("Prefix "+prefix+" is " + 653 "already bound to "+tmpURI+ 654 ". Trying to rebind it to "+namespaceURI+" is an error."); 655 } 656 } 657 fInternalNamespaceContext.declarePrefix(prefix, namespaceURI); 658 } 659 writeAttributeWithPrefix(prefix, localName, value); 660 } else { 661 if (prefix != null) { 662 prefix = fSymbolTable.addSymbol(prefix); 663 } 664 665 namespaceURI = fSymbolTable.addSymbol(namespaceURI); 666 667 Attribute attr = new Attribute(value); 668 attr.setValues(prefix, localName, null, namespaceURI); 669 fAttributeCache.add(attr); 670 } 671 } catch (IOException e) { 672 throw new XMLStreamException(e); 673 } 674 } 675 676 @Override writeCData(String cdata)677 public void writeCData(String cdata) throws XMLStreamException { 678 try { 679 if (cdata == null) { 680 throw new XMLStreamException("cdata cannot be null"); 681 } 682 683 if (fStartTagOpened) { 684 closeStartTag(); 685 } 686 687 fWriter.write(START_CDATA); 688 fWriter.write(cdata); 689 fWriter.write(END_CDATA); 690 } catch (IOException e) { 691 throw new XMLStreamException(e); 692 } 693 } 694 695 @Override writeCharacters(String data)696 public void writeCharacters(String data) throws XMLStreamException { 697 try { 698 if (fStartTagOpened) { 699 closeStartTag(); 700 } 701 702 writeXMLContent(data); 703 } catch (IOException e) { 704 throw new XMLStreamException(e); 705 } 706 } 707 708 @Override writeCharacters(char[] data, int start, int len)709 public void writeCharacters(char[] data, int start, int len) 710 throws XMLStreamException { 711 try { 712 if (fStartTagOpened) { 713 closeStartTag(); 714 } 715 716 writeXMLContent(data, start, len, fEscapeCharacters); 717 } catch (IOException e) { 718 throw new XMLStreamException(e); 719 } 720 } 721 722 @Override writeComment(String comment)723 public void writeComment(String comment) throws XMLStreamException { 724 try { 725 if (fStartTagOpened) { 726 closeStartTag(); 727 } 728 729 fWriter.write(START_COMMENT); 730 731 if (comment != null) { 732 fWriter.write(comment); 733 } 734 735 fWriter.write(END_COMMENT); 736 } catch (IOException e) { 737 throw new XMLStreamException(e); 738 } 739 } 740 741 @Override writeDTD(String dtd)742 public void writeDTD(String dtd) throws XMLStreamException { 743 try { 744 if (fStartTagOpened) { 745 closeStartTag(); 746 } 747 748 fWriter.write(dtd); 749 } catch (IOException e) { 750 throw new XMLStreamException(e); 751 } 752 } 753 754 /* 755 * Write default Namespace. 756 * 757 * If namespaceURI == null, 758 * then it is assumed to be equivilent to {@link XMLConstants.NULL_NS_URI}, 759 * i.e. there is no Namespace. 760 * 761 * @param namespaceURI NamespaceURI to declare. 762 * 763 * @throws XMLStreamException 764 * 765 * @see <a href="http://www.w3.org/TR/REC-xml-names/#defaulting"> 766 * Namespaces in XML, 5.2 Namespace Defaulting</a> 767 */ 768 @Override writeDefaultNamespace(String namespaceURI)769 public void writeDefaultNamespace(String namespaceURI) 770 throws XMLStreamException { 771 772 // normalize namespaceURI 773 String namespaceURINormalized; 774 if (namespaceURI == null) { 775 namespaceURINormalized = ""; // XMLConstants.NULL_NS_URI 776 } else { 777 namespaceURINormalized = namespaceURI; 778 } 779 780 try { 781 if (!fStartTagOpened) { 782 throw new IllegalStateException( 783 "Namespace Attribute not associated with any element"); 784 } 785 786 if (fIsRepairingNamespace) { 787 QName qname = new QName(); 788 qname.setValues(XMLConstants.DEFAULT_NS_PREFIX, 789 XMLConstants.XMLNS_ATTRIBUTE, null, namespaceURINormalized); 790 fNamespaceDecls.add(qname); 791 792 return; 793 } 794 795 namespaceURINormalized = fSymbolTable.addSymbol(namespaceURINormalized); 796 797 if (fInternalNamespaceContext.containsPrefixInCurrentContext("")){ 798 799 String tmp = fInternalNamespaceContext.getURI(""); 800 801 if (tmp != null && !tmp.equals(namespaceURINormalized)) { 802 throw new XMLStreamException( 803 "xmlns has been already bound to " +tmp + 804 ". Rebinding it to "+ namespaceURINormalized + 805 " is an error"); 806 } 807 } 808 fInternalNamespaceContext.declarePrefix("", namespaceURINormalized); 809 810 // use common namespace code with a prefix == null for xmlns="..." 811 writenamespace(null, namespaceURINormalized); 812 } catch (IOException e) { 813 throw new XMLStreamException(e); 814 } 815 } 816 817 @Override writeEmptyElement(String localName)818 public void writeEmptyElement(String localName) throws XMLStreamException { 819 try { 820 if (fStartTagOpened) { 821 closeStartTag(); 822 } 823 824 openStartTag(); 825 fElementStack.push(null, localName, null, null, true); 826 fInternalNamespaceContext.pushContext(); 827 828 if (!fIsRepairingNamespace) { 829 fWriter.write(localName); 830 } 831 } catch (IOException e) { 832 throw new XMLStreamException(e); 833 } 834 } 835 836 @Override writeEmptyElement(String namespaceURI, String localName)837 public void writeEmptyElement(String namespaceURI, String localName) 838 throws XMLStreamException { 839 if (namespaceURI == null) { 840 throw new XMLStreamException("NamespaceURI cannot be null"); 841 } 842 843 namespaceURI = fSymbolTable.addSymbol(namespaceURI); 844 845 String prefix = fNamespaceContext.getPrefix(namespaceURI); 846 writeEmptyElement(prefix, localName, namespaceURI); 847 } 848 849 @Override writeEmptyElement(String prefix, String localName, String namespaceURI)850 public void writeEmptyElement(String prefix, String localName, 851 String namespaceURI) throws XMLStreamException { 852 try { 853 if (localName == null) { 854 throw new XMLStreamException("Local Name cannot be null"); 855 } 856 857 if (namespaceURI == null) { 858 throw new XMLStreamException("NamespaceURI cannot be null"); 859 } 860 861 if (prefix != null) { 862 prefix = fSymbolTable.addSymbol(prefix); 863 } 864 865 namespaceURI = fSymbolTable.addSymbol(namespaceURI); 866 867 if (fStartTagOpened) { 868 closeStartTag(); 869 } 870 871 openStartTag(); 872 873 fElementStack.push(prefix, localName, null, namespaceURI, true); 874 fInternalNamespaceContext.pushContext(); 875 876 if (!fIsRepairingNamespace) { 877 if (prefix == null) { 878 throw new XMLStreamException("NamespaceURI " + 879 namespaceURI + " has not been bound to any prefix"); 880 } 881 } else { 882 return; 883 } 884 885 if ((prefix != null) && (!prefix.equals(XMLConstants.DEFAULT_NS_PREFIX))) { 886 fWriter.write(prefix); 887 fWriter.write(":"); 888 } 889 890 fWriter.write(localName); 891 } catch (IOException e) { 892 throw new XMLStreamException(e); 893 } 894 } 895 896 @Override writeEndDocument()897 public void writeEndDocument() throws XMLStreamException { 898 try { 899 if (fStartTagOpened) { 900 closeStartTag(); 901 } 902 903 while (!fElementStack.empty()) { 904 ElementState elem = fElementStack.pop(); 905 fInternalNamespaceContext.popContext(); 906 907 if (elem.isEmpty) { 908 //fWriter.write(CLOSE_EMPTY_ELEMENT); 909 } else { 910 fWriter.write(OPEN_END_TAG); 911 912 if ((elem.prefix != null) && !(elem.prefix).isEmpty()) { 913 fWriter.write(elem.prefix); 914 fWriter.write(":"); 915 } 916 917 fWriter.write(elem.localpart); 918 fWriter.write(CLOSE_END_TAG); 919 } 920 } 921 } catch (IOException e) { 922 throw new XMLStreamException(e); 923 } catch (ArrayIndexOutOfBoundsException e) { 924 throw new XMLStreamException("No more elements to write"); 925 } 926 } 927 928 @Override writeEndElement()929 public void writeEndElement() throws XMLStreamException { 930 try { 931 if (fStartTagOpened) { 932 closeStartTag(); 933 } 934 935 ElementState currentElement = fElementStack.pop(); 936 937 if (currentElement == null) { 938 throw new XMLStreamException("No element was found to write"); 939 } 940 941 if (currentElement.isEmpty) { 942 //fWriter.write(CLOSE_EMPTY_ELEMENT); 943 return; 944 } 945 946 fWriter.write(OPEN_END_TAG); 947 948 if ((currentElement.prefix != null) && 949 !(currentElement.prefix).isEmpty()) { 950 fWriter.write(currentElement.prefix); 951 fWriter.write(":"); 952 } 953 954 fWriter.write(currentElement.localpart); 955 fWriter.write(CLOSE_END_TAG); 956 fInternalNamespaceContext.popContext(); 957 } catch (IOException e) { 958 throw new XMLStreamException(e); 959 } catch (ArrayIndexOutOfBoundsException e) { 960 throw new XMLStreamException( 961 "No element was found to write: " 962 + e.toString(), e); 963 } 964 } 965 966 @Override writeEntityRef(String refName)967 public void writeEntityRef(String refName) throws XMLStreamException { 968 try { 969 if (fStartTagOpened) { 970 closeStartTag(); 971 } 972 973 fWriter.write('&'); 974 fWriter.write(refName); 975 fWriter.write(';'); 976 } catch (IOException e) { 977 throw new XMLStreamException(e); 978 } 979 } 980 981 /** 982 * Write a Namespace declaration. 983 * 984 * If namespaceURI == null, 985 * then it is assumed to be equivilent to {@link XMLConstants.NULL_NS_URI}, 986 * i.e. there is no Namespace. 987 * 988 * @param prefix Prefix to bind. 989 * @param namespaceURI NamespaceURI to declare. 990 * 991 * @throws XMLStreamException 992 * 993 * @see <a href="http://www.w3.org/TR/REC-xml-names/#defaulting"> 994 * Namespaces in XML, 5.2 Namespace Defaulting</a> 995 */ 996 @Override writeNamespace(String prefix, String namespaceURI)997 public void writeNamespace(String prefix, String namespaceURI) 998 throws XMLStreamException { 999 1000 // normalize namespaceURI 1001 String namespaceURINormalized; 1002 if (namespaceURI == null) { 1003 namespaceURINormalized = ""; // XMLConstants.NULL_NS_URI 1004 } else { 1005 namespaceURINormalized = namespaceURI; 1006 } 1007 1008 try { 1009 QName qname; 1010 1011 if (!fStartTagOpened) { 1012 throw new IllegalStateException( 1013 "Invalid state: start tag is not opened at writeNamespace(" 1014 + prefix 1015 + ", " 1016 + namespaceURINormalized 1017 + ")"); 1018 } 1019 1020 // is this the default Namespace? 1021 if (prefix == null 1022 || prefix.equals(XMLConstants.DEFAULT_NS_PREFIX) 1023 || prefix.equals(XMLConstants.XMLNS_ATTRIBUTE)) { 1024 writeDefaultNamespace(namespaceURINormalized); 1025 return; 1026 } 1027 1028 if (prefix.equals(XMLConstants.XML_NS_PREFIX) && namespaceURINormalized.equals(XMLConstants.XML_NS_URI)) 1029 return; 1030 1031 prefix = fSymbolTable.addSymbol(prefix); 1032 namespaceURINormalized = fSymbolTable.addSymbol(namespaceURINormalized); 1033 1034 if (fIsRepairingNamespace) { 1035 String tmpURI = fInternalNamespaceContext.getURI(prefix); 1036 1037 if ((tmpURI != null) && (tmpURI.equals(namespaceURINormalized))) { 1038 return; 1039 } 1040 1041 qname = new QName(); 1042 qname.setValues(prefix, XMLConstants.XMLNS_ATTRIBUTE, null, 1043 namespaceURINormalized); 1044 fNamespaceDecls.add(qname); 1045 1046 return; 1047 } 1048 1049 1050 if (fInternalNamespaceContext.containsPrefixInCurrentContext(prefix)){ 1051 1052 String tmp = fInternalNamespaceContext.getURI(prefix); 1053 1054 if (tmp != null && !tmp.equals(namespaceURINormalized)) { 1055 1056 throw new XMLStreamException("prefix "+prefix+ 1057 " has been already bound to " +tmp + 1058 ". Rebinding it to "+ namespaceURINormalized+ 1059 " is an error"); 1060 } 1061 } 1062 1063 fInternalNamespaceContext.declarePrefix(prefix, namespaceURINormalized); 1064 writenamespace(prefix, namespaceURINormalized); 1065 1066 } catch (IOException e) { 1067 throw new XMLStreamException(e); 1068 } 1069 } 1070 writenamespace(String prefix, String namespaceURI)1071 private void writenamespace(String prefix, String namespaceURI) 1072 throws IOException { 1073 fWriter.write(" xmlns"); 1074 1075 if ((prefix != null) && (!prefix.equals(XMLConstants.DEFAULT_NS_PREFIX))) { 1076 fWriter.write(":"); 1077 fWriter.write(prefix); 1078 } 1079 1080 fWriter.write("=\""); 1081 writeXMLContent( 1082 namespaceURI, 1083 true, // true = escapeChars 1084 true); // true = escapeDoubleQuotes 1085 fWriter.write("\""); 1086 } 1087 1088 @Override writeProcessingInstruction(String target)1089 public void writeProcessingInstruction(String target) 1090 throws XMLStreamException { 1091 try { 1092 if (fStartTagOpened) { 1093 closeStartTag(); 1094 } 1095 1096 if (target != null) { 1097 fWriter.write("<?"); 1098 fWriter.write(target); 1099 fWriter.write("?>"); 1100 1101 return; 1102 } 1103 } catch (IOException e) { 1104 throw new XMLStreamException(e); 1105 } 1106 1107 throw new XMLStreamException("PI target cannot be null"); 1108 } 1109 1110 /** 1111 * @param target 1112 * @param data 1113 * @throws XMLStreamException 1114 */ 1115 @Override writeProcessingInstruction(String target, String data)1116 public void writeProcessingInstruction(String target, String data) 1117 throws XMLStreamException { 1118 try { 1119 if (fStartTagOpened) { 1120 closeStartTag(); 1121 } 1122 1123 if ((target == null) || (data == null)) { 1124 throw new XMLStreamException("PI target cannot be null"); 1125 } 1126 1127 fWriter.write("<?"); 1128 fWriter.write(target); 1129 fWriter.write(SPACE); 1130 fWriter.write(data); 1131 fWriter.write("?>"); 1132 } catch (IOException e) { 1133 throw new XMLStreamException(e); 1134 } 1135 } 1136 1137 /** 1138 * Writes the XML declaration. 1139 * 1140 * @throws XMLStreamException in case of an IOException 1141 */ 1142 @Override writeStartDocument()1143 public void writeStartDocument() throws XMLStreamException { 1144 writeStartDocument(null, null, false, false); 1145 } 1146 1147 /** 1148 * Writes the XML declaration. 1149 * 1150 * @param version the specified version 1151 * @throws XMLStreamException in case of an IOException 1152 */ 1153 @Override writeStartDocument(String version)1154 public void writeStartDocument(String version) throws XMLStreamException { 1155 writeStartDocument(null, version, false, false); 1156 } 1157 1158 /** 1159 * Writes the XML declaration. 1160 * 1161 * @param encoding the specified encoding 1162 * @param version the specified version 1163 * @throws XMLStreamException in case of an IOException 1164 */ 1165 @Override writeStartDocument(String encoding, String version)1166 public void writeStartDocument(String encoding, String version) 1167 throws XMLStreamException { 1168 writeStartDocument(encoding, version, false, false); 1169 } 1170 writeStartDocument(String encoding, String version, boolean standalone, boolean standaloneSet)1171 public void writeStartDocument(String encoding, String version, 1172 boolean standalone, boolean standaloneSet) 1173 throws XMLStreamException { 1174 1175 try { 1176 if ((encoding == null || encoding.length() == 0) 1177 && (version == null || version.length() == 0) 1178 && (!standaloneSet)) { 1179 fWriter.write(DEFAULT_XMLDECL); 1180 return; 1181 } 1182 1183 // Verify the encoding before writing anything 1184 if (encoding != null && !encoding.isEmpty()) { 1185 verifyEncoding(encoding); 1186 } 1187 1188 fWriter.write("<?xml version=\""); 1189 1190 if ((version == null) || version.isEmpty()) { 1191 fWriter.write(DEFAULT_XML_VERSION); 1192 } else { 1193 fWriter.write(version); 1194 } 1195 1196 if (encoding != null && !encoding.isEmpty()) { 1197 fWriter.write("\" encoding=\""); 1198 fWriter.write(encoding); 1199 } 1200 1201 if (standaloneSet) { 1202 fWriter.write("\" standalone=\""); 1203 if (standalone) { 1204 fWriter.write("yes"); 1205 } else { 1206 fWriter.write("no"); 1207 } 1208 } 1209 1210 fWriter.write("\"?>"); 1211 } catch (IOException ex) { 1212 throw new XMLStreamException(ex); 1213 } 1214 } 1215 1216 /** 1217 * Verifies that the encoding is consistent between the underlying encoding 1218 * and that specified. 1219 * 1220 * @param encoding the specified encoding 1221 * @throws XMLStreamException if they do not match 1222 */ verifyEncoding(String encoding)1223 private void verifyEncoding(String encoding) throws XMLStreamException { 1224 1225 String streamEncoding = null; 1226 if (fWriter instanceof OutputStreamWriter) { 1227 streamEncoding = ((OutputStreamWriter) fWriter).getEncoding(); 1228 } 1229 else if (fWriter instanceof UTF8OutputStreamWriter) { 1230 streamEncoding = ((UTF8OutputStreamWriter) fWriter).getEncoding(); 1231 } 1232 else if (fWriter instanceof XMLWriter) { 1233 streamEncoding = ((OutputStreamWriter) ((XMLWriter)fWriter).getWriter()).getEncoding(); 1234 } 1235 1236 if (streamEncoding != null && !streamEncoding.equalsIgnoreCase(encoding)) { 1237 // If the equality check failed, check for charset encoding aliases 1238 boolean foundAlias = false; 1239 Set<String> aliases = Charset.forName(encoding).aliases(); 1240 for (Iterator<String> it = aliases.iterator(); !foundAlias && it.hasNext(); ) { 1241 if (streamEncoding.equalsIgnoreCase(it.next())) { 1242 foundAlias = true; 1243 } 1244 } 1245 // If no alias matches the encoding name, then report error 1246 if (!foundAlias) { 1247 throw new XMLStreamException("Underlying stream encoding '" 1248 + streamEncoding 1249 + "' and input paramter for writeStartDocument() method '" 1250 + encoding + "' do not match."); 1251 } 1252 } 1253 } 1254 1255 /** 1256 * @param localName 1257 * @throws XMLStreamException 1258 */ 1259 @Override writeStartElement(String localName)1260 public void writeStartElement(String localName) throws XMLStreamException { 1261 try { 1262 if (localName == null) { 1263 throw new XMLStreamException("Local Name cannot be null"); 1264 } 1265 1266 if (fStartTagOpened) { 1267 closeStartTag(); 1268 } 1269 1270 openStartTag(); 1271 fElementStack.push(null, localName, null, null, false); 1272 fInternalNamespaceContext.pushContext(); 1273 1274 if (fIsRepairingNamespace) { 1275 return; 1276 } 1277 1278 fWriter.write(localName); 1279 } catch (IOException ex) { 1280 throw new XMLStreamException(ex); 1281 } 1282 } 1283 1284 /** 1285 * @param namespaceURI 1286 * @param localName 1287 * @throws XMLStreamException 1288 */ 1289 @Override writeStartElement(String namespaceURI, String localName)1290 public void writeStartElement(String namespaceURI, String localName) 1291 throws XMLStreamException { 1292 if (localName == null) { 1293 throw new XMLStreamException("Local Name cannot be null"); 1294 } 1295 1296 if (namespaceURI == null) { 1297 throw new XMLStreamException("NamespaceURI cannot be null"); 1298 } 1299 1300 namespaceURI = fSymbolTable.addSymbol(namespaceURI); 1301 1302 String prefix = null; 1303 1304 if (!fIsRepairingNamespace) { 1305 prefix = fNamespaceContext.getPrefix(namespaceURI); 1306 1307 if (prefix != null) { 1308 prefix = fSymbolTable.addSymbol(prefix); 1309 } 1310 } 1311 1312 writeStartElement(prefix, localName, namespaceURI); 1313 } 1314 1315 /** 1316 * @param prefix 1317 * @param localName 1318 * @param namespaceURI 1319 * @throws XMLStreamException 1320 */ 1321 @Override writeStartElement(String prefix, String localName, String namespaceURI)1322 public void writeStartElement(String prefix, String localName, 1323 String namespaceURI) throws XMLStreamException { 1324 try { 1325 if (localName == null) { 1326 throw new XMLStreamException("Local Name cannot be null"); 1327 } 1328 1329 if (namespaceURI == null) { 1330 throw new XMLStreamException("NamespaceURI cannot be null"); 1331 } 1332 1333 if (!fIsRepairingNamespace) { 1334 if (prefix == null) { 1335 throw new XMLStreamException("Prefix cannot be null"); 1336 } 1337 } 1338 1339 if (fStartTagOpened) { 1340 closeStartTag(); 1341 } 1342 1343 openStartTag(); 1344 namespaceURI = fSymbolTable.addSymbol(namespaceURI); 1345 1346 if (prefix != null) { 1347 prefix = fSymbolTable.addSymbol(prefix); 1348 } 1349 1350 fElementStack.push(prefix, localName, null, namespaceURI, false); 1351 fInternalNamespaceContext.pushContext(); 1352 1353 String tmpPrefix = fNamespaceContext.getPrefix(namespaceURI); 1354 1355 1356 if ((prefix != null) && 1357 ((tmpPrefix == null) || !prefix.equals(tmpPrefix))) { 1358 fInternalNamespaceContext.declarePrefix(prefix, namespaceURI); 1359 1360 } 1361 1362 if (fIsRepairingNamespace) { 1363 if ((prefix == null) || 1364 ((tmpPrefix != null) && prefix.equals(tmpPrefix))) { 1365 return; 1366 } 1367 1368 QName qname = new QName(); 1369 qname.setValues(prefix, XMLConstants.XMLNS_ATTRIBUTE, null, 1370 namespaceURI); 1371 fNamespaceDecls.add(qname); 1372 1373 return; 1374 } 1375 1376 if ((prefix != null) && (prefix != XMLConstants.DEFAULT_NS_PREFIX)) { 1377 fWriter.write(prefix); 1378 fWriter.write(":"); 1379 } 1380 1381 fWriter.write(localName); 1382 1383 } catch (IOException ex) { 1384 throw new XMLStreamException(ex); 1385 } 1386 } 1387 1388 /** 1389 * Writes character reference in hex format. 1390 */ writeCharRef(int codePoint)1391 private void writeCharRef(int codePoint) throws IOException { 1392 fWriter.write( "&#x" ); 1393 fWriter.write( Integer.toHexString(codePoint) ); 1394 fWriter.write( ';' ); 1395 } 1396 1397 /** 1398 * Writes XML content to underlying writer. Escapes characters unless 1399 * escaping character feature is turned off. 1400 */ writeXMLContent(char[] content, int start, int length, boolean escapeChars)1401 private void writeXMLContent(char[] content, int start, int length, 1402 boolean escapeChars) throws IOException { 1403 if (!escapeChars) { 1404 fWriter.write(content, start, length); 1405 1406 return; 1407 } 1408 1409 // Index of the next char to be written 1410 int startWritePos = start; 1411 1412 final int end = start + length; 1413 1414 for (int index = start; index < end; index++) { 1415 char ch = content[index]; 1416 1417 if (fEncoder != null && !fEncoder.canEncode(ch)){ 1418 fWriter.write(content, startWritePos, index - startWritePos ); 1419 1420 // Check if current and next characters forms a surrogate pair 1421 // and escape it to avoid generation of invalid xml content 1422 if ( index != end - 1 && Character.isSurrogatePair(ch, content[index+1])) { 1423 writeCharRef(Character.toCodePoint(ch, content[index+1])); 1424 index++; 1425 } else { 1426 writeCharRef(ch); 1427 } 1428 startWritePos = index + 1; 1429 continue; 1430 } 1431 1432 switch (ch) { 1433 case '<': 1434 fWriter.write(content, startWritePos, index - startWritePos); 1435 fWriter.write("<"); 1436 startWritePos = index + 1; 1437 1438 break; 1439 1440 case '&': 1441 fWriter.write(content, startWritePos, index - startWritePos); 1442 fWriter.write("&"); 1443 startWritePos = index + 1; 1444 1445 break; 1446 1447 case '>': 1448 fWriter.write(content, startWritePos, index - startWritePos); 1449 fWriter.write(">"); 1450 startWritePos = index + 1; 1451 1452 break; 1453 } 1454 } 1455 1456 // Write any pending data 1457 fWriter.write(content, startWritePos, end - startWritePos); 1458 } 1459 writeXMLContent(String content)1460 private void writeXMLContent(String content) throws IOException { 1461 if ((content != null) && (content.length() > 0)) { 1462 writeXMLContent(content, 1463 fEscapeCharacters, // boolean = escapeChars 1464 false); // false = escapeDoubleQuotes 1465 } 1466 } 1467 1468 /** 1469 * Writes XML content to underlying writer. Escapes characters unless 1470 * escaping character feature is turned off. 1471 */ writeXMLContent( String content, boolean escapeChars, boolean escapeDoubleQuotes)1472 private void writeXMLContent( 1473 String content, 1474 boolean escapeChars, 1475 boolean escapeDoubleQuotes) 1476 throws IOException { 1477 1478 if (!escapeChars) { 1479 fWriter.write(content); 1480 1481 return; 1482 } 1483 1484 // Index of the next char to be written 1485 int startWritePos = 0; 1486 1487 final int end = content.length(); 1488 1489 for (int index = 0; index < end; index++) { 1490 char ch = content.charAt(index); 1491 1492 if (fEncoder != null && !fEncoder.canEncode(ch)){ 1493 fWriter.write(content, startWritePos, index - startWritePos ); 1494 1495 // Check if current and next characters forms a surrogate pair 1496 // and escape it to avoid generation of invalid xml content 1497 if ( index != end - 1 && Character.isSurrogatePair(ch, content.charAt(index+1))) { 1498 writeCharRef(Character.toCodePoint(ch, content.charAt(index+1))); 1499 index++; 1500 } else { 1501 writeCharRef(ch); 1502 } 1503 1504 startWritePos = index + 1; 1505 continue; 1506 } 1507 1508 switch (ch) { 1509 case '<': 1510 fWriter.write(content, startWritePos, index - startWritePos); 1511 fWriter.write("<"); 1512 startWritePos = index + 1; 1513 1514 break; 1515 1516 case '&': 1517 fWriter.write(content, startWritePos, index - startWritePos); 1518 fWriter.write("&"); 1519 startWritePos = index + 1; 1520 1521 break; 1522 1523 case '>': 1524 fWriter.write(content, startWritePos, index - startWritePos); 1525 fWriter.write(">"); 1526 startWritePos = index + 1; 1527 1528 break; 1529 1530 case '"': 1531 fWriter.write(content, startWritePos, index - startWritePos); 1532 if (escapeDoubleQuotes) { 1533 fWriter.write("""); 1534 } else { 1535 fWriter.write('"'); 1536 } 1537 startWritePos = index + 1; 1538 1539 break; 1540 } 1541 } 1542 1543 // Write any pending data 1544 fWriter.write(content, startWritePos, end - startWritePos); 1545 } 1546 1547 /** 1548 * marks close of start tag and writes the same into the writer. 1549 */ closeStartTag()1550 private void closeStartTag() throws XMLStreamException { 1551 try { 1552 ElementState currentElement = fElementStack.peek(); 1553 1554 if (fIsRepairingNamespace) { 1555 repair(); 1556 correctPrefix(currentElement, XMLStreamConstants.START_ELEMENT); 1557 1558 if ((currentElement.prefix != null) && 1559 (currentElement.prefix != XMLConstants.DEFAULT_NS_PREFIX)) { 1560 fWriter.write(currentElement.prefix); 1561 fWriter.write(":"); 1562 } 1563 1564 fWriter.write(currentElement.localpart); 1565 1566 int len = fNamespaceDecls.size(); 1567 QName qname; 1568 1569 for (int i = 0; i < len; i++) { 1570 qname = fNamespaceDecls.get(i); 1571 1572 if (qname != null) { 1573 if (fInternalNamespaceContext.declarePrefix(qname.prefix, 1574 qname.uri)) { 1575 writenamespace(qname.prefix, qname.uri); 1576 } 1577 } 1578 } 1579 1580 fNamespaceDecls.clear(); 1581 1582 Attribute attr; 1583 1584 for (int j = 0; j < fAttributeCache.size(); j++) { 1585 attr = fAttributeCache.get(j); 1586 1587 if ((attr.prefix != null) && (attr.uri != null)) { 1588 if (!attr.prefix.isEmpty() && !attr.uri.isEmpty() ) { 1589 String tmp = fInternalNamespaceContext.getPrefix(attr.uri); 1590 1591 if ((tmp == null) || (!tmp.equals(attr.prefix))) { 1592 tmp = getAttrPrefix(attr.uri); 1593 if (tmp == null) { 1594 if (fInternalNamespaceContext.declarePrefix(attr.prefix, 1595 attr.uri)) { 1596 writenamespace(attr.prefix, attr.uri); 1597 } 1598 } else { 1599 writenamespace(attr.prefix, attr.uri); 1600 } 1601 } 1602 } 1603 } 1604 1605 writeAttributeWithPrefix(attr.prefix, attr.localpart, 1606 attr.value); 1607 } 1608 fAttrNamespace = null; 1609 fAttributeCache.clear(); 1610 } 1611 1612 if (currentElement.isEmpty) { 1613 fElementStack.pop(); 1614 fInternalNamespaceContext.popContext(); 1615 fWriter.write(CLOSE_EMPTY_ELEMENT); 1616 } else { 1617 fWriter.write(CLOSE_START_TAG); 1618 } 1619 1620 fStartTagOpened = false; 1621 } catch (IOException ex) { 1622 fStartTagOpened = false; 1623 throw new XMLStreamException(ex); 1624 } 1625 } 1626 1627 /** 1628 * marks open of start tag and writes the same into the writer. 1629 */ openStartTag()1630 private void openStartTag() throws IOException { 1631 fStartTagOpened = true; 1632 fWriter.write(OPEN_START_TAG); 1633 } 1634 1635 /** 1636 * 1637 * @param uri 1638 * @return 1639 */ correctPrefix(QName attr, int type)1640 private void correctPrefix(QName attr, int type) { 1641 String tmpPrefix; 1642 String prefix; 1643 String uri; 1644 prefix = attr.prefix; 1645 uri = attr.uri; 1646 boolean isSpecialCaseURI = false; 1647 1648 if (prefix == null || prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)) { 1649 if (uri == null) { 1650 return; 1651 } 1652 1653 if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix) && uri.equals(XMLConstants.DEFAULT_NS_PREFIX)) 1654 return; 1655 1656 uri = fSymbolTable.addSymbol(uri); 1657 1658 QName decl; 1659 1660 for (int i = 0; i < fNamespaceDecls.size(); i++) { 1661 decl = fNamespaceDecls.get(i); 1662 1663 if ((decl != null) && (decl.uri.equals(attr.uri))) { 1664 attr.prefix = decl.prefix; 1665 1666 return; 1667 } 1668 } 1669 1670 tmpPrefix = fNamespaceContext.getPrefix(uri); 1671 1672 if (XMLConstants.DEFAULT_NS_PREFIX.equals(tmpPrefix)) { 1673 if (type == XMLStreamConstants.START_ELEMENT) { 1674 return; 1675 } 1676 else if (type == XMLStreamConstants.ATTRIBUTE) { 1677 //the uri happens to be the same as that of the default namespace 1678 tmpPrefix = getAttrPrefix(uri); 1679 isSpecialCaseURI = true; 1680 } 1681 } 1682 1683 if (tmpPrefix == null) { 1684 StringBuilder genPrefix = new StringBuilder("zdef"); 1685 1686 for (int i = 0; i < 1; i++) { 1687 genPrefix.append(fPrefixGen.nextInt()); 1688 } 1689 1690 prefix = genPrefix.toString(); 1691 prefix = fSymbolTable.addSymbol(prefix); 1692 } else { 1693 prefix = fSymbolTable.addSymbol(tmpPrefix); 1694 } 1695 1696 if (tmpPrefix == null) { 1697 if (isSpecialCaseURI) { 1698 addAttrNamespace(prefix, uri); 1699 } else { 1700 QName qname = new QName(); 1701 qname.setValues(prefix, XMLConstants.XMLNS_ATTRIBUTE, null, uri); 1702 fNamespaceDecls.add(qname); 1703 fInternalNamespaceContext.declarePrefix(fSymbolTable.addSymbol( 1704 prefix), uri); 1705 } 1706 } 1707 } 1708 1709 attr.prefix = prefix; 1710 } 1711 1712 /** 1713 * return the prefix if the attribute has an uri the same as that of the default namespace 1714 */ getAttrPrefix(String uri)1715 private String getAttrPrefix(String uri) { 1716 if (fAttrNamespace != null) { 1717 return fAttrNamespace.get(uri); 1718 } 1719 return null; 1720 } addAttrNamespace(String prefix, String uri)1721 private void addAttrNamespace(String prefix, String uri) { 1722 if (fAttrNamespace == null) { 1723 fAttrNamespace = new HashMap<>(); 1724 } 1725 fAttrNamespace.put(prefix, uri); 1726 } 1727 /** 1728 * @param uri 1729 * @return 1730 */ isDefaultNamespace(String uri)1731 private boolean isDefaultNamespace(String uri) { 1732 String defaultNamespace = fInternalNamespaceContext.getURI(DEFAULT_PREFIX); 1733 return Objects.equals(uri, defaultNamespace); 1734 } 1735 1736 /** 1737 * @param prefix 1738 * @param uri 1739 * @return 1740 */ checkUserNamespaceContext(String prefix, String uri)1741 private boolean checkUserNamespaceContext(String prefix, String uri) { 1742 if (fNamespaceContext.userContext != null) { 1743 String tmpURI = fNamespaceContext.userContext.getNamespaceURI(prefix); 1744 1745 if ((tmpURI != null) && tmpURI.equals(uri)) { 1746 return true; 1747 } 1748 } 1749 1750 return false; 1751 } 1752 1753 /** 1754 * Correct's namespaces as per requirements of isReparisingNamespace property. 1755 */ repair()1756 protected void repair() { 1757 Attribute attr; 1758 Attribute attr2; 1759 ElementState currentElement = fElementStack.peek(); 1760 removeDuplicateDecls(); 1761 1762 for(int i=0 ; i< fAttributeCache.size();i++){ 1763 attr = fAttributeCache.get(i); 1764 if((attr.prefix != null && !attr.prefix.isEmpty()) || (attr.uri != null && !attr.uri.isEmpty())) { 1765 correctPrefix(currentElement,attr); 1766 } 1767 } 1768 1769 if (!isDeclared(currentElement)) { 1770 if ((currentElement.prefix != null) && 1771 (currentElement.uri != null)) { 1772 if ((!currentElement.prefix.isEmpty()) && (!currentElement.uri.isEmpty())) { 1773 fNamespaceDecls.add(currentElement); 1774 } 1775 } 1776 } 1777 1778 for(int i=0 ; i< fAttributeCache.size();i++){ 1779 attr = fAttributeCache.get(i); 1780 for(int j=i+1;j<fAttributeCache.size();j++){ 1781 attr2 = fAttributeCache.get(j); 1782 if(!"".equals(attr.prefix)&& !"".equals(attr2.prefix)){ 1783 correctPrefix(attr,attr2); 1784 } 1785 } 1786 } 1787 1788 repairNamespaceDecl(currentElement); 1789 1790 int i; 1791 1792 for (i = 0; i < fAttributeCache.size(); i++) { 1793 attr = fAttributeCache.get(i); 1794 /* If 'attr' is an attribute and it is in no namespace(which means that prefix="", uri=""), attr's 1795 namespace should not be redinded. See [http://www.w3.org/TR/REC-xml-names/#defaulting]. 1796 */ 1797 if (attr.prefix != null && attr.prefix.isEmpty() && attr.uri != null && attr.uri.isEmpty()){ 1798 repairNamespaceDecl(attr); 1799 } 1800 } 1801 1802 QName qname = null; 1803 1804 for (i = 0; i < fNamespaceDecls.size(); i++) { 1805 qname = fNamespaceDecls.get(i); 1806 1807 if (qname != null) { 1808 fInternalNamespaceContext.declarePrefix(qname.prefix, qname.uri); 1809 } 1810 } 1811 1812 for (i = 0; i < fAttributeCache.size(); i++) { 1813 attr = fAttributeCache.get(i); 1814 correctPrefix(attr, XMLStreamConstants.ATTRIBUTE); 1815 } 1816 } 1817 1818 /* 1819 *If element and/or attribute names in the same start or empty-element tag 1820 *are bound to different namespace URIs and are using the same prefix then 1821 *the element or the first occurring attribute retains the original prefix 1822 *and the following attributes have their prefixes replaced with a new prefix 1823 *that is bound to the namespace URIs of those attributes. 1824 */ correctPrefix(QName attr1, QName attr2)1825 void correctPrefix(QName attr1, QName attr2) { 1826 String tmpPrefix; 1827 QName decl; 1828 1829 checkForNull(attr1); 1830 checkForNull(attr2); 1831 1832 if(attr1.prefix.equals(attr2.prefix) && !(attr1.uri.equals(attr2.uri))){ 1833 1834 tmpPrefix = fNamespaceContext.getPrefix(attr2.uri); 1835 1836 if (tmpPrefix != null) { 1837 attr2.prefix = fSymbolTable.addSymbol(tmpPrefix); 1838 } else { 1839 for (int n=0; n<fNamespaceDecls.size(); n++) { 1840 decl = fNamespaceDecls.get(n); 1841 if(decl != null && (decl.uri.equals(attr2.uri))){ 1842 attr2.prefix = decl.prefix; 1843 1844 return; 1845 } 1846 } 1847 1848 //No namespace mapping found , so declare prefix. 1849 StringBuilder genPrefix = new StringBuilder("zdef"); 1850 1851 for (int k = 0; k < 1; k++) { 1852 genPrefix.append(fPrefixGen.nextInt()); 1853 } 1854 1855 tmpPrefix = genPrefix.toString(); 1856 tmpPrefix = fSymbolTable.addSymbol(tmpPrefix); 1857 attr2.prefix = tmpPrefix; 1858 1859 QName qname = new QName(); 1860 qname.setValues(tmpPrefix, XMLConstants.XMLNS_ATTRIBUTE, null, 1861 attr2.uri); 1862 fNamespaceDecls.add(qname); 1863 } 1864 } 1865 } 1866 checkForNull(QName attr)1867 void checkForNull(QName attr) { 1868 if (attr.prefix == null) attr.prefix = XMLConstants.DEFAULT_NS_PREFIX; 1869 if (attr.uri == null) attr.uri = XMLConstants.DEFAULT_NS_PREFIX; 1870 } 1871 removeDuplicateDecls()1872 void removeDuplicateDecls(){ 1873 QName decl1,decl2; 1874 for(int i =0; i<fNamespaceDecls.size(); i++) { 1875 decl1 = fNamespaceDecls.get(i); 1876 if(decl1!=null) { 1877 for(int j=i+1;j<fNamespaceDecls.size();j++){ 1878 decl2 = fNamespaceDecls.get(j); 1879 // QName.equals relies on identity equality, so we can't use it, 1880 // because prefixes aren't interned 1881 if(decl2!=null && decl1.prefix.equals(decl2.prefix) && decl1.uri.equals(decl2.uri)) 1882 fNamespaceDecls.remove(j); 1883 } 1884 } 1885 } 1886 } 1887 1888 /* 1889 *If an element or attribute name is bound to a prefix and there is a namespace 1890 *declaration that binds that prefix to a different URI then that namespace declaration 1891 *is either removed if the correct mapping is inherited from the parent context of that element, 1892 *or changed to the namespace URI of the element or attribute using that prefix. 1893 * 1894 */ repairNamespaceDecl(QName attr)1895 void repairNamespaceDecl(QName attr) { 1896 QName decl; 1897 String tmpURI; 1898 1899 //check for null prefix. 1900 for (int j = 0; j < fNamespaceDecls.size(); j++) { 1901 decl = fNamespaceDecls.get(j); 1902 1903 if (decl != null) { 1904 if ((attr.prefix != null) && 1905 (attr.prefix.equals(decl.prefix) && 1906 !(attr.uri.equals(decl.uri)))) { 1907 tmpURI = fNamespaceContext.getNamespaceURI(attr.prefix); 1908 1909 //see if you need to add to symbole table. 1910 if (tmpURI != null) { 1911 if (tmpURI.equals(attr.uri)) { 1912 fNamespaceDecls.set(j, null); 1913 } else { 1914 decl.uri = attr.uri; 1915 } 1916 } 1917 } 1918 } 1919 } 1920 } 1921 isDeclared(QName attr)1922 boolean isDeclared(QName attr) { 1923 QName decl; 1924 1925 for (int n = 0; n < fNamespaceDecls.size(); n++) { 1926 decl = fNamespaceDecls.get(n); 1927 1928 if ((attr.prefix != null) && 1929 ((attr.prefix.equals(decl.prefix)) && (decl.uri.equals(attr.uri)))) { 1930 return true; 1931 } 1932 } 1933 1934 if (attr.uri != null) { 1935 if (fNamespaceContext.getPrefix(attr.uri) != null) { 1936 return true; 1937 } 1938 } 1939 1940 return false; 1941 } 1942 1943 /* 1944 * Start of Internal classes. 1945 * 1946 */ 1947 protected class ElementStack { 1948 /** The stack data. */ 1949 protected ElementState[] fElements; 1950 1951 /** The size of the stack. */ 1952 protected short fDepth; 1953 1954 /** Default constructor. */ ElementStack()1955 public ElementStack() { 1956 fElements = new ElementState[10]; 1957 1958 for (int i = 0; i < fElements.length; i++) { 1959 fElements[i] = new ElementState(); 1960 } 1961 } 1962 1963 /** 1964 * Pushes an element on the stack. 1965 * <p> 1966 * <strong>Note:</strong> The QName values are copied into the 1967 * stack. In other words, the caller does <em>not</em> orphan 1968 * the element to the stack. Also, the QName object returned 1969 * is <em>not</em> orphaned to the caller. It should be 1970 * considered read-only. 1971 * 1972 * @param element The element to push onto the stack. 1973 * 1974 * @return Returns the actual QName object that stores the 1975 */ push(ElementState element)1976 public ElementState push(ElementState element) { 1977 if (fDepth == fElements.length) { 1978 ElementState[] array = new ElementState[fElements.length * 2]; 1979 System.arraycopy(fElements, 0, array, 0, fDepth); 1980 fElements = array; 1981 1982 for (int i = fDepth; i < fElements.length; i++) { 1983 fElements[i] = new ElementState(); 1984 } 1985 } 1986 1987 fElements[fDepth].setValues(element); 1988 1989 return fElements[fDepth++]; 1990 } 1991 1992 /** 1993 * 1994 * @param prefix 1995 * @param localpart 1996 * @param rawname 1997 * @param uri 1998 * @param isEmpty 1999 * @return 2000 */ push(String prefix, String localpart, String rawname, String uri, boolean isEmpty)2001 public ElementState push(String prefix, String localpart, 2002 String rawname, String uri, boolean isEmpty) { 2003 if (fDepth == fElements.length) { 2004 ElementState[] array = new ElementState[fElements.length * 2]; 2005 System.arraycopy(fElements, 0, array, 0, fDepth); 2006 fElements = array; 2007 2008 for (int i = fDepth; i < fElements.length; i++) { 2009 fElements[i] = new ElementState(); 2010 } 2011 } 2012 2013 fElements[fDepth].setValues(prefix, localpart, rawname, uri, isEmpty); 2014 2015 return fElements[fDepth++]; 2016 } 2017 2018 /** 2019 * Pops an element off of the stack by setting the values of 2020 * the specified QName. 2021 * <p> 2022 * <strong>Note:</strong> The object returned is <em>not</em> 2023 * orphaned to the caller. Therefore, the caller should consider 2024 * the object to be read-only. 2025 */ pop()2026 public ElementState pop() { 2027 return fElements[--fDepth]; 2028 } 2029 2030 /** Clears the stack without throwing away existing QName objects. */ clear()2031 public void clear() { 2032 fDepth = 0; 2033 } 2034 2035 /** 2036 * This function is as a result of optimization done for endElement -- 2037 * we dont need to set the value for every end element we encouter. 2038 * For Well formedness checks we can have the same QName object that was pushed. 2039 * the values will be set only if application need to know about the endElement 2040 */ peek()2041 public ElementState peek() { 2042 return fElements[fDepth - 1]; 2043 } 2044 2045 /** 2046 * 2047 * @return 2048 */ empty()2049 public boolean empty() { 2050 return (fDepth > 0) ? false : true; 2051 } 2052 } 2053 2054 /** 2055 * Maintains element state . localName for now. 2056 */ 2057 class ElementState extends QName { 2058 public boolean isEmpty = false; 2059 ElementState()2060 public ElementState() {} 2061 ElementState(String prefix, String localpart, String rawname, String uri)2062 public ElementState(String prefix, String localpart, String rawname, 2063 String uri) { 2064 super(prefix, localpart, rawname, uri); 2065 } 2066 setValues(String prefix, String localpart, String rawname, String uri, boolean isEmpty)2067 public void setValues(String prefix, String localpart, String rawname, 2068 String uri, boolean isEmpty) { 2069 super.setValues(prefix, localpart, rawname, uri); 2070 this.isEmpty = isEmpty; 2071 } 2072 } 2073 2074 /** 2075 * Attributes 2076 */ 2077 class Attribute extends QName { 2078 String value; 2079 Attribute(String value)2080 Attribute(String value) { 2081 super(); 2082 this.value = value; 2083 } 2084 } 2085 2086 /** 2087 * Implementation of NamespaceContext . 2088 * 2089 */ 2090 class NamespaceContextImpl implements NamespaceContext { 2091 //root namespace context set by user. 2092 NamespaceContext userContext = null; 2093 2094 //context built by the writer. 2095 NamespaceSupport internalContext = null; 2096 getNamespaceURI(String prefix)2097 public String getNamespaceURI(String prefix) { 2098 String uri = null; 2099 2100 if (prefix != null) { 2101 prefix = fSymbolTable.addSymbol(prefix); 2102 } 2103 2104 if (internalContext != null) { 2105 uri = internalContext.getURI(prefix); 2106 2107 if (uri != null) { 2108 return uri; 2109 } 2110 } 2111 2112 if (userContext != null) { 2113 uri = userContext.getNamespaceURI(prefix); 2114 2115 return uri; 2116 } 2117 2118 return null; 2119 } 2120 getPrefix(String uri)2121 public String getPrefix(String uri) { 2122 String prefix = null; 2123 2124 if (uri != null) { 2125 uri = fSymbolTable.addSymbol(uri); 2126 } 2127 2128 if (internalContext != null) { 2129 prefix = internalContext.getPrefix(uri); 2130 2131 if (prefix != null) { 2132 return prefix; 2133 } 2134 } 2135 2136 if (userContext != null) { 2137 return userContext.getPrefix(uri); 2138 } 2139 2140 return null; 2141 } 2142 2143 //Cleanup note: leaving these warnings to a xerces.internal.util cleanup getPrefixes(String uri)2144 public Iterator<String> getPrefixes(String uri) { 2145 List<String> prefixes = null; 2146 Iterator<String> itr = null; 2147 2148 if (uri != null) { 2149 uri = fSymbolTable.addSymbol(uri); 2150 } 2151 2152 if (userContext != null) { 2153 itr = userContext.getPrefixes(uri); 2154 } 2155 2156 if (internalContext != null) { 2157 prefixes = internalContext.getPrefixes(uri); 2158 } 2159 2160 if ((prefixes == null) && (itr != null)) { 2161 return itr; 2162 } else if ((prefixes != null) && (itr == null)) { 2163 return new ReadOnlyIterator<>(prefixes.iterator()); 2164 } else if ((prefixes != null) && (itr != null)) { 2165 String ob = null; 2166 2167 while (itr.hasNext()) { 2168 ob = itr.next(); 2169 2170 if (ob != null) { 2171 ob = fSymbolTable.addSymbol(ob); 2172 } 2173 2174 if (!prefixes.contains(ob)) { 2175 prefixes.add(ob); 2176 } 2177 } 2178 2179 return new ReadOnlyIterator<>(prefixes.iterator()); 2180 } 2181 2182 return fReadOnlyIterator; 2183 } 2184 } 2185 2186 // -- Map Interface -------------------------------------------------- 2187 2188 @Override size()2189 public int size() { 2190 return 1; 2191 } 2192 2193 @Override isEmpty()2194 public boolean isEmpty() { 2195 return false; 2196 } 2197 2198 @Override containsKey(Object key)2199 public boolean containsKey(Object key) { 2200 return key.equals(OUTPUTSTREAM_PROPERTY); 2201 } 2202 2203 /** 2204 * Returns the value associated to an implementation-specific 2205 * property. 2206 */ 2207 @Override get(Object key)2208 public Object get(Object key) { 2209 if (key.equals(OUTPUTSTREAM_PROPERTY)) { 2210 return fOutputStream; 2211 } 2212 return null; 2213 } 2214 2215 @Override entrySet()2216 public Set<Entry<Object,Object>> entrySet() { 2217 throw new UnsupportedOperationException(); 2218 } 2219 2220 /** 2221 * Overrides the method defined in AbstractMap which is 2222 * not completely implemented. Calling toString() in 2223 * AbstractMap would cause an unsupported exection to 2224 * be thrown. 2225 */ 2226 @Override toString()2227 public String toString() { 2228 return getClass().getName() + "@" + Integer.toHexString(hashCode()); 2229 } 2230 2231 /** 2232 * Overrides the method defined in AbstractMap 2233 * This is required by the toString() method 2234 */ 2235 @Override hashCode()2236 public int hashCode() { 2237 return fElementStack.hashCode(); 2238 } 2239 /** 2240 * Overrides the method defined in AbstractMap 2241 * This is required to satisfy the contract for hashCode. 2242 */ 2243 @Override equals(Object obj)2244 public boolean equals(Object obj) { 2245 return (this == obj); 2246 } 2247 } 2248