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