001/* 002 * Copyright 2001 (C) MetaStuff, Ltd. All Rights Reserved. 003 * 004 * This software is open source. 005 * See the bottom of this file for the licence. 006 * 007 * $Id: XMLWriter.java,v 1.46 2002/02/14 11:55:46 jstrachan Exp $ 008 */ 009 010package org.dom4j.io; 011 012import java.io.BufferedOutputStream; 013import java.io.BufferedWriter; 014import java.io.ByteArrayOutputStream; 015import java.io.IOException; 016import java.io.OutputStream; 017import java.io.OutputStreamWriter; 018import java.io.StringWriter; 019import java.io.UnsupportedEncodingException; 020import java.io.Writer; 021import java.util.HashMap; 022import java.util.HashSet; 023import java.util.Iterator; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028import java.util.StringTokenizer; 029 030import org.dom4j.Attribute; 031import org.dom4j.CDATA; 032import org.dom4j.CharacterData; 033import org.dom4j.Comment; 034import org.dom4j.DocumentType; 035import org.dom4j.Document; 036import org.dom4j.Element; 037import org.dom4j.Entity; 038import org.dom4j.Namespace; 039import org.dom4j.Node; 040import org.dom4j.ProcessingInstruction; 041import org.dom4j.Text; 042 043import org.dom4j.tree.NamespaceStack; 044 045import org.xml.sax.Attributes; 046import org.xml.sax.ContentHandler; 047import org.xml.sax.DTDHandler; 048import org.xml.sax.InputSource; 049import org.xml.sax.Locator; 050import org.xml.sax.SAXException; 051import org.xml.sax.SAXNotRecognizedException; 052import org.xml.sax.SAXNotSupportedException; 053import org.xml.sax.XMLReader; 054import org.xml.sax.ext.LexicalHandler; 055import org.xml.sax.helpers.XMLFilterImpl; 056 057/**<p><code>XMLWriter</code> takes a DOM4J tree and formats it to a 058 * stream as XML. 059 * It can also take SAX events too so can be used by SAX clients as this object 060 * implements the {@link ContentHandler} and {@link LexicalHandler} interfaces. 061 * as well. This formatter performs typical document 062 * formatting. The XML declaration and processing instructions are 063 * always on their own lines. An {@link OutputFormat} object can be 064 * used to define how whitespace is handled when printing and allows various 065 * configuration options, such as to allow suppression of the XML declaration, 066 * the encoding declaration or whether empty documents are collapsed.</p> 067 * 068 * <p> There are <code>write(...)</code> methods to print any of the 069 * standard DOM4J classes, including <code>Document</code> and 070 * <code>Element</code>, to either a <code>Writer</code> or an 071 * <code>OutputStream</code>. Warning: using your own 072 * <code>Writer</code> may cause the writer's preferred character 073 * encoding to be ignored. If you use encodings other than UTF8, we 074 * recommend using the method that takes an OutputStream instead. 075 * </p> 076 * 077 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> 078 * @author Joseph Bowbeer 079 * @version $Revision: 1.46 $ 080 */ 081public class XMLWriter extends XMLFilterImpl implements LexicalHandler { 082 083 protected static final String[] LEXICAL_HANDLER_NAMES = { 084 "http://xml.org/sax/properties/lexical-handler", 085 "http://xml.org/sax/handlers/LexicalHandler" 086 }; 087 088 private static final boolean ESCAPE_TEXT = true; 089 private static final boolean SUPPORT_PAD_TEXT = false; 090 091 protected static final OutputFormat DEFAULT_FORMAT = new OutputFormat(); 092 093 /** Stores the last type of node written so algorithms can refer to the 094 * previous node type */ 095 protected int lastOutputNodeType; 096 097 /** The Writer used to output to */ 098 protected Writer writer; 099 100 /** The Stack of namespaceStack written so far */ 101 private NamespaceStack namespaceStack = new NamespaceStack(); 102 103 /** The format used by this writer */ 104 private OutputFormat format; 105 /** The initial number of indentations (so you can print a whole 106 document indented, if you like) **/ 107 private int indentLevel = 0; 108 109 /** buffer used when escaping strings */ 110 private StringBuffer buffer = new StringBuffer(); 111 112 /** Whether a flush should occur after writing a document */ 113 private boolean autoFlush; 114 115 /** Lexical handler we should delegate to */ 116 private LexicalHandler lexicalHandler; 117 118 /** Whether comments should appear inside DTD declarations - defaults to false */ 119 private boolean showCommentsInDTDs; 120 121 /** Is the writer curerntly inside a DTD definition? */ 122 private boolean inDTD; 123 124 125 public XMLWriter(Writer writer) { 126 this( writer, DEFAULT_FORMAT ); 127 } 128 129 public XMLWriter(Writer writer, OutputFormat format) { 130 this.writer = writer; 131 this.format = format; 132 } 133 134 public XMLWriter() { 135 this.format = DEFAULT_FORMAT; 136 this.writer = new BufferedWriter( new OutputStreamWriter( System.out ) ); 137 this.autoFlush = true; 138 } 139 140 public XMLWriter(OutputStream out) throws UnsupportedEncodingException { 141 this.format = DEFAULT_FORMAT; 142 this.writer = createWriter(out, format.getEncoding()); 143 this.autoFlush = true; 144 } 145 146 public XMLWriter(OutputStream out, OutputFormat format) throws UnsupportedEncodingException { 147 this.format = format; 148 this.writer = createWriter(out, format.getEncoding()); 149 this.autoFlush = true; 150 } 151 152 public XMLWriter(OutputFormat format) throws UnsupportedEncodingException { 153 this.format = format; 154 this.writer = createWriter( System.out, format.getEncoding() ); 155 this.autoFlush = true; 156 } 157 158 159 public void setWriter(Writer writer) { 160 this.writer = writer; 161 this.autoFlush = false; 162 } 163 164 public void setOutputStream(OutputStream out) throws UnsupportedEncodingException { 165 this.writer = createWriter(out, format.getEncoding()); 166 this.autoFlush = true; 167 } 168 169 170 /** Set the initial indentation level. This can be used to output 171 * a document (or, more likely, an element) starting at a given 172 * indent level, so it's not always flush against the left margin. 173 * Default: 0 174 * 175 * @param indentLevel the number of indents to start with 176 */ 177 public void setIndentLevel(int indentLevel) { 178 this.indentLevel = indentLevel; 179 } 180 181 /** Flushes the underlying Writer */ 182 public void flush() throws IOException { 183 writer.flush(); 184 } 185 186 /** Closes the underlying Writer */ 187 public void close() throws IOException { 188 writer.close(); 189 } 190 191 /** Writes the new line text to the underlying Writer */ 192 public void println() throws IOException { 193 writer.write( format.getLineSeparator() ); 194 } 195 196 /** Writes the given {@link Attribute}. 197 * 198 * @param attribute <code>Attribute</code> to output. 199 */ 200 public void write(Attribute attribute) throws IOException { 201 writeAttribute(attribute); 202 203 if ( autoFlush ) { 204 flush(); 205 } 206 } 207 208 209 /** <p>This will print the <code>Document</code> to the current Writer.</p> 210 * 211 * <p> Warning: using your own Writer may cause the writer's 212 * preferred character encoding to be ignored. If you use 213 * encodings other than UTF8, we recommend using the method that 214 * takes an OutputStream instead. </p> 215 * 216 * <p>Note: as with all Writers, you may need to flush() yours 217 * after this method returns.</p> 218 * 219 * @param doc <code>Document</code> to format. 220 * @throws <code>IOException</code> - if there's any problem writing. 221 **/ 222 public void write(Document doc) throws IOException { 223 writeDeclaration(); 224 225 if (doc.getDocType() != null) { 226 indent(); 227 writeDocType(doc.getDocType()); 228 } 229 230 for ( int i = 0, size = doc.nodeCount(); i < size; i++ ) { 231 Node node = doc.node(i); 232 writeNode( node ); 233 } 234 writePrintln(); 235 236 if ( autoFlush ) { 237 flush(); 238 } 239 } 240 241 /** <p>Writes the <code>{@link Element}</code>, including 242 * its <code>{@link Attribute}</code>s, and its value, and all 243 * its content (child nodes) to the current Writer.</p> 244 * 245 * @param element <code>Element</code> to output. 246 */ 247 public void write(Element element) throws IOException { 248 writeElement(element); 249 250 if ( autoFlush ) { 251 flush(); 252 } 253 } 254 255 256 /** Writes the given {@link CDATA}. 257 * 258 * @param cdata <code>CDATA</code> to output. 259 */ 260 public void write(CDATA cdata) throws IOException { 261 writeCDATA( cdata.getText() ); 262 263 if ( autoFlush ) { 264 flush(); 265 } 266 } 267 268 /** Writes the given {@link Comment}. 269 * 270 * @param comment <code>Comment</code> to output. 271 */ 272 public void write(Comment comment) throws IOException { 273 writeComment( comment.getText() ); 274 275 if ( autoFlush ) { 276 flush(); 277 } 278 } 279 280 /** Writes the given {@link DocumentType}. 281 * 282 * @param docType <code>DocumentType</code> to output. 283 */ 284 public void write(DocumentType docType) throws IOException { 285 writeDocType(docType); 286 287 if ( autoFlush ) { 288 flush(); 289 } 290 } 291 292 293 /** Writes the given {@link Entity}. 294 * 295 * @param entity <code>Entity</code> to output. 296 */ 297 public void write(Entity entity) throws IOException { 298 writeEntity( entity ); 299 300 if ( autoFlush ) { 301 flush(); 302 } 303 } 304 305 306 /** Writes the given {@link Namespace}. 307 * 308 * @param namespace <code>Namespace</code> to output. 309 */ 310 public void write(Namespace namespace) throws IOException { 311 writeNamespace(namespace); 312 313 if ( autoFlush ) { 314 flush(); 315 } 316 } 317 318 /** Writes the given {@link ProcessingInstruction}. 319 * 320 * @param processingInstruction <code>ProcessingInstruction</code> to output. 321 */ 322 public void write(ProcessingInstruction processingInstruction) throws IOException { 323 writeProcessingInstruction(processingInstruction); 324 325 if ( autoFlush ) { 326 flush(); 327 } 328 } 329 330 /** <p>Print out a {@link String}, Perfoms 331 * the necessary entity escaping and whitespace stripping.</p> 332 * 333 * @param text is the text to output 334 */ 335 public void write(String text) throws IOException { 336 writeString(text); 337 338 if ( autoFlush ) { 339 flush(); 340 } 341 } 342 343 /** Writes the given {@link Text}. 344 * 345 * @param text <code>Text</code> to output. 346 */ 347 public void write(Text text) throws IOException { 348 writeString(text.getText()); 349 350 if ( autoFlush ) { 351 flush(); 352 } 353 } 354 355 /** Writes the given {@link Node}. 356 * 357 * @param node <code>Node</code> to output. 358 */ 359 public void write(Node node) throws IOException { 360 writeNode(node); 361 362 if ( autoFlush ) { 363 flush(); 364 } 365 } 366 367 /** Writes the given object which should be a String, a Node or a List 368 * of Nodes. 369 * 370 * @param object is the object to output. 371 */ 372 public void write(Object object) throws IOException { 373 if (object instanceof Node) { 374 write((Node) object); 375 } 376 else if (object instanceof String) { 377 write((String) object); 378 } 379 else if (object instanceof List) { 380 List list = (List) object; 381 for ( int i = 0, size = list.size(); i < size; i++ ) { 382 write( list.get(i) ); 383 } 384 } 385 else if (object != null) { 386 throw new IOException( "Invalid object: " + object ); 387 } 388 } 389 390 391 /** <p>Writes the opening tag of an {@link Element}, 392 * including its {@link Attribute}s 393 * but without its content.</p> 394 * 395 * @param element <code>Element</code> to output. 396 */ 397 public void writeOpen(Element element) throws IOException { 398 writer.write("<"); 399 writer.write( element.getQualifiedName() ); 400 writeAttributes(element); 401 writer.write(">"); 402 } 403 404 /** <p>Writes the closing tag of an {@link Element}</p> 405 * 406 * @param element <code>Element</code> to output. 407 */ 408 public void writeClose(Element element) throws IOException { 409 writeClose( element.getQualifiedName() ); 410 } 411 412 413 // XMLFilterImpl methods 414 //------------------------------------------------------------------------- 415 public void parse(InputSource source) throws IOException, SAXException { 416 installLexicalHandler(); 417 super.parse(source); 418 } 419 420 421 public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException { 422 for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) { 423 if (LEXICAL_HANDLER_NAMES[i].equals(name)) { 424 setLexicalHandler((LexicalHandler) value); 425 return; 426 } 427 } 428 super.setProperty(name, value); 429 } 430 431 public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException { 432 for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) { 433 if (LEXICAL_HANDLER_NAMES[i].equals(name)) { 434 return getLexicalHandler(); 435 } 436 } 437 return super.getProperty(name); 438 } 439 440 public void setLexicalHandler (LexicalHandler handler) { 441 if (handler == null) { 442 throw new NullPointerException("Null lexical handler"); 443 } 444 else { 445 this.lexicalHandler = handler; 446 } 447 } 448 449 public LexicalHandler getLexicalHandler(){ 450 return lexicalHandler; 451 } 452 453 454 // ContentHandler interface 455 //------------------------------------------------------------------------- 456 public void setDocumentLocator(Locator locator) { 457 super.setDocumentLocator(locator); 458 } 459 460 public void startDocument() throws SAXException { 461 try { 462 writeDeclaration(); 463 super.startDocument(); 464 } 465 catch (IOException e) { 466 handleException(e); 467 } 468 } 469 470 public void endDocument() throws SAXException { 471 super.endDocument(); 472 } 473 474 public void startPrefixMapping(String prefix, String uri) throws SAXException { 475 super.startPrefixMapping(prefix, uri); 476 } 477 478 public void endPrefixMapping(String prefix) throws SAXException { 479 super.endPrefixMapping(prefix); 480 } 481 482 483 public void startElement(String namespaceURI, String localName, String qName, Attributes attributes) throws SAXException { 484 try { 485 writePrintln(); 486 indent(); 487 writer.write("<"); 488 writer.write(qName); 489 writeAttributes( attributes ); 490 writer.write(">"); 491 ++indentLevel; 492 lastOutputNodeType = Node.ELEMENT_NODE; 493 494 super.startElement( namespaceURI, localName, qName, attributes ); 495 } 496 catch (IOException e) { 497 handleException(e); 498 } 499 } 500 501 public void endElement(String namespaceURI, String localName, String qName) throws SAXException { 502 try { 503 --indentLevel; 504 if ( lastOutputNodeType == Node.ELEMENT_NODE ) { 505 writePrintln(); 506 indent(); 507 } 508 509 // XXXX: need to determine this using a stack and checking for 510 // content / children 511 boolean hadContent = true; 512 if ( hadContent ) { 513 writeClose(qName); 514 } 515 else { 516 writeEmptyElementClose(qName); 517 } 518 lastOutputNodeType = Node.ELEMENT_NODE; 519 520 super.endElement( namespaceURI, localName, qName ); 521 } 522 catch (IOException e) { 523 handleException(e); 524 } 525 } 526 527 public void characters(char[] ch, int start, int length) throws SAXException { 528 try { 529 write( new String( ch, start, length ) ); 530 531 super.characters(ch, start, length); 532 } 533 catch (IOException e) { 534 handleException(e); 535 } 536 } 537 538 public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { 539 super.ignorableWhitespace(ch, start, length); 540 } 541 542 public void processingInstruction(String target, String data) throws SAXException { 543 try { 544 indent(); 545 writer.write("<?"); 546 writer.write(target); 547 writer.write(" "); 548 writer.write(data); 549 writer.write("?>"); 550 writePrintln(); 551 lastOutputNodeType = Node.PROCESSING_INSTRUCTION_NODE; 552 553 super.processingInstruction(target, data); 554 } 555 catch (IOException e) { 556 handleException(e); 557 } 558 } 559 560 561 562 // DTDHandler interface 563 //------------------------------------------------------------------------- 564 public void notationDecl(String name, String publicID, String systemID) throws SAXException { 565 super.notationDecl(name, publicID, systemID); 566 } 567 568 public void unparsedEntityDecl(String name, String publicID, String systemID, String notationName) throws SAXException { 569 super.unparsedEntityDecl(name, publicID, systemID, notationName); 570 } 571 572 573 // LexicalHandler interface 574 //------------------------------------------------------------------------- 575 public void startDTD(String name, String publicID, String systemID) throws SAXException { 576 inDTD = true; 577 try { 578 writeDocType(name, publicID, systemID); 579 } 580 catch (IOException e) { 581 handleException(e); 582 } 583 584 if (lexicalHandler != null) { 585 lexicalHandler.startDTD(name, publicID, systemID); 586 } 587 } 588 589 public void endDTD() throws SAXException { 590 inDTD = false; 591 if (lexicalHandler != null) { 592 lexicalHandler.endDTD(); 593 } 594 } 595 596 public void startCDATA() throws SAXException { 597 try { 598 writer.write( "<![CDATA[" ); 599 } 600 catch (IOException e) { 601 handleException(e); 602 } 603 604 if (lexicalHandler != null) { 605 lexicalHandler.startCDATA(); 606 } 607 } 608 609 public void endCDATA() throws SAXException { 610 try { 611 writer.write( "]]>" ); 612 } 613 catch (IOException e) { 614 handleException(e); 615 } 616 617 if (lexicalHandler != null) { 618 lexicalHandler.endCDATA(); 619 } 620 } 621 622 public void startEntity(String name) throws SAXException { 623 try { 624 writeEntityRef(name); 625 } 626 catch (IOException e) { 627 handleException(e); 628 } 629 630 if (lexicalHandler != null) { 631 lexicalHandler.startEntity(name); 632 } 633 } 634 635 public void endEntity(String name) throws SAXException { 636 if (lexicalHandler != null) { 637 lexicalHandler.endEntity(name); 638 } 639 } 640 641 public void comment(char[] ch, int start, int length) throws SAXException { 642 if ( showCommentsInDTDs || ! inDTD ) { 643 try { 644 writeComment( new String(ch, start, length) ); 645 } 646 catch (IOException e) { 647 handleException(e); 648 } 649 } 650 651 if (lexicalHandler != null) { 652 lexicalHandler.comment(ch, start, length); 653 } 654 } 655 656 657 658 // Implementation methods 659 //------------------------------------------------------------------------- 660 protected void writeElement(Element element) throws IOException { 661 int size = element.nodeCount(); 662 String qualifiedName = element.getQualifiedName(); 663 664 writePrintln(); 665 indent(); 666 667 writer.write("<"); 668 writer.write(qualifiedName); 669 670 int previouslyDeclaredNamespaces = namespaceStack.size(); 671 Namespace ns = element.getNamespace(); 672 if (isNamespaceDeclaration( ns ) ) { 673 namespaceStack.push(ns); 674 writeNamespace(ns); 675 } 676 677 // Print out additional namespace declarations 678 boolean textOnly = true; 679 for ( int i = 0; i < size; i++ ) { 680 Node node = element.node(i); 681 if ( node instanceof Namespace ) { 682 Namespace additional = (Namespace) node; 683 if (isNamespaceDeclaration( additional ) ) { 684 namespaceStack.push(additional); 685 writeNamespace(additional); 686 } 687 } 688 else if ( node instanceof Element) { 689 textOnly = false; 690 } 691 } 692 693 writeAttributes(element); 694 695 lastOutputNodeType = Node.ELEMENT_NODE; 696 697 if ( size <= 0 ) { 698 writeEmptyElementClose(qualifiedName); 699 } 700 else { 701 writer.write(">"); 702 if ( textOnly ) { 703 // we have at least one text node so lets assume 704 // that its non-empty 705 writeElementContent(element); 706 } 707 else { 708 // we know it's not null or empty from above 709 ++indentLevel; 710 711 writeElementContent(element); 712 713 --indentLevel; 714 715 writePrintln(); 716 indent(); 717 } 718 writer.write("</"); 719 writer.write(qualifiedName); 720 writer.write(">"); 721 } 722 723 // remove declared namespaceStack from stack 724 while (namespaceStack.size() > previouslyDeclaredNamespaces) { 725 namespaceStack.pop(); 726 } 727 728 lastOutputNodeType = Node.ELEMENT_NODE; 729 } 730 731 /** Outputs the content of the given element. If whitespace trimming is 732 * enabled then all adjacent text nodes are appended together before 733 * the whitespace trimming occurs to avoid problems with multiple 734 * text nodes being created due to text content that spans parser buffers 735 * in a SAX parser. 736 */ 737 protected void writeElementContent(Element element) throws IOException { 738 if (format.isTrimText()) { 739 // concatenate adjacent text nodes together 740 // so that whitespace trimming works properly 741 Text lastTextNode = null; 742 StringBuffer buffer = null; 743 for ( int i = 0, size = element.nodeCount(); i < size; i++ ) { 744 Node node = element.node(i); 745 if ( node instanceof Text ) { 746 if ( lastTextNode == null ) { 747 lastTextNode = (Text) node; 748 } 749 else { 750 buffer = new StringBuffer( lastTextNode.getText() ); 751 buffer.append( ((Text) node).getText() ); 752 } 753 } 754 else { 755 if ( lastTextNode != null ) { 756 if ( buffer != null ) { 757 writeString( buffer.toString() ); 758 buffer = null; 759 } 760 else { 761 writeString( lastTextNode.getText() ); 762 } 763 lastTextNode = null; 764 } 765 writeNode(node); 766 } 767 } 768 if ( lastTextNode != null ) { 769 if ( buffer != null ) { 770 writeString( buffer.toString() ); 771 buffer = null; 772 } 773 else { 774 writeString( lastTextNode.getText() ); 775 } 776 lastTextNode = null; 777 } 778 } 779 else { 780 for ( int i = 0, size = element.nodeCount(); i < size; i++ ) { 781 Node node = element.node(i); 782 writeNode(node); 783 } 784 } 785 } 786 protected void writeCDATA(String text) throws IOException { 787 writer.write( "<![CDATA[" ); 788 writer.write( text ); 789 writer.write( "]]>" ); 790 791 lastOutputNodeType = Node.CDATA_SECTION_NODE; 792 } 793 794 protected void writeDocType(DocumentType docType) throws IOException { 795 if (docType != null) { 796 docType.write( writer ); 797 //writeDocType( docType.getElementName(), docType.getPublicID(), docType.getSystemID() ); 798 writePrintln(); 799 } 800 } 801 802 803 protected void writeNamespace(Namespace namespace) throws IOException { 804 if ( namespace != null ) { 805 String prefix = namespace.getPrefix(); 806 writer.write(" xmlns"); 807 if (prefix != null && prefix.length() > 0) { 808 writer.write(":"); 809 writer.write(prefix); 810 } 811 writer.write("=\""); 812 writer.write(namespace.getURI()); 813 writer.write("\""); 814 } 815 } 816 817 protected void writeProcessingInstruction(ProcessingInstruction processingInstruction) throws IOException { 818 //indent(); 819 writer.write( "<?" ); 820 writer.write( processingInstruction.getName() ); 821 writer.write( " " ); 822 writer.write( processingInstruction.getText() ); 823 writer.write( "?>" ); 824 writePrintln(); 825 826 lastOutputNodeType = Node.PROCESSING_INSTRUCTION_NODE; 827 } 828 829 protected void writeString(String text) throws IOException { 830 if ( text != null && text.length() > 0 ) { 831 if ( ESCAPE_TEXT ) { 832 text = escapeElementEntities(text); 833 } 834 835 if ( SUPPORT_PAD_TEXT ) { 836 if (lastOutputNodeType == Node.ELEMENT_NODE) { 837 String padText = getPadText(); 838 if ( padText != null ) { 839 writer.write(padText); 840 } 841 } 842 } 843 844 if (format.isTrimText()) { 845 boolean first = true; 846 StringTokenizer tokenizer = new StringTokenizer(text); 847 while (tokenizer.hasMoreTokens()) { 848 String token = tokenizer.nextToken(); 849 if ( first ) { 850 first = false; 851 if ( lastOutputNodeType == Node.TEXT_NODE ) { 852 writer.write(" "); 853 } 854 } 855 else { 856 writer.write(" "); 857 } 858 writer.write(token); 859 lastOutputNodeType = Node.TEXT_NODE; 860 } 861 } 862 else { 863 lastOutputNodeType = Node.TEXT_NODE; 864 writer.write(text); 865 } 866 } 867 } 868 869 870 protected void writeNode(Node node) throws IOException { 871 int nodeType = node.getNodeType(); 872 switch (nodeType) { 873 case Node.ELEMENT_NODE: 874 writeElement((Element) node); 875 break; 876 case Node.ATTRIBUTE_NODE: 877 writeAttribute((Attribute) node); 878 break; 879 case Node.TEXT_NODE: 880 writeString(node.getText()); 881 //write((Text) node); 882 break; 883 case Node.CDATA_SECTION_NODE: 884 writeCDATA(node.getText()); 885 break; 886 case Node.ENTITY_REFERENCE_NODE: 887 writeEntity((Entity) node); 888 break; 889 case Node.PROCESSING_INSTRUCTION_NODE: 890 writeProcessingInstruction((ProcessingInstruction) node); 891 break; 892 case Node.COMMENT_NODE: 893 writeComment(node.getText()); 894 break; 895 case Node.DOCUMENT_NODE: 896 write((Document) node); 897 break; 898 case Node.DOCUMENT_TYPE_NODE: 899 writeDocType((DocumentType) node); 900 break; 901 case Node.NAMESPACE_NODE: 902 // Will be output with attributes 903 //write((Namespace) node); 904 break; 905 default: 906 throw new IOException( "Invalid node type: " + node ); 907 } 908 } 909 910 911 912 913 protected void installLexicalHandler() { 914 XMLReader parent = getParent(); 915 if (parent == null) { 916 throw new NullPointerException("No parent for filter"); 917 } 918 // try to register for lexical events 919 for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) { 920 try { 921 parent.setProperty(LEXICAL_HANDLER_NAMES[i], this); 922 break; 923 } 924 catch (SAXNotRecognizedException ex) { 925 // ignore 926 } 927 catch (SAXNotSupportedException ex) { 928 // ignore 929 } 930 } 931 } 932 933 protected void writeDocType(String name, String publicID, String systemID) throws IOException { 934 boolean hasPublic = false; 935 936 writer.write("<!DOCTYPE "); 937 writer.write(name); 938 if ((publicID != null) && (!publicID.equals(""))) { 939 writer.write(" PUBLIC \""); 940 writer.write(publicID); 941 writer.write("\""); 942 hasPublic = true; 943 } 944 if ((systemID != null) && (!systemID.equals(""))) { 945 if (!hasPublic) { 946 writer.write(" SYSTEM"); 947 } 948 writer.write(" \""); 949 writer.write(systemID); 950 writer.write("\""); 951 } 952 writer.write(">"); 953 writePrintln(); 954 } 955 956 protected void writeEntity(Entity entity) throws IOException { 957 writeEntityRef( entity.getName() ); 958 } 959 960 protected void writeEntityRef(String name) throws IOException { 961 writer.write( "&" ); 962 writer.write( name ); 963 writer.write( ";" ); 964 965 lastOutputNodeType = Node.ENTITY_REFERENCE_NODE; 966 } 967 968 protected void writeComment(String text) throws IOException { 969 if (format.isNewlines()) { 970 if ( lastOutputNodeType != Node.COMMENT_NODE ) { 971 println(); 972 } 973 indent(); 974 } 975 writer.write( "<!--" ); 976 writer.write( text ); 977 writer.write( "-->" ); 978 979 writePrintln(); 980 981 lastOutputNodeType = Node.COMMENT_NODE; 982 } 983 984 /** Writes the attributes of the given element 985 * 986 */ 987 protected void writeAttributes( Element element ) throws IOException { 988 989 // I do not yet handle the case where the same prefix maps to 990 // two different URIs. For attributes on the same element 991 // this is illegal; but as yet we don't throw an exception 992 // if someone tries to do this 993 for ( int i = 0, size = element.attributeCount(); i < size; i++ ) { 994 Attribute attribute = element.attribute(i); 995 Namespace ns = attribute.getNamespace(); 996 if (ns != null && ns != Namespace.NO_NAMESPACE && ns != Namespace.XML_NAMESPACE) { 997 String prefix = ns.getPrefix(); 998 String uri = namespaceStack.getURI(prefix); 999 if (!ns.getURI().equals(uri)) { // output a new namespace declaration 1000 writeNamespace(ns); 1001 namespaceStack.push(ns); 1002 } 1003 } 1004 1005 writer.write(" "); 1006 writer.write(attribute.getQualifiedName()); 1007 writer.write("=\""); 1008 writeEscapeAttributeEntities(attribute.getValue()); 1009 writer.write("\""); 1010 } 1011 } 1012 1013 protected void writeAttribute(Attribute attribute) throws IOException { 1014 writer.write(" "); 1015 writer.write(attribute.getQualifiedName()); 1016 writer.write("="); 1017 1018 writer.write("\""); 1019 1020 writeEscapeAttributeEntities(attribute.getValue()); 1021 1022 writer.write("\""); 1023 lastOutputNodeType = Node.ATTRIBUTE_NODE; 1024 } 1025 1026 protected void writeAttributes(Attributes attributes) throws IOException { 1027 for (int i = 0, size = attributes.getLength(); i < size; i++) { 1028 writeAttribute( attributes, i ); 1029 } 1030 } 1031 1032 protected void writeAttribute(Attributes attributes, int index) throws IOException { 1033 writer.write(" "); 1034 writer.write(attributes.getQName(index)); 1035 writer.write("=\""); 1036 writeEscapeAttributeEntities(attributes.getValue(index)); 1037 writer.write("\""); 1038 } 1039 1040 1041 1042 protected void indent() throws IOException { 1043 String indent = format.getIndent(); 1044 if ( indent != null && indent.length() > 0 ) { 1045 for ( int i = 0; i < indentLevel; i++ ) { 1046 writer.write(indent); 1047 } 1048 } 1049 } 1050 1051 /** 1052 * <p> 1053 * This will print a new line only if the newlines flag was set to true 1054 * </p> 1055 * 1056 * @param out <code>Writer</code> to write to 1057 */ 1058 protected void writePrintln() throws IOException { 1059 if (format.isNewlines()) { 1060 writer.write( format.getLineSeparator() ); 1061 } 1062 } 1063 1064 /** 1065 * Get an OutputStreamWriter, use preferred encoding. 1066 */ 1067 protected Writer createWriter(OutputStream outStream, String encoding) throws UnsupportedEncodingException { 1068 return new BufferedWriter( 1069 new OutputStreamWriter( outStream, encoding ) 1070 ); 1071 } 1072 1073 /** 1074 * <p> 1075 * This will write the declaration to the given Writer. 1076 * Assumes XML version 1.0 since we don't directly know. 1077 * </p> 1078 */ 1079 protected void writeDeclaration() throws IOException { 1080 String encoding = format.getEncoding(); 1081 1082 // Only print of declaration is not suppressed 1083 if (! format.isSuppressDeclaration()) { 1084 // Assume 1.0 version 1085 if (encoding.equals("UTF8")) { 1086 writer.write("<?xml version=\"1.0\""); 1087 if (!format.isOmitEncoding()) { 1088 writer.write(" encoding=\"UTF-8\""); 1089 } 1090 writer.write("?>"); 1091 } else { 1092 writer.write("<?xml version=\"1.0\""); 1093 if (! format.isOmitEncoding()) { 1094 writer.write(" encoding=\"" + encoding + "\""); 1095 } 1096 writer.write("?>"); 1097 } 1098 println(); 1099 } 1100 } 1101 1102 protected void writeClose(String qualifiedName) throws IOException { 1103 writer.write("</"); 1104 writer.write(qualifiedName); 1105 writer.write(">"); 1106 } 1107 1108 protected void writeEmptyElementClose(String qualifiedName) throws IOException { 1109 // Simply close up 1110 if (! isExpandEmptyElements()) { 1111 writer.write("/>"); 1112 } else { 1113 writer.write("></"); 1114 writer.write(qualifiedName); 1115 writer.write(">"); 1116 } 1117 } 1118 1119 protected boolean isExpandEmptyElements() { 1120 return format.isExpandEmptyElements(); 1121 } 1122 1123 1124 /** This will take the pre-defined entities in XML 1.0 and 1125 * convert their character representation to the appropriate 1126 * entity reference, suitable for XML attributes. 1127 */ 1128 protected String escapeElementEntities(String text) { 1129 char[] block = null; 1130 int i, last = 0, size = text.length(); 1131 for ( i = 0; i < size; i++ ) { 1132 String entity = null; 1133 switch( text.charAt(i) ) { 1134 case '<' : 1135 entity = "<"; 1136 break; 1137 case '>' : 1138 entity = ">"; 1139 break; 1140 case '&' : 1141 entity = "&"; 1142 break; 1143 } 1144 if (entity != null) { 1145 if ( block == null ) { 1146 block = text.toCharArray(); 1147 } 1148 buffer.append(block, last, i - last); 1149 buffer.append(entity); 1150 last = i + 1; 1151 } 1152 } 1153 if ( last == 0 ) { 1154 return text; 1155 } 1156 if ( last < size ) { 1157 if ( block == null ) { 1158 block = text.toCharArray(); 1159 } 1160 buffer.append(block, last, i - last); 1161 } 1162 String answer = buffer.toString(); 1163 buffer.setLength(0); 1164 return answer; 1165 } 1166 1167 protected void writeEscapeAttributeEntities(String text) throws IOException { 1168 if ( text != null ) { 1169 String escapedText = escapeAttributeEntities( text ); 1170 writer.write( escapedText ); 1171 } 1172 } 1173 /** This will take the pre-defined entities in XML 1.0 and 1174 * convert their character representation to the appropriate 1175 * entity reference, suitable for XML attributes. 1176 */ 1177 protected String escapeAttributeEntities(String text) { 1178 char[] block = null; 1179 int i, last = 0, size = text.length(); 1180 for ( i = 0; i < size; i++ ) { 1181 String entity = null; 1182 switch( text.charAt(i) ) { 1183 case '<' : 1184 entity = "<"; 1185 break; 1186 case '>' : 1187 entity = ">"; 1188 break; 1189 case '\'' : 1190 entity = "'"; 1191 break; 1192 case '\"' : 1193 entity = """; 1194 break; 1195 case '&' : 1196 entity = "&"; 1197 break; 1198 } 1199 if (entity != null) { 1200 if ( block == null ) { 1201 block = text.toCharArray(); 1202 } 1203 buffer.append(block, last, i - last); 1204 buffer.append(entity); 1205 last = i + 1; 1206 } 1207 } 1208 if ( last == 0 ) { 1209 return text; 1210 } 1211 if ( last < size ) { 1212 if ( block == null ) { 1213 block = text.toCharArray(); 1214 } 1215 buffer.append(block, last, i - last); 1216 } 1217 String answer = buffer.toString(); 1218 buffer.setLength(0); 1219 return answer; 1220 } 1221 1222 protected boolean isNamespaceDeclaration( Namespace ns ) { 1223 if (ns != null && ns != Namespace.NO_NAMESPACE && ns != Namespace.XML_NAMESPACE) { 1224 String uri = ns.getURI(); 1225 if ( uri != null && uri.length() > 0 ) { 1226 if ( ! namespaceStack.contains( ns ) ) { 1227 return true; 1228 1229 } 1230 } 1231 } 1232 return false; 1233 } 1234 1235 protected void handleException(IOException e) throws SAXException { 1236 throw new SAXException(e); 1237 } 1238 1239 protected String getPadText() { 1240 return null; 1241 } 1242} 1243 1244 1245 1246 1247/* 1248 * Redistribution and use of this software and associated documentation 1249 * ("Software"), with or without modification, are permitted provided 1250 * that the following conditions are met: 1251 * 1252 * 1. Redistributions of source code must retain copyright 1253 * statements and notices. Redistributions must also contain a 1254 * copy of this document. 1255 * 1256 * 2. Redistributions in binary form must reproduce the 1257 * above copyright notice, this list of conditions and the 1258 * following disclaimer in the documentation and/or other 1259 * materials provided with the distribution. 1260 * 1261 * 3. The name "DOM4J" must not be used to endorse or promote 1262 * products derived from this Software without prior written 1263 * permission of MetaStuff, Ltd. For written permission, 1264 * please contact dom4j-info@metastuff.com. 1265 * 1266 * 4. Products derived from this Software may not be called "DOM4J" 1267 * nor may "DOM4J" appear in their names without prior written 1268 * permission of MetaStuff, Ltd. DOM4J is a registered 1269 * trademark of MetaStuff, Ltd. 1270 * 1271 * 5. Due credit should be given to the DOM4J Project 1272 * (http://dom4j.org/). 1273 * 1274 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS 1275 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT 1276 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 1277 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 1278 * METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 1279 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 1280 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 1281 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 1282 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 1283 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 1284 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 1285 * OF THE POSSIBILITY OF SUCH DAMAGE. 1286 * 1287 * Copyright 2001 (C) MetaStuff, Ltd. All Rights Reserved. 1288 * 1289 * $Id: XMLWriter.java,v 1.46 2002/02/14 11:55:46 jstrachan Exp $ 1290 */