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