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