1 // XMLWriter.java - serialize an XML document. 2 // Written by David Megginson, david@megginson.com 3 // and placed by him into the public domain. 4 // Extensively modified by John Cowan for TagSoup. 5 // TagSoup is licensed under the Apache License, 6 // Version 2.0. You may obtain a copy of this license at 7 // http://www.apache.org/licenses/LICENSE-2.0 . You may also have 8 // additional legal rights not granted by this license. 9 // 10 // TagSoup is distributed in the hope that it will be useful, but 11 // unless required by applicable law or agreed to in writing, TagSoup 12 // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 13 // OF ANY KIND, either express or implied; not even the implied warranty 14 // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 15 16 package org.ccil.cowan.tagsoup; 17 18 import java.io.IOException; 19 import java.io.OutputStreamWriter; 20 import java.io.Writer; 21 import java.util.Enumeration; 22 import java.util.Hashtable; 23 import java.util.Properties; 24 25 import org.xml.sax.Attributes; 26 import org.xml.sax.SAXException; 27 import org.xml.sax.XMLReader; 28 import org.xml.sax.helpers.AttributesImpl; 29 import org.xml.sax.helpers.NamespaceSupport; 30 import org.xml.sax.helpers.XMLFilterImpl; 31 import org.xml.sax.ext.LexicalHandler; 32 33 34 /** 35 * Filter to write an XML document from a SAX event stream. 36 * 37 * <p>This class can be used by itself or as part of a SAX event 38 * stream: it takes as input a series of SAX2 ContentHandler 39 * events and uses the information in those events to write 40 * an XML document. Since this class is a filter, it can also 41 * pass the events on down a filter chain for further processing 42 * (you can use the XMLWriter to take a snapshot of the current 43 * state at any point in a filter chain), and it can be 44 * used directly as a ContentHandler for a SAX2 XMLReader.</p> 45 * 46 * <p>The client creates a document by invoking the methods for 47 * standard SAX2 events, always beginning with the 48 * {@link #startDocument startDocument} method and ending with 49 * the {@link #endDocument endDocument} method. There are convenience 50 * methods provided so that clients to not have to create empty 51 * attribute lists or provide empty strings as parameters; for 52 * example, the method invocation</p> 53 * 54 * <pre> 55 * w.startElement("foo"); 56 * </pre> 57 * 58 * <p>is equivalent to the regular SAX2 ContentHandler method</p> 59 * 60 * <pre> 61 * w.startElement("", "foo", "", new AttributesImpl()); 62 * </pre> 63 * 64 * <p>Except that it is more efficient because it does not allocate 65 * a new empty attribute list each time. The following code will send 66 * a simple XML document to standard output:</p> 67 * 68 * <pre> 69 * XMLWriter w = new XMLWriter(); 70 * 71 * w.startDocument(); 72 * w.startElement("greeting"); 73 * w.characters("Hello, world!"); 74 * w.endElement("greeting"); 75 * w.endDocument(); 76 * </pre> 77 * 78 * <p>The resulting document will look like this:</p> 79 * 80 * <pre> 81 * <?xml version="1.0" standalone="yes"?> 82 * 83 * <greeting>Hello, world!</greeting> 84 * </pre> 85 * 86 * <p>In fact, there is an even simpler convenience method, 87 * <var>dataElement</var>, designed for writing elements that 88 * contain only character data, so the code to generate the 89 * document could be shortened to</p> 90 * 91 * <pre> 92 * XMLWriter w = new XMLWriter(); 93 * 94 * w.startDocument(); 95 * w.dataElement("greeting", "Hello, world!"); 96 * w.endDocument(); 97 * </pre> 98 * 99 * <h2>Whitespace</h2> 100 * 101 * <p>According to the XML Recommendation, <em>all</em> whitespace 102 * in an XML document is potentially significant to an application, 103 * so this class never adds newlines or indentation. If you 104 * insert three elements in a row, as in</p> 105 * 106 * <pre> 107 * w.dataElement("item", "1"); 108 * w.dataElement("item", "2"); 109 * w.dataElement("item", "3"); 110 * </pre> 111 * 112 * <p>you will end up with</p> 113 * 114 * <pre> 115 * <item>1</item><item>3</item><item>3</item> 116 * </pre> 117 * 118 * <p>You need to invoke one of the <var>characters</var> methods 119 * explicitly to add newlines or indentation. Alternatively, you 120 * can use {@link com.megginson.sax.DataWriter DataWriter}, which 121 * is derived from this class -- it is optimized for writing 122 * purely data-oriented (or field-oriented) XML, and does automatic 123 * linebreaks and indentation (but does not support mixed content 124 * properly).</p> 125 * 126 * 127 * <h2>Namespace Support</h2> 128 * 129 * <p>The writer contains extensive support for XML Namespaces, so that 130 * a client application does not have to keep track of prefixes and 131 * supply <var>xmlns</var> attributes. By default, the XML writer will 132 * generate Namespace declarations in the form _NS1, _NS2, etc., wherever 133 * they are needed, as in the following example:</p> 134 * 135 * <pre> 136 * w.startDocument(); 137 * w.emptyElement("http://www.foo.com/ns/", "foo"); 138 * w.endDocument(); 139 * </pre> 140 * 141 * <p>The resulting document will look like this:</p> 142 * 143 * <pre> 144 * <?xml version="1.0" standalone="yes"?> 145 * 146 * <_NS1:foo xmlns:_NS1="http://www.foo.com/ns/"/> 147 * </pre> 148 * 149 * <p>In many cases, document authors will prefer to choose their 150 * own prefixes rather than using the (ugly) default names. The 151 * XML writer allows two methods for selecting prefixes:</p> 152 * 153 * <ol> 154 * <li>the qualified name</li> 155 * <li>the {@link #setPrefix setPrefix} method.</li> 156 * </ol> 157 * 158 * <p>Whenever the XML writer finds a new Namespace URI, it checks 159 * to see if a qualified (prefixed) name is also available; if so 160 * it attempts to use the name's prefix (as long as the prefix is 161 * not already in use for another Namespace URI).</p> 162 * 163 * <p>Before writing a document, the client can also pre-map a prefix 164 * to a Namespace URI with the setPrefix method:</p> 165 * 166 * <pre> 167 * w.setPrefix("http://www.foo.com/ns/", "foo"); 168 * w.startDocument(); 169 * w.emptyElement("http://www.foo.com/ns/", "foo"); 170 * w.endDocument(); 171 * </pre> 172 * 173 * <p>The resulting document will look like this:</p> 174 * 175 * <pre> 176 * <?xml version="1.0" standalone="yes"?> 177 * 178 * <foo:foo xmlns:foo="http://www.foo.com/ns/"/> 179 * </pre> 180 * 181 * <p>The default Namespace simply uses an empty string as the prefix:</p> 182 * 183 * <pre> 184 * w.setPrefix("http://www.foo.com/ns/", ""); 185 * w.startDocument(); 186 * w.emptyElement("http://www.foo.com/ns/", "foo"); 187 * w.endDocument(); 188 * </pre> 189 * 190 * <p>The resulting document will look like this:</p> 191 * 192 * <pre> 193 * <?xml version="1.0" standalone="yes"?> 194 * 195 * <foo xmlns="http://www.foo.com/ns/"/> 196 * </pre> 197 * 198 * <p>By default, the XML writer will not declare a Namespace until 199 * it is actually used. Sometimes, this approach will create 200 * a large number of Namespace declarations, as in the following 201 * example:</p> 202 * 203 * <pre> 204 * <xml version="1.0" standalone="yes"?> 205 * 206 * <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> 207 * <rdf:Description about="http://www.foo.com/ids/books/12345"> 208 * <dc:title xmlns:dc="http://www.purl.org/dc/">A Dark Night</dc:title> 209 * <dc:creator xmlns:dc="http://www.purl.org/dc/">Jane Smith</dc:title> 210 * <dc:date xmlns:dc="http://www.purl.org/dc/">2000-09-09</dc:title> 211 * </rdf:Description> 212 * </rdf:RDF> 213 * </pre> 214 * 215 * <p>The "rdf" prefix is declared only once, because the RDF Namespace 216 * is used by the root element and can be inherited by all of its 217 * descendants; the "dc" prefix, on the other hand, is declared three 218 * times, because no higher element uses the Namespace. To solve this 219 * problem, you can instruct the XML writer to predeclare Namespaces 220 * on the root element even if they are not used there:</p> 221 * 222 * <pre> 223 * w.forceNSDecl("http://www.purl.org/dc/"); 224 * </pre> 225 * 226 * <p>Now, the "dc" prefix will be declared on the root element even 227 * though it's not needed there, and can be inherited by its 228 * descendants:</p> 229 * 230 * <pre> 231 * <xml version="1.0" standalone="yes"?> 232 * 233 * <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 234 * xmlns:dc="http://www.purl.org/dc/"> 235 * <rdf:Description about="http://www.foo.com/ids/books/12345"> 236 * <dc:title>A Dark Night</dc:title> 237 * <dc:creator>Jane Smith</dc:title> 238 * <dc:date>2000-09-09</dc:title> 239 * </rdf:Description> 240 * </rdf:RDF> 241 * </pre> 242 * 243 * <p>This approach is also useful for declaring Namespace prefixes 244 * that be used by qualified names appearing in attribute values or 245 * character data.</p> 246 * 247 * @author David Megginson, david@megginson.com 248 * @version 0.2 249 * @see org.xml.sax.XMLFilter 250 * @see org.xml.sax.ContentHandler 251 */ 252 public class XMLWriter extends XMLFilterImpl implements LexicalHandler 253 { 254 255 256 //////////////////////////////////////////////////////////////////// 257 // Constructors. 258 //////////////////////////////////////////////////////////////////// 259 260 261 /** 262 * Create a new XML writer. 263 * 264 * <p>Write to standard output.</p> 265 */ XMLWriter()266 public XMLWriter () 267 { 268 init(null); 269 } 270 271 272 /** 273 * Create a new XML writer. 274 * 275 * <p>Write to the writer provided.</p> 276 * 277 * @param writer The output destination, or null to use standard 278 * output. 279 */ XMLWriter(Writer writer)280 public XMLWriter (Writer writer) 281 { 282 init(writer); 283 } 284 285 286 /** 287 * Create a new XML writer. 288 * 289 * <p>Use the specified XML reader as the parent.</p> 290 * 291 * @param xmlreader The parent in the filter chain, or null 292 * for no parent. 293 */ XMLWriter(XMLReader xmlreader)294 public XMLWriter (XMLReader xmlreader) 295 { 296 super(xmlreader); 297 init(null); 298 } 299 300 301 /** 302 * Create a new XML writer. 303 * 304 * <p>Use the specified XML reader as the parent, and write 305 * to the specified writer.</p> 306 * 307 * @param xmlreader The parent in the filter chain, or null 308 * for no parent. 309 * @param writer The output destination, or null to use standard 310 * output. 311 */ XMLWriter(XMLReader xmlreader, Writer writer)312 public XMLWriter (XMLReader xmlreader, Writer writer) 313 { 314 super(xmlreader); 315 init(writer); 316 } 317 318 319 /** 320 * Internal initialization method. 321 * 322 * <p>All of the public constructors invoke this method. 323 * 324 * @param writer The output destination, or null to use 325 * standard output. 326 */ init(Writer writer)327 private void init (Writer writer) 328 { 329 setOutput(writer); 330 nsSupport = new NamespaceSupport(); 331 prefixTable = new Hashtable(); 332 forcedDeclTable = new Hashtable(); 333 doneDeclTable = new Hashtable(); 334 outputProperties = new Properties(); 335 } 336 337 338 339 //////////////////////////////////////////////////////////////////// 340 // Public methods. 341 //////////////////////////////////////////////////////////////////// 342 343 344 /** 345 * Reset the writer. 346 * 347 * <p>This method is especially useful if the writer throws an 348 * exception before it is finished, and you want to reuse the 349 * writer for a new document. It is usually a good idea to 350 * invoke {@link #flush flush} before resetting the writer, 351 * to make sure that no output is lost.</p> 352 * 353 * <p>This method is invoked automatically by the 354 * {@link #startDocument startDocument} method before writing 355 * a new document.</p> 356 * 357 * <p><strong>Note:</strong> this method will <em>not</em> 358 * clear the prefix or URI information in the writer or 359 * the selected output writer.</p> 360 * 361 * @see #flush 362 */ reset()363 public void reset () 364 { 365 elementLevel = 0; 366 prefixCounter = 0; 367 nsSupport.reset(); 368 } 369 370 371 /** 372 * Flush the output. 373 * 374 * <p>This method flushes the output stream. It is especially useful 375 * when you need to make certain that the entire document has 376 * been written to output but do not want to close the output 377 * stream.</p> 378 * 379 * <p>This method is invoked automatically by the 380 * {@link #endDocument endDocument} method after writing a 381 * document.</p> 382 * 383 * @see #reset 384 */ flush()385 public void flush () 386 throws IOException 387 { 388 output.flush(); 389 } 390 391 392 /** 393 * Set a new output destination for the document. 394 * 395 * @param writer The output destination, or null to use 396 * standard output. 397 * @return The current output writer. 398 * @see #flush 399 */ setOutput(Writer writer)400 public void setOutput (Writer writer) 401 { 402 if (writer == null) { 403 output = new OutputStreamWriter(System.out); 404 } else { 405 output = writer; 406 } 407 } 408 409 410 /** 411 * Specify a preferred prefix for a Namespace URI. 412 * 413 * <p>Note that this method does not actually force the Namespace 414 * to be declared; to do that, use the {@link 415 * #forceNSDecl(java.lang.String) forceNSDecl} method as well.</p> 416 * 417 * @param uri The Namespace URI. 418 * @param prefix The preferred prefix, or "" to select 419 * the default Namespace. 420 * @see #getPrefix 421 * @see #forceNSDecl(java.lang.String) 422 * @see #forceNSDecl(java.lang.String,java.lang.String) 423 */ setPrefix(String uri, String prefix)424 public void setPrefix (String uri, String prefix) 425 { 426 prefixTable.put(uri, prefix); 427 } 428 429 430 /** 431 * Get the current or preferred prefix for a Namespace URI. 432 * 433 * @param uri The Namespace URI. 434 * @return The preferred prefix, or "" for the default Namespace. 435 * @see #setPrefix 436 */ getPrefix(String uri)437 public String getPrefix (String uri) 438 { 439 return (String)prefixTable.get(uri); 440 } 441 442 443 /** 444 * Force a Namespace to be declared on the root element. 445 * 446 * <p>By default, the XMLWriter will declare only the Namespaces 447 * needed for an element; as a result, a Namespace may be 448 * declared many places in a document if it is not used on the 449 * root element.</p> 450 * 451 * <p>This method forces a Namespace to be declared on the root 452 * element even if it is not used there, and reduces the number 453 * of xmlns attributes in the document.</p> 454 * 455 * @param uri The Namespace URI to declare. 456 * @see #forceNSDecl(java.lang.String,java.lang.String) 457 * @see #setPrefix 458 */ forceNSDecl(String uri)459 public void forceNSDecl (String uri) 460 { 461 forcedDeclTable.put(uri, Boolean.TRUE); 462 } 463 464 465 /** 466 * Force a Namespace declaration with a preferred prefix. 467 * 468 * <p>This is a convenience method that invokes {@link 469 * #setPrefix setPrefix} then {@link #forceNSDecl(java.lang.String) 470 * forceNSDecl}.</p> 471 * 472 * @param uri The Namespace URI to declare on the root element. 473 * @param prefix The preferred prefix for the Namespace, or "" 474 * for the default Namespace. 475 * @see #setPrefix 476 * @see #forceNSDecl(java.lang.String) 477 */ forceNSDecl(String uri, String prefix)478 public void forceNSDecl (String uri, String prefix) 479 { 480 setPrefix(uri, prefix); 481 forceNSDecl(uri); 482 } 483 484 485 486 //////////////////////////////////////////////////////////////////// 487 // Methods from org.xml.sax.ContentHandler. 488 //////////////////////////////////////////////////////////////////// 489 490 491 /** 492 * Write the XML declaration at the beginning of the document. 493 * 494 * Pass the event on down the filter chain for further processing. 495 * 496 * @exception org.xml.sax.SAXException If there is an error 497 * writing the XML declaration, or if a handler further down 498 * the filter chain raises an exception. 499 * @see org.xml.sax.ContentHandler#startDocument 500 */ startDocument()501 public void startDocument () 502 throws SAXException 503 { 504 reset(); 505 if (!("yes".equals(outputProperties.getProperty(OMIT_XML_DECLARATION, "no")))) { 506 write("<?xml"); 507 if (version == null) { 508 write(" version=\"1.0\""); 509 } else { 510 write(" version=\""); 511 write(version); 512 write("\""); 513 } 514 if (outputEncoding != null && outputEncoding != "") { 515 write(" encoding=\""); 516 write(outputEncoding); 517 write("\""); 518 } 519 if (standalone == null) { 520 write(" standalone=\"yes\"?>\n"); 521 } else { 522 write(" standalone=\""); 523 write(standalone); 524 write("\""); 525 } 526 } 527 super.startDocument(); 528 } 529 530 531 /** 532 * Write a newline at the end of the document. 533 * 534 * Pass the event on down the filter chain for further processing. 535 * 536 * @exception org.xml.sax.SAXException If there is an error 537 * writing the newline, or if a handler further down 538 * the filter chain raises an exception. 539 * @see org.xml.sax.ContentHandler#endDocument 540 */ endDocument()541 public void endDocument () 542 throws SAXException 543 { 544 write('\n'); 545 super.endDocument(); 546 try { 547 flush(); 548 } catch (IOException e) { 549 throw new SAXException(e); 550 } 551 } 552 553 554 /** 555 * Write a start tag. 556 * 557 * Pass the event on down the filter chain for further processing. 558 * 559 * @param uri The Namespace URI, or the empty string if none 560 * is available. 561 * @param localName The element's local (unprefixed) name (required). 562 * @param qName The element's qualified (prefixed) name, or the 563 * empty string is none is available. This method will 564 * use the qName as a template for generating a prefix 565 * if necessary, but it is not guaranteed to use the 566 * same qName. 567 * @param atts The element's attribute list (must not be null). 568 * @exception org.xml.sax.SAXException If there is an error 569 * writing the start tag, or if a handler further down 570 * the filter chain raises an exception. 571 * @see org.xml.sax.ContentHandler#startElement 572 */ startElement(String uri, String localName, String qName, Attributes atts)573 public void startElement (String uri, String localName, 574 String qName, Attributes atts) 575 throws SAXException 576 { 577 elementLevel++; 578 nsSupport.pushContext(); 579 if (forceDTD && !hasOutputDTD) startDTD(localName == null ? qName : localName, "", ""); 580 write('<'); 581 writeName(uri, localName, qName, true); 582 writeAttributes(atts); 583 if (elementLevel == 1) { 584 forceNSDecls(); 585 } 586 writeNSDecls(); 587 write('>'); 588 // System.out.println("%%%% startElement [" + qName + "] htmlMode = " + htmlMode); 589 if (htmlMode && (qName.equals("script") || qName.equals("style"))) { 590 cdataElement = true; 591 // System.out.println("%%%% CDATA element"); 592 } 593 super.startElement(uri, localName, qName, atts); 594 } 595 596 597 /** 598 * Write an end tag. 599 * 600 * Pass the event on down the filter chain for further processing. 601 * 602 * @param uri The Namespace URI, or the empty string if none 603 * is available. 604 * @param localName The element's local (unprefixed) name (required). 605 * @param qName The element's qualified (prefixed) name, or the 606 * empty string is none is available. This method will 607 * use the qName as a template for generating a prefix 608 * if necessary, but it is not guaranteed to use the 609 * same qName. 610 * @exception org.xml.sax.SAXException If there is an error 611 * writing the end tag, or if a handler further down 612 * the filter chain raises an exception. 613 * @see org.xml.sax.ContentHandler#endElement 614 */ endElement(String uri, String localName, String qName)615 public void endElement (String uri, String localName, String qName) 616 throws SAXException 617 { 618 if (!(htmlMode && 619 (uri.equals("http://www.w3.org/1999/xhtml") || 620 uri.equals("")) && 621 (qName.equals("area") || qName.equals("base") || 622 qName.equals("basefont") || qName.equals("br") || 623 qName.equals("col") || qName.equals("frame") || 624 qName.equals("hr") || qName.equals("img") || 625 qName.equals("input") || qName.equals("isindex") || 626 qName.equals("link") || qName.equals("meta") || 627 qName.equals("param")))) { 628 write("</"); 629 writeName(uri, localName, qName, true); 630 write('>'); 631 } 632 if (elementLevel == 1) { 633 write('\n'); 634 } 635 cdataElement = false; 636 super.endElement(uri, localName, qName); 637 nsSupport.popContext(); 638 elementLevel--; 639 } 640 641 642 /** 643 * Write character data. 644 * 645 * Pass the event on down the filter chain for further processing. 646 * 647 * @param ch The array of characters to write. 648 * @param start The starting position in the array. 649 * @param length The number of characters to write. 650 * @exception org.xml.sax.SAXException If there is an error 651 * writing the characters, or if a handler further down 652 * the filter chain raises an exception. 653 * @see org.xml.sax.ContentHandler#characters 654 */ characters(char ch[], int start, int len)655 public void characters (char ch[], int start, int len) 656 throws SAXException 657 { 658 if (!cdataElement) { 659 writeEsc(ch, start, len, false); 660 } 661 else { 662 for (int i = start; i < start + len; i++) { 663 write(ch[i]); 664 } 665 } 666 super.characters(ch, start, len); 667 } 668 669 670 /** 671 * Write ignorable whitespace. 672 * 673 * Pass the event on down the filter chain for further processing. 674 * 675 * @param ch The array of characters to write. 676 * @param start The starting position in the array. 677 * @param length The number of characters to write. 678 * @exception org.xml.sax.SAXException If there is an error 679 * writing the whitespace, or if a handler further down 680 * the filter chain raises an exception. 681 * @see org.xml.sax.ContentHandler#ignorableWhitespace 682 */ ignorableWhitespace(char ch[], int start, int length)683 public void ignorableWhitespace (char ch[], int start, int length) 684 throws SAXException 685 { 686 writeEsc(ch, start, length, false); 687 super.ignorableWhitespace(ch, start, length); 688 } 689 690 691 692 /** 693 * Write a processing instruction. 694 * 695 * Pass the event on down the filter chain for further processing. 696 * 697 * @param target The PI target. 698 * @param data The PI data. 699 * @exception org.xml.sax.SAXException If there is an error 700 * writing the PI, or if a handler further down 701 * the filter chain raises an exception. 702 * @see org.xml.sax.ContentHandler#processingInstruction 703 */ processingInstruction(String target, String data)704 public void processingInstruction (String target, String data) 705 throws SAXException 706 { 707 write("<?"); 708 write(target); 709 write(' '); 710 write(data); 711 write("?>"); 712 if (elementLevel < 1) { 713 write('\n'); 714 } 715 super.processingInstruction(target, data); 716 } 717 718 719 720 //////////////////////////////////////////////////////////////////// 721 // Additional markup. 722 //////////////////////////////////////////////////////////////////// 723 724 /** 725 * Write an empty element. 726 * 727 * This method writes an empty element tag rather than a start tag 728 * followed by an end tag. Both a {@link #startElement 729 * startElement} and an {@link #endElement endElement} event will 730 * be passed on down the filter chain. 731 * 732 * @param uri The element's Namespace URI, or the empty string 733 * if the element has no Namespace or if Namespace 734 * processing is not being performed. 735 * @param localName The element's local name (without prefix). This 736 * parameter must be provided. 737 * @param qName The element's qualified name (with prefix), or 738 * the empty string if none is available. This parameter 739 * is strictly advisory: the writer may or may not use 740 * the prefix attached. 741 * @param atts The element's attribute list. 742 * @exception org.xml.sax.SAXException If there is an error 743 * writing the empty tag, or if a handler further down 744 * the filter chain raises an exception. 745 * @see #startElement 746 * @see #endElement 747 */ emptyElement(String uri, String localName, String qName, Attributes atts)748 public void emptyElement (String uri, String localName, 749 String qName, Attributes atts) 750 throws SAXException 751 { 752 nsSupport.pushContext(); 753 write('<'); 754 writeName(uri, localName, qName, true); 755 writeAttributes(atts); 756 if (elementLevel == 1) { 757 forceNSDecls(); 758 } 759 writeNSDecls(); 760 write("/>"); 761 super.startElement(uri, localName, qName, atts); 762 super.endElement(uri, localName, qName); 763 } 764 765 766 767 //////////////////////////////////////////////////////////////////// 768 // Convenience methods. 769 //////////////////////////////////////////////////////////////////// 770 771 772 773 /** 774 * Start a new element without a qname or attributes. 775 * 776 * <p>This method will provide a default empty attribute 777 * list and an empty string for the qualified name. 778 * It invokes {@link 779 * #startElement(String, String, String, Attributes)} 780 * directly.</p> 781 * 782 * @param uri The element's Namespace URI. 783 * @param localName The element's local name. 784 * @exception org.xml.sax.SAXException If there is an error 785 * writing the start tag, or if a handler further down 786 * the filter chain raises an exception. 787 * @see #startElement(String, String, String, Attributes) 788 */ startElement(String uri, String localName)789 public void startElement (String uri, String localName) 790 throws SAXException 791 { 792 startElement(uri, localName, "", EMPTY_ATTS); 793 } 794 795 796 /** 797 * Start a new element without a qname, attributes or a Namespace URI. 798 * 799 * <p>This method will provide an empty string for the 800 * Namespace URI, and empty string for the qualified name, 801 * and a default empty attribute list. It invokes 802 * #startElement(String, String, String, Attributes)} 803 * directly.</p> 804 * 805 * @param localName The element's local name. 806 * @exception org.xml.sax.SAXException If there is an error 807 * writing the start tag, or if a handler further down 808 * the filter chain raises an exception. 809 * @see #startElement(String, String, String, Attributes) 810 */ startElement(String localName)811 public void startElement (String localName) 812 throws SAXException 813 { 814 startElement("", localName, "", EMPTY_ATTS); 815 } 816 817 818 /** 819 * End an element without a qname. 820 * 821 * <p>This method will supply an empty string for the qName. 822 * It invokes {@link #endElement(String, String, String)} 823 * directly.</p> 824 * 825 * @param uri The element's Namespace URI. 826 * @param localName The element's local name. 827 * @exception org.xml.sax.SAXException If there is an error 828 * writing the end tag, or if a handler further down 829 * the filter chain raises an exception. 830 * @see #endElement(String, String, String) 831 */ endElement(String uri, String localName)832 public void endElement (String uri, String localName) 833 throws SAXException 834 { 835 endElement(uri, localName, ""); 836 } 837 838 839 /** 840 * End an element without a Namespace URI or qname. 841 * 842 * <p>This method will supply an empty string for the qName 843 * and an empty string for the Namespace URI. 844 * It invokes {@link #endElement(String, String, String)} 845 * directly.</p> 846 * 847 * @param localName The element's local name. 848 * @exception org.xml.sax.SAXException If there is an error 849 * writing the end tag, or if a handler further down 850 * the filter chain raises an exception. 851 * @see #endElement(String, String, String) 852 */ endElement(String localName)853 public void endElement (String localName) 854 throws SAXException 855 { 856 endElement("", localName, ""); 857 } 858 859 860 /** 861 * Add an empty element without a qname or attributes. 862 * 863 * <p>This method will supply an empty string for the qname 864 * and an empty attribute list. It invokes 865 * {@link #emptyElement(String, String, String, Attributes)} 866 * directly.</p> 867 * 868 * @param uri The element's Namespace URI. 869 * @param localName The element's local name. 870 * @exception org.xml.sax.SAXException If there is an error 871 * writing the empty tag, or if a handler further down 872 * the filter chain raises an exception. 873 * @see #emptyElement(String, String, String, Attributes) 874 */ emptyElement(String uri, String localName)875 public void emptyElement (String uri, String localName) 876 throws SAXException 877 { 878 emptyElement(uri, localName, "", EMPTY_ATTS); 879 } 880 881 882 /** 883 * Add an empty element without a Namespace URI, qname or attributes. 884 * 885 * <p>This method will supply an empty string for the qname, 886 * and empty string for the Namespace URI, and an empty 887 * attribute list. It invokes 888 * {@link #emptyElement(String, String, String, Attributes)} 889 * directly.</p> 890 * 891 * @param localName The element's local name. 892 * @exception org.xml.sax.SAXException If there is an error 893 * writing the empty tag, or if a handler further down 894 * the filter chain raises an exception. 895 * @see #emptyElement(String, String, String, Attributes) 896 */ emptyElement(String localName)897 public void emptyElement (String localName) 898 throws SAXException 899 { 900 emptyElement("", localName, "", EMPTY_ATTS); 901 } 902 903 904 /** 905 * Write an element with character data content. 906 * 907 * <p>This is a convenience method to write a complete element 908 * with character data content, including the start tag 909 * and end tag.</p> 910 * 911 * <p>This method invokes 912 * {@link #startElement(String, String, String, Attributes)}, 913 * followed by 914 * {@link #characters(String)}, followed by 915 * {@link #endElement(String, String, String)}.</p> 916 * 917 * @param uri The element's Namespace URI. 918 * @param localName The element's local name. 919 * @param qName The element's default qualified name. 920 * @param atts The element's attributes. 921 * @param content The character data content. 922 * @exception org.xml.sax.SAXException If there is an error 923 * writing the empty tag, or if a handler further down 924 * the filter chain raises an exception. 925 * @see #startElement(String, String, String, Attributes) 926 * @see #characters(String) 927 * @see #endElement(String, String, String) 928 */ dataElement(String uri, String localName, String qName, Attributes atts, String content)929 public void dataElement (String uri, String localName, 930 String qName, Attributes atts, 931 String content) 932 throws SAXException 933 { 934 startElement(uri, localName, qName, atts); 935 characters(content); 936 endElement(uri, localName, qName); 937 } 938 939 940 /** 941 * Write an element with character data content but no attributes. 942 * 943 * <p>This is a convenience method to write a complete element 944 * with character data content, including the start tag 945 * and end tag. This method provides an empty string 946 * for the qname and an empty attribute list.</p> 947 * 948 * <p>This method invokes 949 * {@link #startElement(String, String, String, Attributes)}, 950 * followed by 951 * {@link #characters(String)}, followed by 952 * {@link #endElement(String, String, String)}.</p> 953 * 954 * @param uri The element's Namespace URI. 955 * @param localName The element's local name. 956 * @param content The character data content. 957 * @exception org.xml.sax.SAXException If there is an error 958 * writing the empty tag, or if a handler further down 959 * the filter chain raises an exception. 960 * @see #startElement(String, String, String, Attributes) 961 * @see #characters(String) 962 * @see #endElement(String, String, String) 963 */ dataElement(String uri, String localName, String content)964 public void dataElement (String uri, String localName, String content) 965 throws SAXException 966 { 967 dataElement(uri, localName, "", EMPTY_ATTS, content); 968 } 969 970 971 /** 972 * Write an element with character data content but no attributes or Namespace URI. 973 * 974 * <p>This is a convenience method to write a complete element 975 * with character data content, including the start tag 976 * and end tag. The method provides an empty string for the 977 * Namespace URI, and empty string for the qualified name, 978 * and an empty attribute list.</p> 979 * 980 * <p>This method invokes 981 * {@link #startElement(String, String, String, Attributes)}, 982 * followed by 983 * {@link #characters(String)}, followed by 984 * {@link #endElement(String, String, String)}.</p> 985 * 986 * @param localName The element's local name. 987 * @param content The character data content. 988 * @exception org.xml.sax.SAXException If there is an error 989 * writing the empty tag, or if a handler further down 990 * the filter chain raises an exception. 991 * @see #startElement(String, String, String, Attributes) 992 * @see #characters(String) 993 * @see #endElement(String, String, String) 994 */ dataElement(String localName, String content)995 public void dataElement (String localName, String content) 996 throws SAXException 997 { 998 dataElement("", localName, "", EMPTY_ATTS, content); 999 } 1000 1001 1002 /** 1003 * Write a string of character data, with XML escaping. 1004 * 1005 * <p>This is a convenience method that takes an XML 1006 * String, converts it to a character array, then invokes 1007 * {@link #characters(char[], int, int)}.</p> 1008 * 1009 * @param data The character data. 1010 * @exception org.xml.sax.SAXException If there is an error 1011 * writing the string, or if a handler further down 1012 * the filter chain raises an exception. 1013 * @see #characters(char[], int, int) 1014 */ characters(String data)1015 public void characters (String data) 1016 throws SAXException 1017 { 1018 char ch[] = data.toCharArray(); 1019 characters(ch, 0, ch.length); 1020 } 1021 1022 1023 1024 //////////////////////////////////////////////////////////////////// 1025 // Internal methods. 1026 //////////////////////////////////////////////////////////////////// 1027 1028 1029 /** 1030 * Force all Namespaces to be declared. 1031 * 1032 * This method is used on the root element to ensure that 1033 * the predeclared Namespaces all appear. 1034 */ forceNSDecls()1035 private void forceNSDecls () 1036 { 1037 Enumeration prefixes = forcedDeclTable.keys(); 1038 while (prefixes.hasMoreElements()) { 1039 String prefix = (String)prefixes.nextElement(); 1040 doPrefix(prefix, null, true); 1041 } 1042 } 1043 1044 1045 /** 1046 * Determine the prefix for an element or attribute name. 1047 * 1048 * TODO: this method probably needs some cleanup. 1049 * 1050 * @param uri The Namespace URI. 1051 * @param qName The qualified name (optional); this will be used 1052 * to indicate the preferred prefix if none is currently 1053 * bound. 1054 * @param isElement true if this is an element name, false 1055 * if it is an attribute name (which cannot use the 1056 * default Namespace). 1057 */ doPrefix(String uri, String qName, boolean isElement)1058 private String doPrefix (String uri, String qName, boolean isElement) 1059 { 1060 String defaultNS = nsSupport.getURI(""); 1061 if ("".equals(uri)) { 1062 if (isElement && defaultNS != null) 1063 nsSupport.declarePrefix("", ""); 1064 return null; 1065 } 1066 String prefix; 1067 if (isElement && defaultNS != null && uri.equals(defaultNS)) { 1068 prefix = ""; 1069 } else { 1070 prefix = nsSupport.getPrefix(uri); 1071 } 1072 if (prefix != null) { 1073 return prefix; 1074 } 1075 prefix = (String) doneDeclTable.get(uri); 1076 if (prefix != null && 1077 ((!isElement || defaultNS != null) && 1078 "".equals(prefix) || nsSupport.getURI(prefix) != null)) { 1079 prefix = null; 1080 } 1081 if (prefix == null) { 1082 prefix = (String) prefixTable.get(uri); 1083 if (prefix != null && 1084 ((!isElement || defaultNS != null) && 1085 "".equals(prefix) || nsSupport.getURI(prefix) != null)) { 1086 prefix = null; 1087 } 1088 } 1089 if (prefix == null && qName != null && !"".equals(qName)) { 1090 int i = qName.indexOf(':'); 1091 if (i == -1) { 1092 if (isElement && defaultNS == null) { 1093 prefix = ""; 1094 } 1095 } else { 1096 prefix = qName.substring(0, i); 1097 } 1098 } 1099 for (; 1100 prefix == null || nsSupport.getURI(prefix) != null; 1101 prefix = "__NS" + ++prefixCounter) 1102 ; 1103 nsSupport.declarePrefix(prefix, uri); 1104 doneDeclTable.put(uri, prefix); 1105 return prefix; 1106 } 1107 1108 1109 /** 1110 * Write a raw character. 1111 * 1112 * @param c The character to write. 1113 * @exception org.xml.sax.SAXException If there is an error writing 1114 * the character, this method will throw an IOException 1115 * wrapped in a SAXException. 1116 */ write(char c)1117 private void write (char c) 1118 throws SAXException 1119 { 1120 try { 1121 output.write(c); 1122 } catch (IOException e) { 1123 throw new SAXException(e); 1124 } 1125 } 1126 1127 1128 /** 1129 * Write a raw string. 1130 * 1131 * @param s 1132 * @exception org.xml.sax.SAXException If there is an error writing 1133 * the string, this method will throw an IOException 1134 * wrapped in a SAXException 1135 */ write(String s)1136 private void write (String s) 1137 throws SAXException 1138 { 1139 try { 1140 output.write(s); 1141 } catch (IOException e) { 1142 throw new SAXException(e); 1143 } 1144 } 1145 1146 1147 /** 1148 * Write out an attribute list, escaping values. 1149 * 1150 * The names will have prefixes added to them. 1151 * 1152 * @param atts The attribute list to write. 1153 * @exception org.xml.SAXException If there is an error writing 1154 * the attribute list, this method will throw an 1155 * IOException wrapped in a SAXException. 1156 */ writeAttributes(Attributes atts)1157 private void writeAttributes (Attributes atts) 1158 throws SAXException 1159 { 1160 int len = atts.getLength(); 1161 for (int i = 0; i < len; i++) { 1162 char ch[] = atts.getValue(i).toCharArray(); 1163 write(' '); 1164 writeName(atts.getURI(i), atts.getLocalName(i), 1165 atts.getQName(i), false); 1166 if (htmlMode && 1167 booleanAttribute(atts.getLocalName(i), atts.getQName(i), atts.getValue(i))) break; 1168 write("=\""); 1169 writeEsc(ch, 0, ch.length, true); 1170 write('"'); 1171 } 1172 } 1173 1174 1175 private String[] booleans = {"checked", "compact", "declare", "defer", 1176 "disabled", "ismap", "multiple", 1177 "nohref", "noresize", "noshade", 1178 "nowrap", "readonly", "selected"}; 1179 1180 // Return true if the attribute is an HTML boolean from the above list. booleanAttribute(String localName, String qName, String value)1181 private boolean booleanAttribute (String localName, String qName, String value) 1182 { 1183 String name = localName; 1184 if (name == null) { 1185 int i = qName.indexOf(':'); 1186 if (i != -1) name = qName.substring(i + 1, qName.length()); 1187 } 1188 if (!name.equals(value)) return false; 1189 for (int j = 0; j < booleans.length; j++) { 1190 if (name.equals(booleans[j])) return true; 1191 } 1192 return false; 1193 } 1194 1195 /** 1196 * Write an array of data characters with escaping. 1197 * 1198 * @param ch The array of characters. 1199 * @param start The starting position. 1200 * @param length The number of characters to use. 1201 * @param isAttVal true if this is an attribute value literal. 1202 * @exception org.xml.SAXException If there is an error writing 1203 * the characters, this method will throw an 1204 * IOException wrapped in a SAXException. 1205 */ writeEsc(char ch[], int start, int length, boolean isAttVal)1206 private void writeEsc (char ch[], int start, 1207 int length, boolean isAttVal) 1208 throws SAXException 1209 { 1210 for (int i = start; i < start + length; i++) { 1211 switch (ch[i]) { 1212 case '&': 1213 write("&"); 1214 break; 1215 case '<': 1216 write("<"); 1217 break; 1218 case '>': 1219 write(">"); 1220 break; 1221 case '\"': 1222 if (isAttVal) { 1223 write("""); 1224 } else { 1225 write('\"'); 1226 } 1227 break; 1228 default: 1229 if (!unicodeMode && ch[i] > '\u007f') { 1230 write("&#"); 1231 write(Integer.toString(ch[i])); 1232 write(';'); 1233 } else { 1234 write(ch[i]); 1235 } 1236 } 1237 } 1238 } 1239 1240 1241 /** 1242 * Write out the list of Namespace declarations. 1243 * 1244 * @exception org.xml.sax.SAXException This method will throw 1245 * an IOException wrapped in a SAXException if 1246 * there is an error writing the Namespace 1247 * declarations. 1248 */ writeNSDecls()1249 private void writeNSDecls () 1250 throws SAXException 1251 { 1252 Enumeration prefixes = nsSupport.getDeclaredPrefixes(); 1253 while (prefixes.hasMoreElements()) { 1254 String prefix = (String) prefixes.nextElement(); 1255 String uri = nsSupport.getURI(prefix); 1256 if (uri == null) { 1257 uri = ""; 1258 } 1259 char ch[] = uri.toCharArray(); 1260 write(' '); 1261 if ("".equals(prefix)) { 1262 write("xmlns=\""); 1263 } else { 1264 write("xmlns:"); 1265 write(prefix); 1266 write("=\""); 1267 } 1268 writeEsc(ch, 0, ch.length, true); 1269 write('\"'); 1270 } 1271 } 1272 1273 1274 /** 1275 * Write an element or attribute name. 1276 * 1277 * @param uri The Namespace URI. 1278 * @param localName The local name. 1279 * @param qName The prefixed name, if available, or the empty string. 1280 * @param isElement true if this is an element name, false if it 1281 * is an attribute name. 1282 * @exception org.xml.sax.SAXException This method will throw an 1283 * IOException wrapped in a SAXException if there is 1284 * an error writing the name. 1285 */ writeName(String uri, String localName, String qName, boolean isElement)1286 private void writeName (String uri, String localName, 1287 String qName, boolean isElement) 1288 throws SAXException 1289 { 1290 String prefix = doPrefix(uri, qName, isElement); 1291 if (prefix != null && !"".equals(prefix)) { 1292 write(prefix); 1293 write(':'); 1294 } 1295 if (localName != null && !"".equals(localName)) { 1296 write(localName); 1297 } else { 1298 int i = qName.indexOf(':'); 1299 write(qName.substring(i + 1, qName.length())); 1300 } 1301 } 1302 1303 1304 1305 //////////////////////////////////////////////////////////////////// 1306 // Default LexicalHandler implementation 1307 //////////////////////////////////////////////////////////////////// 1308 comment(char[] ch, int start, int length)1309 public void comment(char[] ch, int start, int length) throws SAXException 1310 { 1311 write("<!--"); 1312 for (int i = start; i < start + length; i++) { 1313 write(ch[i]); 1314 if (ch[i] == '-' && i + 1 <= start + length && ch[i+1] == '-') 1315 write(' '); 1316 } 1317 write("-->"); 1318 } 1319 endCDATA()1320 public void endCDATA() throws SAXException { } endDTD()1321 public void endDTD() throws SAXException { } endEntity(String name)1322 public void endEntity(String name) throws SAXException { } startCDATA()1323 public void startCDATA() throws SAXException { } startDTD(String name, String publicid, String systemid)1324 public void startDTD(String name, String publicid, String systemid) throws SAXException { 1325 if (name == null) return; // can't cope 1326 if (hasOutputDTD) return; // only one DTD 1327 hasOutputDTD = true; 1328 write("<!DOCTYPE "); 1329 write(name); 1330 if (systemid == null) systemid = ""; 1331 if (overrideSystem != null) systemid = overrideSystem; 1332 char sysquote = (systemid.indexOf('"') != -1) ? '\'': '"'; 1333 if (overridePublic != null) publicid = overridePublic; 1334 if (!(publicid == null || "".equals(publicid))) { 1335 char pubquote = (publicid.indexOf('"') != -1) ? '\'': '"'; 1336 write(" PUBLIC "); 1337 write(pubquote); 1338 write(publicid); 1339 write(pubquote); 1340 write(' '); 1341 } 1342 else { 1343 write(" SYSTEM "); 1344 } 1345 write(sysquote); 1346 write(systemid); 1347 write(sysquote); 1348 write(">\n"); 1349 } 1350 startEntity(String name)1351 public void startEntity(String name) throws SAXException { } 1352 1353 1354 //////////////////////////////////////////////////////////////////// 1355 // Output properties 1356 //////////////////////////////////////////////////////////////////// 1357 getOutputProperty(String key)1358 public String getOutputProperty(String key) { 1359 return outputProperties.getProperty(key); 1360 } 1361 setOutputProperty(String key, String value)1362 public void setOutputProperty(String key, String value) { 1363 outputProperties.setProperty(key, value); 1364 // System.out.println("%%%% key = [" + key + "] value = [" + value +"]"); 1365 if (key.equals(ENCODING)) { 1366 outputEncoding = value; 1367 unicodeMode = value.substring(0, 3).equalsIgnoreCase("utf"); 1368 // System.out.println("%%%% unicodeMode = " + unicodeMode); 1369 } 1370 else if (key.equals(METHOD)) { 1371 htmlMode = value.equals("html"); 1372 } 1373 else if (key.equals(DOCTYPE_PUBLIC)) { 1374 overridePublic = value; 1375 forceDTD = true; 1376 } 1377 else if (key.equals(DOCTYPE_SYSTEM)) { 1378 overrideSystem = value; 1379 forceDTD = true; 1380 } 1381 else if (key.equals(VERSION)) { 1382 version = value; 1383 } 1384 else if (key.equals(STANDALONE)) { 1385 standalone = value; 1386 } 1387 // System.out.println("%%%% htmlMode = " + htmlMode); 1388 } 1389 1390 1391 //////////////////////////////////////////////////////////////////// 1392 // Constants. 1393 //////////////////////////////////////////////////////////////////// 1394 1395 private final Attributes EMPTY_ATTS = new AttributesImpl(); 1396 public static final String CDATA_SECTION_ELEMENTS = 1397 "cdata-section-elements"; 1398 public static final String DOCTYPE_PUBLIC = "doctype-public"; 1399 public static final String DOCTYPE_SYSTEM = "doctype-system"; 1400 public static final String ENCODING = "encoding"; 1401 public static final String INDENT = "indent"; // currently ignored 1402 public static final String MEDIA_TYPE = "media-type"; // currently ignored 1403 public static final String METHOD = "method"; // currently html or xml 1404 public static final String OMIT_XML_DECLARATION = "omit-xml-declaration"; 1405 public static final String STANDALONE = "standalone"; // currently ignored 1406 public static final String VERSION = "version"; 1407 1408 1409 1410 //////////////////////////////////////////////////////////////////// 1411 // Internal state. 1412 //////////////////////////////////////////////////////////////////// 1413 1414 private Hashtable prefixTable; 1415 private Hashtable forcedDeclTable; 1416 private Hashtable doneDeclTable; 1417 private int elementLevel = 0; 1418 private Writer output; 1419 private NamespaceSupport nsSupport; 1420 private int prefixCounter = 0; 1421 private Properties outputProperties; 1422 private boolean unicodeMode = false; 1423 private String outputEncoding = ""; 1424 private boolean htmlMode = false; 1425 private boolean forceDTD = false; 1426 private boolean hasOutputDTD = false; 1427 private String overridePublic = null; 1428 private String overrideSystem = null; 1429 private String version = null; 1430 private String standalone = null; 1431 private boolean cdataElement = false; 1432 1433 } 1434 1435 // end of XMLWriter.java 1436