1 // XMLWriter.java - serialize an XML document. 2 // Written by David Megginson, david (at) 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 (at) 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 //////////////////////////////////////////////////////////////////// 258 // Constructors. 259 //////////////////////////////////////////////////////////////////// 260 261 262 /** 263 * Create a new XML writer. 264 * 265 * <p>Write to standard output.</p> 266 */ 267 public XMLWriter () 268 { 269 init(null); 270 } 271 272 273 /** 274 * Create a new XML writer. 275 * 276 * <p>Write to the writer provided.</p> 277 * 278 * @param writer The output destination, or null to use standard 279 * output. 280 */ 281 public XMLWriter (Writer writer) 282 { 283 init(writer); 284 } 285 286 287 /** 288 * Create a new XML writer. 289 * 290 * <p>Use the specified XML reader as the parent.</p> 291 * 292 * @param xmlreader The parent in the filter chain, or null 293 * for no parent. 294 */ 295 public XMLWriter (XMLReader xmlreader) 296 { 297 super(xmlreader); 298 init(null); 299 } 300 301 302 /** 303 * Create a new XML writer. 304 * 305 * <p>Use the specified XML reader as the parent, and write 306 * to the specified writer.</p> 307 * 308 * @param xmlreader The parent in the filter chain, or null 309 * for no parent. 310 * @param writer The output destination, or null to use standard 311 * output. 312 */ 313 public XMLWriter (XMLReader xmlreader, Writer writer) 314 { 315 super(xmlreader); 316 init(writer); 317 } 318 319 320 /** 321 * Internal initialization method. 322 * 323 * <p>All of the public constructors invoke this method. 324 * 325 * @param writer The output destination, or null to use 326 * standard output. 327 */ 328 private void init (Writer writer) 329 { 330 setOutput(writer); 331 nsSupport = new NamespaceSupport(); 332 prefixTable = new Hashtable(); 333 forcedDeclTable = new Hashtable(); 334 doneDeclTable = new Hashtable(); 335 outputProperties = new Properties(); 336 } 337 338 339 340 //////////////////////////////////////////////////////////////////// 342 // Public methods. 343 //////////////////////////////////////////////////////////////////// 344 345 346 /** 347 * Reset the writer. 348 * 349 * <p>This method is especially useful if the writer throws an 350 * exception before it is finished, and you want to reuse the 351 * writer for a new document. It is usually a good idea to 352 * invoke {@link #flush flush} before resetting the writer, 353 * to make sure that no output is lost.</p> 354 * 355 * <p>This method is invoked automatically by the 356 * {@link #startDocument startDocument} method before writing 357 * a new document.</p> 358 * 359 * <p><strong>Note:</strong> this method will <em>not</em> 360 * clear the prefix or URI information in the writer or 361 * the selected output writer.</p> 362 * 363 * @see #flush 364 */ 365 public void reset () 366 { 367 elementLevel = 0; 368 prefixCounter = 0; 369 nsSupport.reset(); 370 } 371 372 373 /** 374 * Flush the output. 375 * 376 * <p>This method flushes the output stream. It is especially useful 377 * when you need to make certain that the entire document has 378 * been written to output but do not want to close the output 379 * stream.</p> 380 * 381 * <p>This method is invoked automatically by the 382 * {@link #endDocument endDocument} method after writing a 383 * document.</p> 384 * 385 * @see #reset 386 */ 387 public void flush () 388 throws IOException 389 { 390 output.flush(); 391 } 392 393 394 /** 395 * Set a new output destination for the document. 396 * 397 * @param writer The output destination, or null to use 398 * standard output. 399 * @return The current output writer. 400 * @see #flush 401 */ 402 public void setOutput (Writer writer) 403 { 404 if (writer == null) { 405 output = new OutputStreamWriter(System.out); 406 } else { 407 output = writer; 408 } 409 } 410 411 412 /** 413 * Specify a preferred prefix for a Namespace URI. 414 * 415 * <p>Note that this method does not actually force the Namespace 416 * to be declared; to do that, use the {@link 417 * #forceNSDecl(java.lang.String) forceNSDecl} method as well.</p> 418 * 419 * @param uri The Namespace URI. 420 * @param prefix The preferred prefix, or "" to select 421 * the default Namespace. 422 * @see #getPrefix 423 * @see #forceNSDecl(java.lang.String) 424 * @see #forceNSDecl(java.lang.String,java.lang.String) 425 */ 426 public void setPrefix (String uri, String prefix) 427 { 428 prefixTable.put(uri, prefix); 429 } 430 431 432 /** 433 * Get the current or preferred prefix for a Namespace URI. 434 * 435 * @param uri The Namespace URI. 436 * @return The preferred prefix, or "" for the default Namespace. 437 * @see #setPrefix 438 */ 439 public String getPrefix (String uri) 440 { 441 return (String)prefixTable.get(uri); 442 } 443 444 445 /** 446 * Force a Namespace to be declared on the root element. 447 * 448 * <p>By default, the XMLWriter will declare only the Namespaces 449 * needed for an element; as a result, a Namespace may be 450 * declared many places in a document if it is not used on the 451 * root element.</p> 452 * 453 * <p>This method forces a Namespace to be declared on the root 454 * element even if it is not used there, and reduces the number 455 * of xmlns attributes in the document.</p> 456 * 457 * @param uri The Namespace URI to declare. 458 * @see #forceNSDecl(java.lang.String,java.lang.String) 459 * @see #setPrefix 460 */ 461 public void forceNSDecl (String uri) 462 { 463 forcedDeclTable.put(uri, Boolean.TRUE); 464 } 465 466 467 /** 468 * Force a Namespace declaration with a preferred prefix. 469 * 470 * <p>This is a convenience method that invokes {@link 471 * #setPrefix setPrefix} then {@link #forceNSDecl(java.lang.String) 472 * forceNSDecl}.</p> 473 * 474 * @param uri The Namespace URI to declare on the root element. 475 * @param prefix The preferred prefix for the Namespace, or "" 476 * for the default Namespace. 477 * @see #setPrefix 478 * @see #forceNSDecl(java.lang.String) 479 */ 480 public void forceNSDecl (String uri, String prefix) 481 { 482 setPrefix(uri, prefix); 483 forceNSDecl(uri); 484 } 485 486 487 488 //////////////////////////////////////////////////////////////////// 490 // Methods from org.xml.sax.ContentHandler. 491 //////////////////////////////////////////////////////////////////// 492 493 494 /** 495 * Write the XML declaration at the beginning of the document. 496 * 497 * Pass the event on down the filter chain for further processing. 498 * 499 * @exception org.xml.sax.SAXException If there is an error 500 * writing the XML declaration, or if a handler further down 501 * the filter chain raises an exception. 502 * @see org.xml.sax.ContentHandler#startDocument 503 */ 504 public void startDocument () 505 throws SAXException 506 { 507 reset(); 508 if (!("yes".equals(outputProperties.getProperty(OMIT_XML_DECLARATION, "no")))) { 509 write("<?xml"); 510 if (version == null) { 511 write(" version=\"1.0\""); 512 } else { 513 write(" version=\""); 514 write(version); 515 write("\""); 516 } 517 if (outputEncoding != null && outputEncoding != "") { 518 write(" encoding=\""); 519 write(outputEncoding); 520 write("\""); 521 } 522 if (standalone == null) { 523 write(" standalone=\"yes\"?>\n"); 524 } else { 525 write(" standalone=\""); 526 write(standalone); 527 write("\""); 528 } 529 } 530 super.startDocument(); 531 } 532 533 534 /** 535 * Write a newline at the end of the document. 536 * 537 * Pass the event on down the filter chain for further processing. 538 * 539 * @exception org.xml.sax.SAXException If there is an error 540 * writing the newline, or if a handler further down 541 * the filter chain raises an exception. 542 * @see org.xml.sax.ContentHandler#endDocument 543 */ 544 public void endDocument () 545 throws SAXException 546 { 547 write('\n'); 548 super.endDocument(); 549 try { 550 flush(); 551 } catch (IOException e) { 552 throw new SAXException(e); 553 } 554 } 555 556 557 /** 558 * Write a start tag. 559 * 560 * Pass the event on down the filter chain for further processing. 561 * 562 * @param uri The Namespace URI, or the empty string if none 563 * is available. 564 * @param localName The element's local (unprefixed) name (required). 565 * @param qName The element's qualified (prefixed) name, or the 566 * empty string is none is available. This method will 567 * use the qName as a template for generating a prefix 568 * if necessary, but it is not guaranteed to use the 569 * same qName. 570 * @param atts The element's attribute list (must not be null). 571 * @exception org.xml.sax.SAXException If there is an error 572 * writing the start tag, or if a handler further down 573 * the filter chain raises an exception. 574 * @see org.xml.sax.ContentHandler#startElement 575 */ 576 public void startElement (String uri, String localName, 577 String qName, Attributes atts) 578 throws SAXException 579 { 580 elementLevel++; 581 nsSupport.pushContext(); 582 if (forceDTD && !hasOutputDTD) startDTD(localName == null ? qName : localName, "", ""); 583 write('<'); 584 writeName(uri, localName, qName, true); 585 writeAttributes(atts); 586 if (elementLevel == 1) { 587 forceNSDecls(); 588 } 589 writeNSDecls(); 590 write('>'); 591 // System.out.println("%%%% startElement [" + qName + "] htmlMode = " + htmlMode); 592 if (htmlMode && (qName.equals("script") || qName.equals("style"))) { 593 cdataElement = true; 594 // System.out.println("%%%% CDATA element"); 595 } 596 super.startElement(uri, localName, qName, atts); 597 } 598 599 600 /** 601 * Write an end tag. 602 * 603 * Pass the event on down the filter chain for further processing. 604 * 605 * @param uri The Namespace URI, or the empty string if none 606 * is available. 607 * @param localName The element's local (unprefixed) name (required). 608 * @param qName The element's qualified (prefixed) name, or the 609 * empty string is none is available. This method will 610 * use the qName as a template for generating a prefix 611 * if necessary, but it is not guaranteed to use the 612 * same qName. 613 * @exception org.xml.sax.SAXException If there is an error 614 * writing the end tag, or if a handler further down 615 * the filter chain raises an exception. 616 * @see org.xml.sax.ContentHandler#endElement 617 */ 618 public void endElement (String uri, String localName, String qName) 619 throws SAXException 620 { 621 if (!(htmlMode && 622 (uri.equals("http://www.w3.org/1999/xhtml") || 623 uri.equals("")) && 624 (qName.equals("area") || qName.equals("base") || 625 qName.equals("basefont") || qName.equals("br") || 626 qName.equals("col") || qName.equals("frame") || 627 qName.equals("hr") || qName.equals("img") || 628 qName.equals("input") || qName.equals("isindex") || 629 qName.equals("link") || qName.equals("meta") || 630 qName.equals("param")))) { 631 write("</"); 632 writeName(uri, localName, qName, true); 633 write('>'); 634 } 635 if (elementLevel == 1) { 636 write('\n'); 637 } 638 cdataElement = false; 639 super.endElement(uri, localName, qName); 640 nsSupport.popContext(); 641 elementLevel--; 642 } 643 644 645 /** 646 * Write character data. 647 * 648 * Pass the event on down the filter chain for further processing. 649 * 650 * @param ch The array of characters to write. 651 * @param start The starting position in the array. 652 * @param length The number of characters to write. 653 * @exception org.xml.sax.SAXException If there is an error 654 * writing the characters, or if a handler further down 655 * the filter chain raises an exception. 656 * @see org.xml.sax.ContentHandler#characters 657 */ 658 public void characters (char ch[], int start, int len) 659 throws SAXException 660 { 661 if (!cdataElement) { 662 writeEsc(ch, start, len, false); 663 } 664 else { 665 for (int i = start; i < start + len; i++) { 666 write(ch[i]); 667 } 668 } 669 super.characters(ch, start, len); 670 } 671 672 673 /** 674 * Write ignorable whitespace. 675 * 676 * Pass the event on down the filter chain for further processing. 677 * 678 * @param ch The array of characters to write. 679 * @param start The starting position in the array. 680 * @param length The number of characters to write. 681 * @exception org.xml.sax.SAXException If there is an error 682 * writing the whitespace, or if a handler further down 683 * the filter chain raises an exception. 684 * @see org.xml.sax.ContentHandler#ignorableWhitespace 685 */ 686 public void ignorableWhitespace (char ch[], int start, int length) 687 throws SAXException 688 { 689 writeEsc(ch, start, length, false); 690 super.ignorableWhitespace(ch, start, length); 691 } 692 693 694 695 /** 696 * Write a processing instruction. 697 * 698 * Pass the event on down the filter chain for further processing. 699 * 700 * @param target The PI target. 701 * @param data The PI data. 702 * @exception org.xml.sax.SAXException If there is an error 703 * writing the PI, or if a handler further down 704 * the filter chain raises an exception. 705 * @see org.xml.sax.ContentHandler#processingInstruction 706 */ 707 public void processingInstruction (String target, String data) 708 throws SAXException 709 { 710 write("<?"); 711 write(target); 712 write(' '); 713 write(data); 714 write("?>"); 715 if (elementLevel < 1) { 716 write('\n'); 717 } 718 super.processingInstruction(target, data); 719 } 720 721 722 723 //////////////////////////////////////////////////////////////////// 725 // Additional markup. 726 //////////////////////////////////////////////////////////////////// 727 728 /** 729 * Write an empty element. 730 * 731 * This method writes an empty element tag rather than a start tag 732 * followed by an end tag. Both a {@link #startElement 733 * startElement} and an {@link #endElement endElement} event will 734 * be passed on down the filter chain. 735 * 736 * @param uri The element's Namespace URI, or the empty string 737 * if the element has no Namespace or if Namespace 738 * processing is not being performed. 739 * @param localName The element's local name (without prefix). This 740 * parameter must be provided. 741 * @param qName The element's qualified name (with prefix), or 742 * the empty string if none is available. This parameter 743 * is strictly advisory: the writer may or may not use 744 * the prefix attached. 745 * @param atts The element's attribute list. 746 * @exception org.xml.sax.SAXException If there is an error 747 * writing the empty tag, or if a handler further down 748 * the filter chain raises an exception. 749 * @see #startElement 750 * @see #endElement 751 */ 752 public void emptyElement (String uri, String localName, 753 String qName, Attributes atts) 754 throws SAXException 755 { 756 nsSupport.pushContext(); 757 write('<'); 758 writeName(uri, localName, qName, true); 759 writeAttributes(atts); 760 if (elementLevel == 1) { 761 forceNSDecls(); 762 } 763 writeNSDecls(); 764 write("/>"); 765 super.startElement(uri, localName, qName, atts); 766 super.endElement(uri, localName, qName); 767 } 768 769 770 771 //////////////////////////////////////////////////////////////////// 773 // Convenience methods. 774 //////////////////////////////////////////////////////////////////// 775 776 777 778 /** 779 * Start a new element without a qname or attributes. 780 * 781 * <p>This method will provide a default empty attribute 782 * list and an empty string for the qualified name. 783 * It invokes {@link 784 * #startElement(String, String, String, Attributes)} 785 * directly.</p> 786 * 787 * @param uri The element's Namespace URI. 788 * @param localName The element's local name. 789 * @exception org.xml.sax.SAXException If there is an error 790 * writing the start tag, or if a handler further down 791 * the filter chain raises an exception. 792 * @see #startElement(String, String, String, Attributes) 793 */ 794 public void startElement (String uri, String localName) 795 throws SAXException 796 { 797 startElement(uri, localName, "", EMPTY_ATTS); 798 } 799 800 801 /** 802 * Start a new element without a qname, attributes or a Namespace URI. 803 * 804 * <p>This method will provide an empty string for the 805 * Namespace URI, and empty string for the qualified name, 806 * and a default empty attribute list. It invokes 807 * #startElement(String, String, String, Attributes)} 808 * directly.</p> 809 * 810 * @param localName The element's local name. 811 * @exception org.xml.sax.SAXException If there is an error 812 * writing the start tag, or if a handler further down 813 * the filter chain raises an exception. 814 * @see #startElement(String, String, String, Attributes) 815 */ 816 public void startElement (String localName) 817 throws SAXException 818 { 819 startElement("", localName, "", EMPTY_ATTS); 820 } 821 822 823 /** 824 * End an element without a qname. 825 * 826 * <p>This method will supply an empty string for the qName. 827 * It invokes {@link #endElement(String, String, String)} 828 * directly.</p> 829 * 830 * @param uri The element's Namespace URI. 831 * @param localName The element's local name. 832 * @exception org.xml.sax.SAXException If there is an error 833 * writing the end tag, or if a handler further down 834 * the filter chain raises an exception. 835 * @see #endElement(String, String, String) 836 */ 837 public void endElement (String uri, String localName) 838 throws SAXException 839 { 840 endElement(uri, localName, ""); 841 } 842 843 844 /** 845 * End an element without a Namespace URI or qname. 846 * 847 * <p>This method will supply an empty string for the qName 848 * and an empty string for the Namespace URI. 849 * It invokes {@link #endElement(String, String, String)} 850 * directly.</p> 851 * 852 * @param localName The element's local name. 853 * @exception org.xml.sax.SAXException If there is an error 854 * writing the end tag, or if a handler further down 855 * the filter chain raises an exception. 856 * @see #endElement(String, String, String) 857 */ 858 public void endElement (String localName) 859 throws SAXException 860 { 861 endElement("", localName, ""); 862 } 863 864 865 /** 866 * Add an empty element without a qname or attributes. 867 * 868 * <p>This method will supply an empty string for the qname 869 * and an empty attribute list. It invokes 870 * {@link #emptyElement(String, String, String, Attributes)} 871 * directly.</p> 872 * 873 * @param uri The element's Namespace URI. 874 * @param localName The element's local name. 875 * @exception org.xml.sax.SAXException If there is an error 876 * writing the empty tag, or if a handler further down 877 * the filter chain raises an exception. 878 * @see #emptyElement(String, String, String, Attributes) 879 */ 880 public void emptyElement (String uri, String localName) 881 throws SAXException 882 { 883 emptyElement(uri, localName, "", EMPTY_ATTS); 884 } 885 886 887 /** 888 * Add an empty element without a Namespace URI, qname or attributes. 889 * 890 * <p>This method will supply an empty string for the qname, 891 * and empty string for the Namespace URI, and an empty 892 * attribute list. It invokes 893 * {@link #emptyElement(String, String, String, Attributes)} 894 * directly.</p> 895 * 896 * @param localName The element's local name. 897 * @exception org.xml.sax.SAXException If there is an error 898 * writing the empty tag, or if a handler further down 899 * the filter chain raises an exception. 900 * @see #emptyElement(String, String, String, Attributes) 901 */ 902 public void emptyElement (String localName) 903 throws SAXException 904 { 905 emptyElement("", localName, "", EMPTY_ATTS); 906 } 907 908 909 /** 910 * Write an element with character data content. 911 * 912 * <p>This is a convenience method to write a complete element 913 * with character data content, including the start tag 914 * and end tag.</p> 915 * 916 * <p>This method invokes 917 * {@link #startElement(String, String, String, Attributes)}, 918 * followed by 919 * {@link #characters(String)}, followed by 920 * {@link #endElement(String, String, String)}.</p> 921 * 922 * @param uri The element's Namespace URI. 923 * @param localName The element's local name. 924 * @param qName The element's default qualified name. 925 * @param atts The element's attributes. 926 * @param content The character data content. 927 * @exception org.xml.sax.SAXException If there is an error 928 * writing the empty tag, or if a handler further down 929 * the filter chain raises an exception. 930 * @see #startElement(String, String, String, Attributes) 931 * @see #characters(String) 932 * @see #endElement(String, String, String) 933 */ 934 public void dataElement (String uri, String localName, 935 String qName, Attributes atts, 936 String content) 937 throws SAXException 938 { 939 startElement(uri, localName, qName, atts); 940 characters(content); 941 endElement(uri, localName, qName); 942 } 943 944 945 /** 946 * Write an element with character data content but no attributes. 947 * 948 * <p>This is a convenience method to write a complete element 949 * with character data content, including the start tag 950 * and end tag. This method provides an empty string 951 * for the qname and an empty attribute list.</p> 952 * 953 * <p>This method invokes 954 * {@link #startElement(String, String, String, Attributes)}, 955 * followed by 956 * {@link #characters(String)}, followed by 957 * {@link #endElement(String, String, String)}.</p> 958 * 959 * @param uri The element's Namespace URI. 960 * @param localName The element's local name. 961 * @param content The character data content. 962 * @exception org.xml.sax.SAXException If there is an error 963 * writing the empty tag, or if a handler further down 964 * the filter chain raises an exception. 965 * @see #startElement(String, String, String, Attributes) 966 * @see #characters(String) 967 * @see #endElement(String, String, String) 968 */ 969 public void dataElement (String uri, String localName, String content) 970 throws SAXException 971 { 972 dataElement(uri, localName, "", EMPTY_ATTS, content); 973 } 974 975 976 /** 977 * Write an element with character data content but no attributes or Namespace URI. 978 * 979 * <p>This is a convenience method to write a complete element 980 * with character data content, including the start tag 981 * and end tag. The method provides an empty string for the 982 * Namespace URI, and empty string for the qualified name, 983 * and an empty attribute list.</p> 984 * 985 * <p>This method invokes 986 * {@link #startElement(String, String, String, Attributes)}, 987 * followed by 988 * {@link #characters(String)}, followed by 989 * {@link #endElement(String, String, String)}.</p> 990 * 991 * @param localName The element's local name. 992 * @param content The character data content. 993 * @exception org.xml.sax.SAXException If there is an error 994 * writing the empty tag, or if a handler further down 995 * the filter chain raises an exception. 996 * @see #startElement(String, String, String, Attributes) 997 * @see #characters(String) 998 * @see #endElement(String, String, String) 999 */ 1000 public void dataElement (String localName, String content) 1001 throws SAXException 1002 { 1003 dataElement("", localName, "", EMPTY_ATTS, content); 1004 } 1005 1006 1007 /** 1008 * Write a string of character data, with XML escaping. 1009 * 1010 * <p>This is a convenience method that takes an XML 1011 * String, converts it to a character array, then invokes 1012 * {@link #characters(char[], int, int)}.</p> 1013 * 1014 * @param data The character data. 1015 * @exception org.xml.sax.SAXException If there is an error 1016 * writing the string, or if a handler further down 1017 * the filter chain raises an exception. 1018 * @see #characters(char[], int, int) 1019 */ 1020 public void characters (String data) 1021 throws SAXException 1022 { 1023 char ch[] = data.toCharArray(); 1024 characters(ch, 0, ch.length); 1025 } 1026 1027 1028 1029 //////////////////////////////////////////////////////////////////// 1031 // Internal methods. 1032 //////////////////////////////////////////////////////////////////// 1033 1034 1035 /** 1036 * Force all Namespaces to be declared. 1037 * 1038 * This method is used on the root element to ensure that 1039 * the predeclared Namespaces all appear. 1040 */ 1041 private void forceNSDecls () 1042 { 1043 Enumeration prefixes = forcedDeclTable.keys(); 1044 while (prefixes.hasMoreElements()) { 1045 String prefix = (String)prefixes.nextElement(); 1046 doPrefix(prefix, null, true); 1047 } 1048 } 1049 1050 1051 /** 1052 * Determine the prefix for an element or attribute name. 1053 * 1054 * TODO: this method probably needs some cleanup. 1055 * 1056 * @param uri The Namespace URI. 1057 * @param qName The qualified name (optional); this will be used 1058 * to indicate the preferred prefix if none is currently 1059 * bound. 1060 * @param isElement true if this is an element name, false 1061 * if it is an attribute name (which cannot use the 1062 * default Namespace). 1063 */ 1064 private String doPrefix (String uri, String qName, boolean isElement) 1065 { 1066 String defaultNS = nsSupport.getURI(""); 1067 if ("".equals(uri)) { 1068 if (isElement && defaultNS != null) 1069 nsSupport.declarePrefix("", ""); 1070 return null; 1071 } 1072 String prefix; 1073 if (isElement && defaultNS != null && uri.equals(defaultNS)) { 1074 prefix = ""; 1075 } else { 1076 prefix = nsSupport.getPrefix(uri); 1077 } 1078 if (prefix != null) { 1079 return prefix; 1080 } 1081 prefix = (String) doneDeclTable.get(uri); 1082 if (prefix != null && 1083 ((!isElement || defaultNS != null) && 1084 "".equals(prefix) || nsSupport.getURI(prefix) != null)) { 1085 prefix = null; 1086 } 1087 if (prefix == null) { 1088 prefix = (String) prefixTable.get(uri); 1089 if (prefix != null && 1090 ((!isElement || defaultNS != null) && 1091 "".equals(prefix) || nsSupport.getURI(prefix) != null)) { 1092 prefix = null; 1093 } 1094 } 1095 if (prefix == null && qName != null && !"".equals(qName)) { 1096 int i = qName.indexOf(':'); 1097 if (i == -1) { 1098 if (isElement && defaultNS == null) { 1099 prefix = ""; 1100 } 1101 } else { 1102 prefix = qName.substring(0, i); 1103 } 1104 } 1105 for (; 1106 prefix == null || nsSupport.getURI(prefix) != null; 1107 prefix = "__NS" + ++prefixCounter) 1108 ; 1109 nsSupport.declarePrefix(prefix, uri); 1110 doneDeclTable.put(uri, prefix); 1111 return prefix; 1112 } 1113 1114 1115 /** 1116 * Write a raw character. 1117 * 1118 * @param c The character to write. 1119 * @exception org.xml.sax.SAXException If there is an error writing 1120 * the character, this method will throw an IOException 1121 * wrapped in a SAXException. 1122 */ 1123 private void write (char c) 1124 throws SAXException 1125 { 1126 try { 1127 output.write(c); 1128 } catch (IOException e) { 1129 throw new SAXException(e); 1130 } 1131 } 1132 1133 1134 /** 1135 * Write a raw string. 1136 * 1137 * @param s 1138 * @exception org.xml.sax.SAXException If there is an error writing 1139 * the string, this method will throw an IOException 1140 * wrapped in a SAXException 1141 */ 1142 private void write (String s) 1143 throws SAXException 1144 { 1145 try { 1146 output.write(s); 1147 } catch (IOException e) { 1148 throw new SAXException(e); 1149 } 1150 } 1151 1152 1153 /** 1154 * Write out an attribute list, escaping values. 1155 * 1156 * The names will have prefixes added to them. 1157 * 1158 * @param atts The attribute list to write. 1159 * @exception org.xml.SAXException If there is an error writing 1160 * the attribute list, this method will throw an 1161 * IOException wrapped in a SAXException. 1162 */ 1163 private void writeAttributes (Attributes atts) 1164 throws SAXException 1165 { 1166 int len = atts.getLength(); 1167 for (int i = 0; i < len; i++) { 1168 char ch[] = atts.getValue(i).toCharArray(); 1169 write(' '); 1170 writeName(atts.getURI(i), atts.getLocalName(i), 1171 atts.getQName(i), false); 1172 if (htmlMode && 1173 booleanAttribute(atts.getLocalName(i), atts.getQName(i), atts.getValue(i))) break; 1174 write("=\""); 1175 writeEsc(ch, 0, ch.length, true); 1176 write('"'); 1177 } 1178 } 1179 1180 1181 private String[] booleans = {"checked", "compact", "declare", "defer", 1182 "disabled", "ismap", "multiple", 1183 "nohref", "noresize", "noshade", 1184 "nowrap", "readonly", "selected"}; 1185 1186 // Return true if the attribute is an HTML boolean from the above list. 1187 private boolean booleanAttribute (String localName, String qName, String value) 1188 { 1189 String name = localName; 1190 if (name == null) { 1191 int i = qName.indexOf(':'); 1192 if (i != -1) name = qName.substring(i + 1, qName.length()); 1193 } 1194 if (!name.equals(value)) return false; 1195 for (int j = 0; j < booleans.length; j++) { 1196 if (name.equals(booleans[j])) return true; 1197 } 1198 return false; 1199 } 1200 1201 /** 1202 * Write an array of data characters with escaping. 1203 * 1204 * @param ch The array of characters. 1205 * @param start The starting position. 1206 * @param length The number of characters to use. 1207 * @param isAttVal true if this is an attribute value literal. 1208 * @exception org.xml.SAXException If there is an error writing 1209 * the characters, this method will throw an 1210 * IOException wrapped in a SAXException. 1211 */ 1212 private void writeEsc (char ch[], int start, 1213 int length, boolean isAttVal) 1214 throws SAXException 1215 { 1216 for (int i = start; i < start + length; i++) { 1217 switch (ch[i]) { 1218 case '&': 1219 write("&"); 1220 break; 1221 case '<': 1222 write("<"); 1223 break; 1224 case '>': 1225 write(">"); 1226 break; 1227 case '\"': 1228 if (isAttVal) { 1229 write("""); 1230 } else { 1231 write('\"'); 1232 } 1233 break; 1234 default: 1235 if (!unicodeMode && ch[i] > '\u007f') { 1236 write("&#"); 1237 write(Integer.toString(ch[i])); 1238 write(';'); 1239 } else { 1240 write(ch[i]); 1241 } 1242 } 1243 } 1244 } 1245 1246 1247 /** 1248 * Write out the list of Namespace declarations. 1249 * 1250 * @exception org.xml.sax.SAXException This method will throw 1251 * an IOException wrapped in a SAXException if 1252 * there is an error writing the Namespace 1253 * declarations. 1254 */ 1255 private void writeNSDecls () 1256 throws SAXException 1257 { 1258 Enumeration prefixes = nsSupport.getDeclaredPrefixes(); 1259 while (prefixes.hasMoreElements()) { 1260 String prefix = (String) prefixes.nextElement(); 1261 String uri = nsSupport.getURI(prefix); 1262 if (uri == null) { 1263 uri = ""; 1264 } 1265 char ch[] = uri.toCharArray(); 1266 write(' '); 1267 if ("".equals(prefix)) { 1268 write("xmlns=\""); 1269 } else { 1270 write("xmlns:"); 1271 write(prefix); 1272 write("=\""); 1273 } 1274 writeEsc(ch, 0, ch.length, true); 1275 write('\"'); 1276 } 1277 } 1278 1279 1280 /** 1281 * Write an element or attribute name. 1282 * 1283 * @param uri The Namespace URI. 1284 * @param localName The local name. 1285 * @param qName The prefixed name, if available, or the empty string. 1286 * @param isElement true if this is an element name, false if it 1287 * is an attribute name. 1288 * @exception org.xml.sax.SAXException This method will throw an 1289 * IOException wrapped in a SAXException if there is 1290 * an error writing the name. 1291 */ 1292 private void writeName (String uri, String localName, 1293 String qName, boolean isElement) 1294 throws SAXException 1295 { 1296 String prefix = doPrefix(uri, qName, isElement); 1297 if (prefix != null && !"".equals(prefix)) { 1298 write(prefix); 1299 write(':'); 1300 } 1301 if (localName != null && !"".equals(localName)) { 1302 write(localName); 1303 } else { 1304 int i = qName.indexOf(':'); 1305 write(qName.substring(i + 1, qName.length())); 1306 } 1307 } 1308 1309 1310 1312 //////////////////////////////////////////////////////////////////// 1313 // Default LexicalHandler implementation 1314 //////////////////////////////////////////////////////////////////// 1315 1316 public void comment(char[] ch, int start, int length) throws SAXException 1317 { 1318 write("<!--"); 1319 for (int i = start; i < start + length; i++) { 1320 write(ch[i]); 1321 if (ch[i] == '-' && i + 1 <= start + length && ch[i+1] == '-') 1322 write(' '); 1323 } 1324 write("-->"); 1325 } 1326 1327 public void endCDATA() throws SAXException { } 1328 public void endDTD() throws SAXException { } 1329 public void endEntity(String name) throws SAXException { } 1330 public void startCDATA() throws SAXException { } 1331 public void startDTD(String name, String publicid, String systemid) throws SAXException { 1332 if (name == null) return; // can't cope 1333 if (hasOutputDTD) return; // only one DTD 1334 hasOutputDTD = true; 1335 write("<!DOCTYPE "); 1336 write(name); 1337 if (systemid == null) systemid = ""; 1338 if (overrideSystem != null) systemid = overrideSystem; 1339 char sysquote = (systemid.indexOf('"') != -1) ? '\'': '"'; 1340 if (overridePublic != null) publicid = overridePublic; 1341 if (!(publicid == null || "".equals(publicid))) { 1342 char pubquote = (publicid.indexOf('"') != -1) ? '\'': '"'; 1343 write(" PUBLIC "); 1344 write(pubquote); 1345 write(publicid); 1346 write(pubquote); 1347 write(' '); 1348 } 1349 else { 1350 write(" SYSTEM "); 1351 } 1352 write(sysquote); 1353 write(systemid); 1354 write(sysquote); 1355 write(">\n"); 1356 } 1357 1358 public void startEntity(String name) throws SAXException { } 1359 1360 1361 //////////////////////////////////////////////////////////////////// 1362 // Output properties 1363 //////////////////////////////////////////////////////////////////// 1364 1365 public String getOutputProperty(String key) { 1366 return outputProperties.getProperty(key); 1367 } 1368 1369 public void setOutputProperty(String key, String value) { 1370 outputProperties.setProperty(key, value); 1371 // System.out.println("%%%% key = [" + key + "] value = [" + value +"]"); 1372 if (key.equals(ENCODING)) { 1373 outputEncoding = value; 1374 unicodeMode = value.substring(0, 3).equalsIgnoreCase("utf"); 1375 // System.out.println("%%%% unicodeMode = " + unicodeMode); 1376 } 1377 else if (key.equals(METHOD)) { 1378 htmlMode = value.equals("html"); 1379 } 1380 else if (key.equals(DOCTYPE_PUBLIC)) { 1381 overridePublic = value; 1382 forceDTD = true; 1383 } 1384 else if (key.equals(DOCTYPE_SYSTEM)) { 1385 overrideSystem = value; 1386 forceDTD = true; 1387 } 1388 else if (key.equals(VERSION)) { 1389 version = value; 1390 } 1391 else if (key.equals(STANDALONE)) { 1392 standalone = value; 1393 } 1394 // System.out.println("%%%% htmlMode = " + htmlMode); 1395 } 1396 1397 1398 //////////////////////////////////////////////////////////////////// 1400 // Constants. 1401 //////////////////////////////////////////////////////////////////// 1402 1403 private final Attributes EMPTY_ATTS = new AttributesImpl(); 1404 public static final String CDATA_SECTION_ELEMENTS = 1405 "cdata-section-elements"; 1406 public static final String DOCTYPE_PUBLIC = "doctype-public"; 1407 public static final String DOCTYPE_SYSTEM = "doctype-system"; 1408 public static final String ENCODING = "encoding"; 1409 public static final String INDENT = "indent"; // currently ignored 1410 public static final String MEDIA_TYPE = "media-type"; // currently ignored 1411 public static final String METHOD = "method"; // currently html or xml 1412 public static final String OMIT_XML_DECLARATION = "omit-xml-declaration"; 1413 public static final String STANDALONE = "standalone"; // currently ignored 1414 public static final String VERSION = "version"; 1415 1416 1417 1418 //////////////////////////////////////////////////////////////////// 1420 // Internal state. 1421 //////////////////////////////////////////////////////////////////// 1422 1423 private Hashtable prefixTable; 1424 private Hashtable forcedDeclTable; 1425 private Hashtable doneDeclTable; 1426 private int elementLevel = 0; 1427 private Writer output; 1428 private NamespaceSupport nsSupport; 1429 private int prefixCounter = 0; 1430 private Properties outputProperties; 1431 private boolean unicodeMode = false; 1432 private String outputEncoding = ""; 1433 private boolean htmlMode = false; 1434 private boolean forceDTD = false; 1435 private boolean hasOutputDTD = false; 1436 private String overridePublic = null; 1437 private String overrideSystem = null; 1438 private String version = null; 1439 private String standalone = null; 1440 private boolean cdataElement = false; 1441 1442 } 1443 1444 // end of XMLWriter.java 1445