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