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 XMLStreamWriter { 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 * @throws XMLStreamException 1116 */ 1117 public void writeStartDocument() throws XMLStreamException { 1118 try { 1119 fWriter.write(DEFAULT_XMLDECL); 1120 } catch (IOException ex) { 1121 throw new XMLStreamException(ex); 1122 } 1123 } 1124 1125 /** 1126 * @param version 1127 * @throws XMLStreamException 1128 */ 1129 public void writeStartDocument(String version) throws XMLStreamException { 1130 try { 1131 if ((version == null) || version.equals("")) { 1132 writeStartDocument(); 1133 1134 return; 1135 } 1136 1137 fWriter.write("<?xml version=\""); 1138 fWriter.write(version); 1139 fWriter.write("\""); 1140 1141 //fWriter.write(DEFAULT_ENCODING); 1142 fWriter.write("?>"); 1143 } catch (IOException ex) { 1144 throw new XMLStreamException(ex); 1145 } 1146 } 1147 1148 /** 1149 * @param encoding 1150 * @param version 1151 * @throws XMLStreamException 1152 */ 1153 public void writeStartDocument(String encoding, String version) 1154 throws XMLStreamException { 1155 //Revisit : What about standalone ? 1156 try { 1157 if ((encoding == null) && (version == null)) { 1158 writeStartDocument(); 1159 1160 return; 1161 } 1162 1163 if (encoding == null) { 1164 writeStartDocument(version); 1165 1166 return; 1167 } 1168 1169 String streamEncoding = null; 1170 if (fWriter instanceof OutputStreamWriter) { 1171 streamEncoding = ((OutputStreamWriter) fWriter).getEncoding(); 1172 } 1173 else if (fWriter instanceof UTF8OutputStreamWriter) { 1174 streamEncoding = ((UTF8OutputStreamWriter) fWriter).getEncoding(); 1175 } 1176 else if (fWriter instanceof XMLWriter) { 1177 streamEncoding = ((OutputStreamWriter) ((XMLWriter)fWriter).getWriter()).getEncoding(); 1178 } 1179 1180 if (streamEncoding != null && !streamEncoding.equalsIgnoreCase(encoding)) { 1181 // If the equality check failed, check for charset encoding aliases 1182 boolean foundAlias = false; 1183 Set aliases = Charset.forName(encoding).aliases(); 1184 for (Iterator it = aliases.iterator(); !foundAlias && it.hasNext(); ) { 1185 if (streamEncoding.equalsIgnoreCase((String) it.next())) { 1186 foundAlias = true; 1187 } 1188 } 1189 // If no alias matches the encoding name, then report error 1190 if (!foundAlias) { 1191 throw new XMLStreamException("Underlying stream encoding '" 1192 + streamEncoding 1193 + "' and input paramter for writeStartDocument() method '" 1194 + encoding + "' do not match."); 1195 } 1196 } 1197 1198 1199 fWriter.write("<?xml version=\""); 1200 1201 if ((version == null) || version.equals("")) { 1202 fWriter.write(DEFAULT_XML_VERSION); 1203 } else { 1204 fWriter.write(version); 1205 } 1206 1207 if (!encoding.equals("")) { 1208 fWriter.write("\" encoding=\""); 1209 fWriter.write(encoding); 1210 } 1211 1212 fWriter.write("\"?>"); 1213 } catch (IOException ex) { 1214 throw new XMLStreamException(ex); 1215 } 1216 } 1217 1218 /** 1219 * @param localName 1220 * @throws XMLStreamException 1221 */ 1222 public void writeStartElement(String localName) throws XMLStreamException { 1223 try { 1224 if (localName == null) { 1225 throw new XMLStreamException("Local Name cannot be null"); 1226 } 1227 1228 if (fStartTagOpened) { 1229 closeStartTag(); 1230 } 1231 1232 openStartTag(); 1233 fElementStack.push(null, localName, null, null, false); 1234 fInternalNamespaceContext.pushContext(); 1235 1236 if (fIsRepairingNamespace) { 1237 return; 1238 } 1239 1240 fWriter.write(localName); 1241 } catch (IOException ex) { 1242 throw new XMLStreamException(ex); 1243 } 1244 } 1245 1246 /** 1247 * @param namespaceURI 1248 * @param localName 1249 * @throws XMLStreamException 1250 */ 1251 public void writeStartElement(String namespaceURI, String localName) 1252 throws XMLStreamException { 1253 if (localName == null) { 1254 throw new XMLStreamException("Local Name cannot be null"); 1255 } 1256 1257 if (namespaceURI == null) { 1258 throw new XMLStreamException("NamespaceURI cannot be null"); 1259 } 1260 1261 namespaceURI = fSymbolTable.addSymbol(namespaceURI); 1262 1263 String prefix = null; 1264 1265 if (!fIsRepairingNamespace) { 1266 prefix = fNamespaceContext.getPrefix(namespaceURI); 1267 1268 if (prefix != null) { 1269 prefix = fSymbolTable.addSymbol(prefix); 1270 } 1271 } 1272 1273 writeStartElement(prefix, localName, namespaceURI); 1274 } 1275 1276 /** 1277 * @param prefix 1278 * @param localName 1279 * @param namespaceURI 1280 * @throws XMLStreamException 1281 */ 1282 public void writeStartElement(String prefix, String localName, 1283 String namespaceURI) throws XMLStreamException { 1284 try { 1285 if (localName == null) { 1286 throw new XMLStreamException("Local Name cannot be null"); 1287 } 1288 1289 if (namespaceURI == null) { 1290 throw new XMLStreamException("NamespaceURI cannot be null"); 1291 } 1292 1293 if (!fIsRepairingNamespace) { 1294 if (prefix == null) { 1295 throw new XMLStreamException("Prefix cannot be null"); 1296 } 1297 } 1298 1299 if (fStartTagOpened) { 1300 closeStartTag(); 1301 } 1302 1303 openStartTag(); 1304 namespaceURI = fSymbolTable.addSymbol(namespaceURI); 1305 1306 if (prefix != null) { 1307 prefix = fSymbolTable.addSymbol(prefix); 1308 } 1309 1310 fElementStack.push(prefix, localName, null, namespaceURI, false); 1311 fInternalNamespaceContext.pushContext(); 1312 1313 String tmpPrefix = fNamespaceContext.getPrefix(namespaceURI); 1314 1315 1316 if ((prefix != null) && 1317 ((tmpPrefix == null) || !prefix.equals(tmpPrefix))) { 1318 fInternalNamespaceContext.declarePrefix(prefix, namespaceURI); 1319 1320 } 1321 1322 if (fIsRepairingNamespace) { 1323 if ((prefix == null) || 1324 ((tmpPrefix != null) && prefix.equals(tmpPrefix))) { 1325 return; 1326 } 1327 1328 QName qname = new QName(); 1329 qname.setValues(prefix, XMLConstants.XMLNS_ATTRIBUTE, null, 1330 namespaceURI); 1331 fNamespaceDecls.add(qname); 1332 1333 return; 1334 } 1335 1336 if ((prefix != null) && (prefix != XMLConstants.DEFAULT_NS_PREFIX)) { 1337 fWriter.write(prefix); 1338 fWriter.write(":"); 1339 } 1340 1341 fWriter.write(localName); 1342 1343 } catch (IOException ex) { 1344 throw new XMLStreamException(ex); 1345 } 1346 } 1347 1348 /** 1349 * Writes character reference in hex format. 1350 */ 1351 private void writeCharRef(int codePoint) throws IOException { 1352 fWriter.write( "&#x" ); 1353 fWriter.write( Integer.toHexString(codePoint) ); 1354 fWriter.write( ';' ); 1355 } 1356 1357 /** 1358 * Writes XML content to underlying writer. Escapes characters unless 1359 * escaping character feature is turned off. 1360 */ 1361 private void writeXMLContent(char[] content, int start, int length, 1362 boolean escapeChars) throws IOException { 1363 if (!escapeChars) { 1364 fWriter.write(content, start, length); 1365 1366 return; 1367 } 1368 1369 // Index of the next char to be written 1370 int startWritePos = start; 1371 1372 final int end = start + length; 1373 1374 for (int index = start; index < end; index++) { 1375 char ch = content[index]; 1376 1377 if (fEncoder != null && !fEncoder.canEncode(ch)){ 1378 fWriter.write(content, startWritePos, index - startWritePos ); 1379 1380 // Check if current and next characters forms a surrogate pair 1381 // and escape it to avoid generation of invalid xml content 1382 if ( index != end - 1 && Character.isSurrogatePair(ch, content[index+1])) { 1383 writeCharRef(Character.toCodePoint(ch, content[index+1])); 1384 index++; 1385 } else { 1386 writeCharRef(ch); 1387 } 1388 startWritePos = index + 1; 1389 continue; 1390 } 1391 1392 switch (ch) { 1393 case '<': 1394 fWriter.write(content, startWritePos, index - startWritePos); 1395 fWriter.write("<"); 1396 startWritePos = index + 1; 1397 1398 break; 1399 1400 case '&': 1401 fWriter.write(content, startWritePos, index - startWritePos); 1402 fWriter.write("&"); 1403 startWritePos = index + 1; 1404 1405 break; 1406 1407 case '>': 1408 fWriter.write(content, startWritePos, index - startWritePos); 1409 fWriter.write(">"); 1410 startWritePos = index + 1; 1411 1412 break; 1413 } 1414 } 1415 1416 // Write any pending data 1417 fWriter.write(content, startWritePos, end - startWritePos); 1418 } 1419 1420 private void writeXMLContent(String content) throws IOException { 1421 if ((content != null) && (content.length() > 0)) { 1422 writeXMLContent(content, 1423 fEscapeCharacters, // boolean = escapeChars 1424 false); // false = escapeDoubleQuotes 1425 } 1426 } 1427 1428 /** 1429 * Writes XML content to underlying writer. Escapes characters unless 1430 * escaping character feature is turned off. 1431 */ 1432 private void writeXMLContent( 1433 String content, 1434 boolean escapeChars, 1435 boolean escapeDoubleQuotes) 1436 throws IOException { 1437 1438 if (!escapeChars) { 1439 fWriter.write(content); 1440 1441 return; 1442 } 1443 1444 // Index of the next char to be written 1445 int startWritePos = 0; 1446 1447 final int end = content.length(); 1448 1449 for (int index = 0; index < end; index++) { 1450 char ch = content.charAt(index); 1451 1452 if (fEncoder != null && !fEncoder.canEncode(ch)){ 1453 fWriter.write(content, startWritePos, index - startWritePos ); 1454 1455 // Check if current and next characters forms a surrogate pair 1456 // and escape it to avoid generation of invalid xml content 1457 if ( index != end - 1 && Character.isSurrogatePair(ch, content.charAt(index+1))) { 1458 writeCharRef(Character.toCodePoint(ch, content.charAt(index+1))); 1459 index++; 1460 } else { 1461 writeCharRef(ch); 1462 } 1463 1464 startWritePos = index + 1; 1465 continue; 1466 } 1467 1468 switch (ch) { 1469 case '<': 1470 fWriter.write(content, startWritePos, index - startWritePos); 1471 fWriter.write("<"); 1472 startWritePos = index + 1; 1473 1474 break; 1475 1476 case '&': 1477 fWriter.write(content, startWritePos, index - startWritePos); 1478 fWriter.write("&"); 1479 startWritePos = index + 1; 1480 1481 break; 1482 1483 case '>': 1484 fWriter.write(content, startWritePos, index - startWritePos); 1485 fWriter.write(">"); 1486 startWritePos = index + 1; 1487 1488 break; 1489 1490 case '"': 1491 fWriter.write(content, startWritePos, index - startWritePos); 1492 if (escapeDoubleQuotes) { 1493 fWriter.write("""); 1494 } else { 1495 fWriter.write('"'); 1496 } 1497 startWritePos = index + 1; 1498 1499 break; 1500 } 1501 } 1502 1503 // Write any pending data 1504 fWriter.write(content, startWritePos, end - startWritePos); 1505 } 1506 1507 /** 1508 * marks close of start tag and writes the same into the writer. 1509 */ 1510 private void closeStartTag() throws XMLStreamException { 1511 try { 1512 ElementState currentElement = fElementStack.peek(); 1513 1514 if (fIsRepairingNamespace) { 1515 repair(); 1516 correctPrefix(currentElement, XMLStreamConstants.START_ELEMENT); 1517 1518 if ((currentElement.prefix != null) && 1519 (currentElement.prefix != XMLConstants.DEFAULT_NS_PREFIX)) { 1520 fWriter.write(currentElement.prefix); 1521 fWriter.write(":"); 1522 } 1523 1524 fWriter.write(currentElement.localpart); 1525 1526 int len = fNamespaceDecls.size(); 1527 QName qname = null; 1528 1529 for (int i = 0; i < len; i++) { 1530 qname = (QName) fNamespaceDecls.get(i); 1531 1532 if (qname != null) { 1533 if (fInternalNamespaceContext.declarePrefix(qname.prefix, 1534 qname.uri)) { 1535 writenamespace(qname.prefix, qname.uri); 1536 } 1537 } 1538 } 1539 1540 fNamespaceDecls.clear(); 1541 1542 Attribute attr = null; 1543 1544 for (int j = 0; j < fAttributeCache.size(); j++) { 1545 attr = (Attribute) fAttributeCache.get(j); 1546 1547 if ((attr.prefix != null) && (attr.uri != null)) { 1548 if (!attr.prefix.equals("") && !attr.uri.equals("") ) { 1549 String tmp = fInternalNamespaceContext.getPrefix(attr.uri); 1550 1551 if ((tmp == null) || (tmp != attr.prefix)) { 1552 tmp = getAttrPrefix(attr.uri); 1553 if (tmp == null) { 1554 if (fInternalNamespaceContext.declarePrefix(attr.prefix, 1555 attr.uri)) { 1556 writenamespace(attr.prefix, attr.uri); 1557 } 1558 } else { 1559 writenamespace(attr.prefix, attr.uri); 1560 } 1561 } 1562 } 1563 } 1564 1565 writeAttributeWithPrefix(attr.prefix, attr.localpart, 1566 attr.value); 1567 } 1568 fAttrNamespace = null; 1569 fAttributeCache.clear(); 1570 } 1571 1572 if (currentElement.isEmpty) { 1573 fElementStack.pop(); 1574 fInternalNamespaceContext.popContext(); 1575 fWriter.write(CLOSE_EMPTY_ELEMENT); 1576 } else { 1577 fWriter.write(CLOSE_START_TAG); 1578 } 1579 1580 fStartTagOpened = false; 1581 } catch (IOException ex) { 1582 fStartTagOpened = false; 1583 throw new XMLStreamException(ex); 1584 } 1585 } 1586 1587 /** 1588 * marks open of start tag and writes the same into the writer. 1589 */ 1590 private void openStartTag() throws IOException { 1591 fStartTagOpened = true; 1592 fWriter.write(OPEN_START_TAG); 1593 } 1594 1595 /** 1596 * 1597 * @param uri 1598 * @return 1599 */ 1600 private void correctPrefix(QName attr, int type) { 1601 String tmpPrefix = null; 1602 String prefix; 1603 String uri; 1604 prefix = attr.prefix; 1605 uri = attr.uri; 1606 boolean isSpecialCaseURI = false; 1607 1608 if (prefix == null || prefix.equals("")) { 1609 if (uri == null) { 1610 return; 1611 } 1612 1613 if (prefix == XMLConstants.DEFAULT_NS_PREFIX && uri == XMLConstants.DEFAULT_NS_PREFIX) 1614 return; 1615 1616 uri = fSymbolTable.addSymbol(uri); 1617 1618 QName decl = null; 1619 1620 for (int i = 0; i < fNamespaceDecls.size(); i++) { 1621 decl = (QName) fNamespaceDecls.get(i); 1622 1623 if ((decl != null) && (decl.uri == attr.uri)) { 1624 attr.prefix = decl.prefix; 1625 1626 return; 1627 } 1628 } 1629 1630 tmpPrefix = fNamespaceContext.getPrefix(uri); 1631 1632 if (tmpPrefix == XMLConstants.DEFAULT_NS_PREFIX) { 1633 if (type == XMLStreamConstants.START_ELEMENT) { 1634 return; 1635 } 1636 else if (type == XMLStreamConstants.ATTRIBUTE) { 1637 //the uri happens to be the same as that of the default namespace 1638 tmpPrefix = getAttrPrefix(uri); 1639 isSpecialCaseURI = true; 1640 } 1641 } 1642 1643 if (tmpPrefix == null) { 1644 StringBuffer genPrefix = new StringBuffer("zdef"); 1645 1646 for (int i = 0; i < 1; i++) { 1647 genPrefix.append(fPrefixGen.nextInt()); 1648 } 1649 1650 prefix = genPrefix.toString(); 1651 prefix = fSymbolTable.addSymbol(prefix); 1652 } else { 1653 prefix = fSymbolTable.addSymbol(tmpPrefix); 1654 } 1655 1656 if (tmpPrefix == null) { 1657 if (isSpecialCaseURI) { 1658 addAttrNamespace(prefix, uri); 1659 } else { 1660 QName qname = new QName(); 1661 qname.setValues(prefix, XMLConstants.XMLNS_ATTRIBUTE, null, uri); 1662 fNamespaceDecls.add(qname); 1663 fInternalNamespaceContext.declarePrefix(fSymbolTable.addSymbol( 1664 prefix), uri); 1665 } 1666 } 1667 } 1668 1669 attr.prefix = prefix; 1670 } 1671 1672 /** 1673 * return the prefix if the attribute has an uri the same as that of the default namespace 1674 */ 1675 private String getAttrPrefix(String uri) { 1676 if (fAttrNamespace != null) { 1677 return (String)fAttrNamespace.get(uri); 1678 } 1679 return null; 1680 } 1681 private void addAttrNamespace(String prefix, String uri) { 1682 if (fAttrNamespace == null) { 1683 fAttrNamespace = new HashMap(); 1684 } 1685 fAttrNamespace.put(prefix, uri); 1686 } 1687 /** 1688 * @param uri 1689 * @return 1690 */ 1691 private boolean isDefaultNamespace(String uri) { 1692 String defaultNamespace = fInternalNamespaceContext.getURI(DEFAULT_PREFIX); 1693 1694 if (uri == defaultNamespace) { 1695 return true; 1696 } 1697 1698 return false; 1699 } 1700 1701 /** 1702 * @param prefix 1703 * @param uri 1704 * @return 1705 */ 1706 private boolean checkUserNamespaceContext(String prefix, String uri) { 1707 if (fNamespaceContext.userContext != null) { 1708 String tmpURI = fNamespaceContext.userContext.getNamespaceURI(prefix); 1709 1710 if ((tmpURI != null) && tmpURI.equals(uri)) { 1711 return true; 1712 } 1713 } 1714 1715 return false; 1716 } 1717 1718 /** 1719 * Correct's namespaces as per requirements of isReparisingNamespace property. 1720 */ 1721 protected void repair() { 1722 Attribute attr = null; 1723 Attribute attr2 = null; 1724 ElementState currentElement = fElementStack.peek(); 1725 removeDuplicateDecls(); 1726 1727 for(int i=0 ; i< fAttributeCache.size();i++){ 1728 attr = (Attribute)fAttributeCache.get(i); 1729 if((attr.prefix != null && !attr.prefix.equals("")) || (attr.uri != null && !attr.uri.equals(""))) { 1730 correctPrefix(currentElement,attr); 1731 } 1732 } 1733 1734 if (!isDeclared(currentElement)) { 1735 if ((currentElement.prefix != null) && 1736 (currentElement.uri != null)) { 1737 if ((!currentElement.prefix.equals("")) && (!currentElement.uri.equals(""))) { 1738 fNamespaceDecls.add(currentElement); 1739 } 1740 } 1741 } 1742 1743 for(int i=0 ; i< fAttributeCache.size();i++){ 1744 attr = (Attribute)fAttributeCache.get(i); 1745 for(int j=i+1;j<fAttributeCache.size();j++){ 1746 attr2 = (Attribute)fAttributeCache.get(j); 1747 if(!"".equals(attr.prefix)&& !"".equals(attr2.prefix)){ 1748 correctPrefix(attr,attr2); 1749 } 1750 } 1751 } 1752 1753 repairNamespaceDecl(currentElement); 1754 1755 int i = 0; 1756 1757 for (i = 0; i < fAttributeCache.size(); i++) { 1758 attr = (Attribute) fAttributeCache.get(i); 1759 /* If 'attr' is an attribute and it is in no namespace(which means that prefix="", uri=""), attr's 1760 namespace should not be redinded. See [http://www.w3.org/TR/REC-xml-names/#defaulting]. 1761 */ 1762 if (attr.prefix != null && attr.prefix.equals("") && attr.uri != null && attr.uri.equals("")){ 1763 repairNamespaceDecl(attr); 1764 } 1765 } 1766 1767 QName qname = null; 1768 1769 for (i = 0; i < fNamespaceDecls.size(); i++) { 1770 qname = (QName) fNamespaceDecls.get(i); 1771 1772 if (qname != null) { 1773 fInternalNamespaceContext.declarePrefix(qname.prefix, qname.uri); 1774 } 1775 } 1776 1777 for (i = 0; i < fAttributeCache.size(); i++) { 1778 attr = (Attribute) fAttributeCache.get(i); 1779 correctPrefix(attr, XMLStreamConstants.ATTRIBUTE); 1780 } 1781 } 1782 1783 /* 1784 *If element and/or attribute names in the same start or empty-element tag 1785 *are bound to different namespace URIs and are using the same prefix then 1786 *the element or the first occurring attribute retains the original prefix 1787 *and the following attributes have their prefixes replaced with a new prefix 1788 *that is bound to the namespace URIs of those attributes. 1789 */ 1790 void correctPrefix(QName attr1, QName attr2) { 1791 String tmpPrefix = null; 1792 QName decl = null; 1793 boolean done = false; 1794 1795 checkForNull(attr1); 1796 checkForNull(attr2); 1797 1798 if(attr1.prefix.equals(attr2.prefix) && !(attr1.uri.equals(attr2.uri))){ 1799 1800 tmpPrefix = fNamespaceContext.getPrefix(attr2.uri); 1801 1802 if (tmpPrefix != null) { 1803 attr2.prefix = fSymbolTable.addSymbol(tmpPrefix); 1804 } else { 1805 decl = null; 1806 for(int n=0;n<fNamespaceDecls.size();n++){ 1807 decl = (QName)fNamespaceDecls.get(n); 1808 if(decl != null && (decl.uri == attr2.uri)){ 1809 attr2.prefix = decl.prefix; 1810 1811 return; 1812 } 1813 } 1814 1815 //No namespace mapping found , so declare prefix. 1816 StringBuffer genPrefix = new StringBuffer("zdef"); 1817 1818 for (int k = 0; k < 1; k++) { 1819 genPrefix.append(fPrefixGen.nextInt()); 1820 } 1821 1822 tmpPrefix = genPrefix.toString(); 1823 tmpPrefix = fSymbolTable.addSymbol(tmpPrefix); 1824 attr2.prefix = tmpPrefix; 1825 1826 QName qname = new QName(); 1827 qname.setValues(tmpPrefix, XMLConstants.XMLNS_ATTRIBUTE, null, 1828 attr2.uri); 1829 fNamespaceDecls.add(qname); 1830 } 1831 } 1832 } 1833 1834 void checkForNull(QName attr) { 1835 if (attr.prefix == null) attr.prefix = XMLConstants.DEFAULT_NS_PREFIX; 1836 if (attr.uri == null) attr.uri = XMLConstants.DEFAULT_NS_PREFIX; 1837 } 1838 1839 void removeDuplicateDecls(){ 1840 QName decl1,decl2; 1841 for(int i =0;i<fNamespaceDecls.size();i++){ 1842 decl1 = (QName)fNamespaceDecls.get(i); 1843 if(decl1!=null) { 1844 for(int j=i+1;j<fNamespaceDecls.size();j++){ 1845 decl2 = (QName)fNamespaceDecls.get(j); 1846 // QName.equals relies on identity equality, so we can't use it, 1847 // because prefixes aren't interned 1848 if(decl2!=null && decl1.prefix.equals(decl2.prefix) && decl1.uri.equals(decl2.uri)) 1849 fNamespaceDecls.remove(j); 1850 } 1851 } 1852 } 1853 } 1854 1855 /* 1856 *If an element or attribute name is bound to a prefix and there is a namespace 1857 *declaration that binds that prefix to a different URI then that namespace declaration 1858 *is either removed if the correct mapping is inherited from the parent context of that element, 1859 *or changed to the namespace URI of the element or attribute using that prefix. 1860 * 1861 */ 1862 void repairNamespaceDecl(QName attr) { 1863 QName decl = null; 1864 String tmpURI; 1865 1866 //check for null prefix. 1867 for (int j = 0; j < fNamespaceDecls.size(); j++) { 1868 decl = (QName) fNamespaceDecls.get(j); 1869 1870 if (decl != null) { 1871 if ((attr.prefix != null) && 1872 (attr.prefix.equals(decl.prefix) && 1873 !(attr.uri.equals(decl.uri)))) { 1874 tmpURI = fNamespaceContext.getNamespaceURI(attr.prefix); 1875 1876 //see if you need to add to symbole table. 1877 if (tmpURI != null) { 1878 if (tmpURI.equals(attr.uri)) { 1879 fNamespaceDecls.set(j, null); 1880 } else { 1881 decl.uri = attr.uri; 1882 } 1883 } 1884 } 1885 } 1886 } 1887 } 1888 1889 boolean isDeclared(QName attr) { 1890 QName decl = null; 1891 1892 for (int n = 0; n < fNamespaceDecls.size(); n++) { 1893 decl = (QName) fNamespaceDecls.get(n); 1894 1895 if ((attr.prefix != null) && 1896 ((attr.prefix == decl.prefix) && (decl.uri == attr.uri))) { 1897 return true; 1898 } 1899 } 1900 1901 if (attr.uri != null) { 1902 if (fNamespaceContext.getPrefix(attr.uri) != null) { 1903 return true; 1904 } 1905 } 1906 1907 return false; 1908 } 1909 1910 /* 1911 * Start of Internal classes. 1912 * 1913 */ 1914 protected class ElementStack { 1915 /** The stack data. */ 1916 protected ElementState[] fElements; 1917 1918 /** The size of the stack. */ 1919 protected short fDepth; 1920 1921 /** Default constructor. */ 1922 public ElementStack() { 1923 fElements = new ElementState[10]; 1924 1925 for (int i = 0; i < fElements.length; i++) { 1926 fElements[i] = new ElementState(); 1927 } 1928 } 1929 1930 /** 1931 * Pushes an element on the stack. 1932 * <p> 1933 * <strong>Note:</strong> The QName values are copied into the 1934 * stack. In other words, the caller does <em>not</em> orphan 1935 * the element to the stack. Also, the QName object returned 1936 * is <em>not</em> orphaned to the caller. It should be 1937 * considered read-only. 1938 * 1939 * @param element The element to push onto the stack. 1940 * 1941 * @return Returns the actual QName object that stores the 1942 */ 1943 public ElementState push(ElementState element) { 1944 if (fDepth == fElements.length) { 1945 ElementState[] array = new ElementState[fElements.length * 2]; 1946 System.arraycopy(fElements, 0, array, 0, fDepth); 1947 fElements = array; 1948 1949 for (int i = fDepth; i < fElements.length; i++) { 1950 fElements[i] = new ElementState(); 1951 } 1952 } 1953 1954 fElements[fDepth].setValues(element); 1955 1956 return fElements[fDepth++]; 1957 } 1958 1959 /** 1960 * 1961 * @param prefix 1962 * @param localpart 1963 * @param rawname 1964 * @param uri 1965 * @param isEmpty 1966 * @return 1967 */ 1968 public ElementState push(String prefix, String localpart, 1969 String rawname, String uri, boolean isEmpty) { 1970 if (fDepth == fElements.length) { 1971 ElementState[] array = new ElementState[fElements.length * 2]; 1972 System.arraycopy(fElements, 0, array, 0, fDepth); 1973 fElements = array; 1974 1975 for (int i = fDepth; i < fElements.length; i++) { 1976 fElements[i] = new ElementState(); 1977 } 1978 } 1979 1980 fElements[fDepth].setValues(prefix, localpart, rawname, uri, isEmpty); 1981 1982 return fElements[fDepth++]; 1983 } 1984 1985 /** 1986 * Pops an element off of the stack by setting the values of 1987 * the specified QName. 1988 * <p> 1989 * <strong>Note:</strong> The object returned is <em>not</em> 1990 * orphaned to the caller. Therefore, the caller should consider 1991 * the object to be read-only. 1992 */ 1993 public ElementState pop() { 1994 return fElements[--fDepth]; 1995 } 1996 1997 /** Clears the stack without throwing away existing QName objects. */ 1998 public void clear() { 1999 fDepth = 0; 2000 } 2001 2002 /** 2003 * This function is as a result of optimization done for endElement -- 2004 * we dont need to set the value for every end element we encouter. 2005 * For Well formedness checks we can have the same QName object that was pushed. 2006 * the values will be set only if application need to know about the endElement 2007 * -- neeraj.bajaj@sun.com 2008 */ 2009 public ElementState peek() { 2010 return fElements[fDepth - 1]; 2011 } 2012 2013 /** 2014 * 2015 * @return 2016 */ 2017 public boolean empty() { 2018 return (fDepth > 0) ? false : true; 2019 } 2020 } 2021 2022 /** 2023 * Maintains element state . localName for now. 2024 */ 2025 class ElementState extends QName { 2026 public boolean isEmpty = false; 2027 2028 public ElementState() {} 2029 2030 public ElementState(String prefix, String localpart, String rawname, 2031 String uri) { 2032 super(prefix, localpart, rawname, uri); 2033 } 2034 2035 public void setValues(String prefix, String localpart, String rawname, 2036 String uri, boolean isEmpty) { 2037 super.setValues(prefix, localpart, rawname, uri); 2038 this.isEmpty = isEmpty; 2039 } 2040 } 2041 2042 /** 2043 * Attributes 2044 */ 2045 class Attribute extends QName { 2046 String value; 2047 2048 Attribute(String value) { 2049 super(); 2050 this.value = value; 2051 } 2052 } 2053 2054 /** 2055 * Implementation of NamespaceContext . 2056 * 2057 */ 2058 class NamespaceContextImpl implements NamespaceContext { 2059 //root namespace context set by user. 2060 NamespaceContext userContext = null; 2061 2062 //context built by the writer. 2063 NamespaceSupport internalContext = null; 2064 2065 public String getNamespaceURI(String prefix) { 2066 String uri = null; 2067 2068 if (prefix != null) { 2069 prefix = fSymbolTable.addSymbol(prefix); 2070 } 2071 2072 if (internalContext != null) { 2073 uri = internalContext.getURI(prefix); 2074 2075 if (uri != null) { 2076 return uri; 2077 } 2078 } 2079 2080 if (userContext != null) { 2081 uri = userContext.getNamespaceURI(prefix); 2082 2083 return uri; 2084 } 2085 2086 return null; 2087 } 2088 2089 public String getPrefix(String uri) { 2090 String prefix = null; 2091 2092 if (uri != null) { 2093 uri = fSymbolTable.addSymbol(uri); 2094 } 2095 2096 if (internalContext != null) { 2097 prefix = internalContext.getPrefix(uri); 2098 2099 if (prefix != null) { 2100 return prefix; 2101 } 2102 } 2103 2104 if (userContext != null) { 2105 return userContext.getPrefix(uri); 2106 } 2107 2108 return null; 2109 } 2110 2111 public java.util.Iterator getPrefixes(String uri) { 2112 Vector prefixes = null; 2113 Iterator itr = null; 2114 2115 if (uri != null) { 2116 uri = fSymbolTable.addSymbol(uri); 2117 } 2118 2119 if (userContext != null) { 2120 itr = userContext.getPrefixes(uri); 2121 } 2122 2123 if (internalContext != null) { 2124 prefixes = internalContext.getPrefixes(uri); 2125 } 2126 2127 if ((prefixes == null) && (itr != null)) { 2128 return itr; 2129 } else if ((prefixes != null) && (itr == null)) { 2130 return new ReadOnlyIterator(prefixes.iterator()); 2131 } else if ((prefixes != null) && (itr != null)) { 2132 String ob = null; 2133 2134 while (itr.hasNext()) { 2135 ob = (String) itr.next(); 2136 2137 if (ob != null) { 2138 ob = fSymbolTable.addSymbol(ob); 2139 } 2140 2141 if (!prefixes.contains(ob)) { 2142 prefixes.add(ob); 2143 } 2144 } 2145 2146 return new ReadOnlyIterator(prefixes.iterator()); 2147 } 2148 2149 return fReadOnlyIterator; 2150 } 2151 } 2152 2153 // -- Map Interface -------------------------------------------------- 2154 2155 public int size() { 2156 return 1; 2157 } 2158 2159 public boolean isEmpty() { 2160 return false; 2161 } 2162 2163 public boolean containsKey(Object key) { 2164 return key.equals(OUTPUTSTREAM_PROPERTY); 2165 } 2166 2167 /** 2168 * Returns the value associated to an implementation-specific 2169 * property. 2170 */ 2171 public Object get(Object key) { 2172 if (key.equals(OUTPUTSTREAM_PROPERTY)) { 2173 return fOutputStream; 2174 } 2175 return null; 2176 } 2177 2178 public java.util.Set entrySet() { 2179 throw new UnsupportedOperationException(); 2180 } 2181 2182 /** 2183 * Overrides the method defined in AbstractMap which is 2184 * not completely implemented. Calling toString() in 2185 * AbstractMap would cause an unsupported exection to 2186 * be thrown. 2187 */ 2188 public String toString() { 2189 return getClass().getName() + "@" + Integer.toHexString(hashCode()); 2190 } 2191 2192 /** 2193 * Overrides the method defined in AbstractMap 2194 * This is required by the toString() method 2195 */ 2196 public int hashCode() { 2197 return fElementStack.hashCode(); 2198 } 2199 /** 2200 * Overrides the method defined in AbstractMap 2201 * This is required to satisfy the contract for hashCode. 2202 */ 2203 public boolean equals(Object obj) { 2204 return (this == obj); 2205 } 2206 }