| 1 package org.jivesoftware.util; 2 3 import org.dom4j.*; 4 import org.dom4j.io.OutputFormat; 5 import org.dom4j.tree.NamespaceStack; 6 import org.xml.sax.*; 7 import org.xml.sax.ext.LexicalHandler ; 8 import org.xml.sax.helpers.XMLFilterImpl ; 9 10 import java.io.*; 11 import java.util.*; 12 13 17 public class XMLWriter extends XMLFilterImpl implements LexicalHandler { 18 19 private static final String PAD_TEXT = " "; 20 21 protected static final String [] LEXICAL_HANDLER_NAMES = { 22 "http://xml.org/sax/properties/lexical-handler", 23 "http://xml.org/sax/handlers/LexicalHandler" 24 }; 25 26 protected static final OutputFormat DEFAULT_FORMAT = new OutputFormat(); 27 28 29 private boolean resolveEntityRefs = true; 30 31 33 protected int lastOutputNodeType; 34 35 36 protected boolean preserve=false; 37 38 39 protected Writer writer; 40 41 42 private NamespaceStack namespaceStack = new NamespaceStack(); 43 44 45 private OutputFormat format; 46 47 48 private boolean escapeText = true; 49 51 private int indentLevel = 0; 52 53 54 private StringBuilder buffer = new StringBuilder (); 55 56 57 private boolean charactersAdded = false; 58 private char lastChar; 59 60 61 private boolean autoFlush; 62 63 64 private LexicalHandler lexicalHandler; 65 66 67 private boolean showCommentsInDTDs; 68 69 70 private boolean inDTD; 71 72 73 private Map namespacesMap; 74 75 80 private int maximumAllowedCharacter; 81 82 public XMLWriter(Writer writer) { 83 this( writer, DEFAULT_FORMAT ); 84 } 85 86 public XMLWriter(Writer writer, OutputFormat format) { 87 this.writer = writer; 88 this.format = format; 89 namespaceStack.push(Namespace.NO_NAMESPACE); 90 } 91 92 public XMLWriter() { 93 this.format = DEFAULT_FORMAT; 94 this.writer = new BufferedWriter( new OutputStreamWriter( System.out ) ); 95 this.autoFlush = true; 96 namespaceStack.push(Namespace.NO_NAMESPACE); 97 } 98 99 public XMLWriter(OutputStream out) throws UnsupportedEncodingException { 100 this.format = DEFAULT_FORMAT; 101 this.writer = createWriter(out, format.getEncoding()); 102 this.autoFlush = true; 103 namespaceStack.push(Namespace.NO_NAMESPACE); 104 } 105 106 public XMLWriter(OutputStream out, OutputFormat format) throws UnsupportedEncodingException { 107 this.format = format; 108 this.writer = createWriter(out, format.getEncoding()); 109 this.autoFlush = true; 110 namespaceStack.push(Namespace.NO_NAMESPACE); 111 } 112 113 public XMLWriter(OutputFormat format) throws UnsupportedEncodingException { 114 this.format = format; 115 this.writer = createWriter( System.out, format.getEncoding() ); 116 this.autoFlush = true; 117 namespaceStack.push(Namespace.NO_NAMESPACE); 118 } 119 120 public void setWriter(Writer writer) { 121 this.writer = writer; 122 this.autoFlush = false; 123 } 124 125 public void setOutputStream(OutputStream out) throws UnsupportedEncodingException { 126 this.writer = createWriter(out, format.getEncoding()); 127 this.autoFlush = true; 128 } 129 130 136 public boolean isEscapeText() { 137 return escapeText; 138 } 139 140 146 public void setEscapeText(boolean escapeText) { 147 this.escapeText = escapeText; 148 } 149 150 151 158 public void setIndentLevel(int indentLevel) { 159 this.indentLevel = indentLevel; 160 } 161 162 167 public int getMaximumAllowedCharacter() { 168 if (maximumAllowedCharacter == 0) { 169 maximumAllowedCharacter = defaultMaximumAllowedCharacter(); 170 } 171 return maximumAllowedCharacter; 172 } 173 174 184 public void setMaximumAllowedCharacter(int maximumAllowedCharacter) { 185 this.maximumAllowedCharacter = maximumAllowedCharacter; 186 } 187 188 189 public void flush() throws IOException { 190 writer.flush(); 191 } 192 193 194 public void close() throws IOException { 195 writer.close(); 196 } 197 198 199 public void println() throws IOException { 200 writer.write( format.getLineSeparator() ); 201 } 202 203 207 public void write(Attribute attribute) throws IOException { 208 writeAttribute(attribute); 209 210 if ( autoFlush ) { 211 flush(); 212 } 213 } 214 215 216 229 public void write(Document doc) throws IOException { 230 writeDeclaration(); 231 232 if (doc.getDocType() != null) { 233 indent(); 234 writeDocType(doc.getDocType()); 235 } 236 237 for ( int i = 0, size = doc.nodeCount(); i < size; i++ ) { 238 Node node = doc.node(i); 239 writeNode( node ); 240 } 241 writePrintln(); 242 243 if ( autoFlush ) { 244 flush(); 245 } 246 } 247 248 254 public void write(Element element) throws IOException { 255 writeElement(element); 256 257 if ( autoFlush ) { 258 flush(); 259 } 260 } 261 262 263 267 public void write(CDATA cdata) throws IOException { 268 writeCDATA( cdata.getText() ); 269 270 if ( autoFlush ) { 271 flush(); 272 } 273 } 274 275 279 public void write(Comment comment) throws IOException { 280 writeComment( comment.getText() ); 281 282 if ( autoFlush ) { 283 flush(); 284 } 285 } 286 287 291 public void write(DocumentType docType) throws IOException { 292 writeDocType(docType); 293 294 if ( autoFlush ) { 295 flush(); 296 } 297 } 298 299 300 304 public void write(Entity entity) throws IOException { 305 writeEntity( entity ); 306 307 if ( autoFlush ) { 308 flush(); 309 } 310 } 311 312 313 317 public void write(Namespace namespace) throws IOException { 318 writeNamespace(namespace); 319 320 if ( autoFlush ) { 321 flush(); 322 } 323 } 324 325 329 public void write(ProcessingInstruction processingInstruction) throws IOException { 330 writeProcessingInstruction(processingInstruction); 331 332 if ( autoFlush ) { 333 flush(); 334 } 335 } 336 337 342 public void write(String text) throws IOException { 343 writeString(text); 344 345 if ( autoFlush ) { 346 flush(); 347 } 348 } 349 350 354 public void write(Text text) throws IOException { 355 writeString(text.getText()); 356 357 if ( autoFlush ) { 358 flush(); 359 } 360 } 361 362 366 public void write(Node node) throws IOException { 367 writeNode(node); 368 369 if ( autoFlush ) { 370 flush(); 371 } 372 } 373 374 379 public void write(Object object) throws IOException { 380 if (object instanceof Node) { 381 write((Node) object); 382 } 383 else if (object instanceof String ) { 384 write((String ) object); 385 } 386 else if (object instanceof List) { 387 List list = (List) object; 388 for ( int i = 0, size = list.size(); i < size; i++ ) { 389 write( list.get(i) ); 390 } 391 } 392 else if (object != null) { 393 throw new IOException( "Invalid object: " + object ); 394 } 395 } 396 397 398 404 public void writeOpen(Element element) throws IOException { 405 writer.write("<"); 406 writer.write( element.getQualifiedName() ); 407 writeAttributes(element); 408 writer.write(">"); 409 } 410 411 415 public void writeClose(Element element) throws IOException { 416 writeClose( element.getQualifiedName() ); 417 } 418 419 420 public void parse(InputSource source) throws IOException, SAXException { 423 installLexicalHandler(); 424 super.parse(source); 425 } 426 427 428 public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException { 429 for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) { 430 if (LEXICAL_HANDLER_NAMES[i].equals(name)) { 431 setLexicalHandler((LexicalHandler ) value); 432 return; 433 } 434 } 435 super.setProperty(name, value); 436 } 437 438 public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException { 439 for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) { 440 if (LEXICAL_HANDLER_NAMES[i].equals(name)) { 441 return getLexicalHandler(); 442 } 443 } 444 return super.getProperty(name); 445 } 446 447 public void setLexicalHandler (LexicalHandler handler) { 448 if (handler == null) { 449 throw new NullPointerException ("Null lexical handler"); 450 } 451 else { 452 this.lexicalHandler = handler; 453 } 454 } 455 456 public LexicalHandler getLexicalHandler(){ 457 return lexicalHandler; 458 } 459 460 461 public void setDocumentLocator(Locator locator) { 464 super.setDocumentLocator(locator); 465 } 466 467 public void startDocument() throws SAXException { 468 try { 469 writeDeclaration(); 470 super.startDocument(); 471 } 472 catch (IOException e) { 473 handleException(e); 474 } 475 } 476 477 public void endDocument() throws SAXException { 478 super.endDocument(); 479 480 if ( autoFlush ) { 481 try { 482 flush(); 483 } catch ( IOException e) {} 484 } 485 } 486 487 public void startPrefixMapping(String prefix, String uri) throws SAXException { 488 if ( namespacesMap == null ) { 489 namespacesMap = new HashMap(); 490 } 491 namespacesMap.put(prefix, uri); 492 super.startPrefixMapping(prefix, uri); 493 } 494 495 public void endPrefixMapping(String prefix) throws SAXException { 496 super.endPrefixMapping(prefix); 497 } 498 499 public void startElement(String namespaceURI, String localName, String qName, Attributes attributes) throws SAXException { 500 try { 501 charactersAdded = false; 502 503 writePrintln(); 504 indent(); 505 writer.write("<"); 506 writer.write(qName); 507 writeNamespaces(); 508 writeAttributes( attributes ); 509 writer.write(">"); 510 ++indentLevel; 511 lastOutputNodeType = Node.ELEMENT_NODE; 512 513 super.startElement( namespaceURI, localName, qName, attributes ); 514 } 515 catch (IOException e) { 516 handleException(e); 517 } 518 } 519 520 public void endElement(String namespaceURI, String localName, String qName) throws SAXException { 521 try { 522 charactersAdded = false; 523 --indentLevel; 524 if ( lastOutputNodeType == Node.ELEMENT_NODE ) { 525 writePrintln(); 526 indent(); 527 } 528 529 boolean hadContent = true; 532 if ( hadContent ) { 533 writeClose(qName); 534 } 535 else { 536 writeEmptyElementClose(qName); 537 } 538 lastOutputNodeType = Node.ELEMENT_NODE; 539 540 super.endElement( namespaceURI, localName, qName ); 541 } 542 catch (IOException e) { 543 handleException(e); 544 } 545 } 546 547 public void characters(char[] ch, int start, int length) throws SAXException { 548 if (ch == null || ch.length == 0 || length <= 0) { 549 return; 550 } 551 552 try { 553 559 String string = new String (ch, start, length); 560 561 if (escapeText) { 562 string = escapeElementEntities(string); 563 } 564 565 if (format.isTrimText()) { 566 if ((lastOutputNodeType == Node.TEXT_NODE) && !charactersAdded) { 567 writer.write(" "); 568 } else if (charactersAdded && Character.isWhitespace(lastChar)) { 569 writer.write(lastChar); 570 } 571 572 String delim = ""; 573 StringTokenizer tokens = new StringTokenizer(string); 574 while (tokens.hasMoreTokens()) { 575 writer.write(delim); 576 writer.write(tokens.nextToken()); 577 delim = " "; 578 } 579 } else { 580 writer.write(string); 581 } 582 583 charactersAdded = true; 584 lastChar = ch[start + length - 1]; 585 lastOutputNodeType = Node.TEXT_NODE; 586 587 super.characters(ch, start, length); 588 } 589 catch (IOException e) { 590 handleException(e); 591 } 592 } 593 594 public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { 595 super.ignorableWhitespace(ch, start, length); 596 } 597 598 public void processingInstruction(String target, String data) throws SAXException { 599 try { 600 indent(); 601 writer.write("<?"); 602 writer.write(target); 603 writer.write(" "); 604 writer.write(data); 605 writer.write("?>"); 606 writePrintln(); 607 lastOutputNodeType = Node.PROCESSING_INSTRUCTION_NODE; 608 609 super.processingInstruction(target, data); 610 } 611 catch (IOException e) { 612 handleException(e); 613 } 614 } 615 616 617 618 public void notationDecl(String name, String publicID, String systemID) throws SAXException { 621 super.notationDecl(name, publicID, systemID); 622 } 623 624 public void unparsedEntityDecl(String name, String publicID, String systemID, String notationName) throws SAXException { 625 super.unparsedEntityDecl(name, publicID, systemID, notationName); 626 } 627 628 629 public void startDTD(String name, String publicID, String systemID) throws SAXException { 632 inDTD = true; 633 try { 634 writeDocType(name, publicID, systemID); 635 } 636 catch (IOException e) { 637 handleException(e); 638 } 639 640 if (lexicalHandler != null) { 641 lexicalHandler.startDTD(name, publicID, systemID); 642 } 643 } 644 645 public void endDTD() throws SAXException { 646 inDTD = false; 647 if (lexicalHandler != null) { 648 lexicalHandler.endDTD(); 649 } 650 } 651 652 public void startCDATA() throws SAXException { 653 try { 654 writer.write( "<![CDATA[" ); 655 } 656 catch (IOException e) { 657 handleException(e); 658 } 659 660 if (lexicalHandler != null) { 661 lexicalHandler.startCDATA(); 662 } 663 } 664 665 public void endCDATA() throws SAXException { 666 try { 667 writer.write( "]]>" ); 668 } 669 catch (IOException e) { 670 handleException(e); 671 } 672 673 if (lexicalHandler != null) { 674 lexicalHandler.endCDATA(); 675 } 676 } 677 678 public void startEntity(String name) throws SAXException { 679 try { 680 writeEntityRef(name); 681 } 682 catch (IOException e) { 683 handleException(e); 684 } 685 686 if (lexicalHandler != null) { 687 lexicalHandler.startEntity(name); 688 } 689 } 690 691 public void endEntity(String name) throws SAXException { 692 if (lexicalHandler != null) { 693 lexicalHandler.endEntity(name); 694 } 695 } 696 697 public void comment(char[] ch, int start, int length) throws SAXException { 698 if ( showCommentsInDTDs || ! inDTD ) { 699 try { 700 charactersAdded = false; 701 writeComment( new String (ch, start, length) ); 702 } 703 catch (IOException e) { 704 handleException(e); 705 } 706 } 707 708 if (lexicalHandler != null) { 709 lexicalHandler.comment(ch, start, length); 710 } 711 } 712 713 714 715 protected void writeElement(Element element) throws IOException { 718 int size = element.nodeCount(); 719 String qualifiedName = element.getQualifiedName(); 720 721 writePrintln(); 722 indent(); 723 724 writer.write("<"); 725 writer.write(qualifiedName); 726 727 int previouslyDeclaredNamespaces = namespaceStack.size(); 728 Namespace ns = element.getNamespace(); 729 if (isNamespaceDeclaration( ns ) ) { 730 namespaceStack.push(ns); 731 writeNamespace(ns); 732 } 733 734 boolean textOnly = true; 736 for ( int i = 0; i < size; i++ ) { 737 Node node = element.node(i); 738 if ( node instanceof Namespace ) { 739 Namespace additional = (Namespace) node; 740 if (isNamespaceDeclaration( additional ) ) { 741 namespaceStack.push(additional); 742 writeNamespace(additional); 743 } 744 } 745 else if ( node instanceof Element) { 746 textOnly = false; 747 } 748 else if ( node instanceof Comment) { 749 textOnly = false; 750 } 751 } 752 753 writeAttributes(element); 754 755 lastOutputNodeType = Node.ELEMENT_NODE; 756 757 if ( size <= 0 ) { 758 writeEmptyElementClose(qualifiedName); 759 } 760 else { 761 writer.write(">"); 762 if ( textOnly ) { 763 writeElementContent(element); 766 } 767 else { 768 ++indentLevel; 770 771 writeElementContent(element); 772 773 --indentLevel; 774 775 writePrintln(); 776 indent(); 777 } 778 writer.write("</"); 779 writer.write(qualifiedName); 780 writer.write(">"); 781 } 782 783 while (namespaceStack.size() > previouslyDeclaredNamespaces) { 785 namespaceStack.pop(); 786 } 787 788 lastOutputNodeType = Node.ELEMENT_NODE; 789 } 790 791 796 protected final boolean isElementSpacePreserved(Element element) { 797 final Attribute attr = (Attribute)element.attribute("space"); 798 boolean preserveFound=preserve; if (attr!=null) { 800 if ("xml".equals(attr.getNamespacePrefix()) && 801 "preserve".equals(attr.getText())) { 802 preserveFound = true; 803 } 804 else { 805 preserveFound = false; 806 } 807 } 808 return preserveFound; 809 } 810 816 protected void writeElementContent(Element element) throws IOException { 817 boolean trim = format.isTrimText(); 818 boolean oldPreserve=preserve; 819 if (trim) { preserve=isElementSpacePreserved(element); 821 trim = !preserve; 822 } 823 if (trim) { 824 Text lastTextNode = null; 827 StringBuilder buffer = null; 828 boolean textOnly = true; 829 for ( int i = 0, size = element.nodeCount(); i < size; i++ ) { 830 Node node = element.node(i); 831 if ( node instanceof Text ) { 832 if ( lastTextNode == null ) { 833 lastTextNode = (Text) node; 834 } 835 else { 836 if (buffer == null) { 837 buffer = new StringBuilder ( lastTextNode.getText() ); 838 } 839 buffer.append( ((Text) node).getText() ); 840 } 841 } 842 else { 843 if (!textOnly && format.isPadText()) { 844 writer.write(PAD_TEXT); 845 } 846 847 textOnly = false; 848 849 if ( lastTextNode != null ) { 850 if ( buffer != null ) { 851 writeString( buffer.toString() ); 852 buffer = null; 853 } 854 else { 855 writeString( lastTextNode.getText() ); 856 } 857 lastTextNode = null; 858 859 if (format.isPadText()) { 860 writer.write(PAD_TEXT); 861 } 862 } 863 writeNode(node); 864 } 865 } 866 if ( lastTextNode != null ) { 867 if (!textOnly && format.isPadText()) { 868 writer.write(PAD_TEXT); 869 } 870 if ( buffer != null ) { 871 writeString( buffer.toString() ); 872 buffer = null; 873 } 874 else { 875 writeString( lastTextNode.getText() ); 876 } 877 lastTextNode = null; 878 } 879 } 880 else { 881 Node lastTextNode = null; 882 for ( int i = 0, size = element.nodeCount(); i < size; i++ ) { 883 Node node = element.node(i); 884 if (node instanceof Text) { 885 writeNode(node); 886 lastTextNode = node; 887 } else { 888 if ((lastTextNode != null) && format.isPadText()) { 889 writer.write(PAD_TEXT); 890 } 891 writeNode(node); 892 if ((lastTextNode != null) && format.isPadText()) { 893 writer.write(PAD_TEXT); 894 } 895 lastTextNode = null; 896 } 897 } 898 } 899 preserve=oldPreserve; 900 } 901 protected void writeCDATA(String text) throws IOException { 902 writer.write( "<![CDATA[" ); 903 if (text != null) { 904 writer.write( text ); 905 } 906 writer.write( "]]>" ); 907 908 lastOutputNodeType = Node.CDATA_SECTION_NODE; 909 } 910 911 protected void writeDocType(DocumentType docType) throws IOException { 912 if (docType != null) { 913 docType.write( writer ); 914 writePrintln(); 916 } 917 } 918 919 920 protected void writeNamespace(Namesp
|