Clover coverage report - dom4j - 1.6.1
Coverage timestamp: ma mei 16 2005 14:23:01 GMT+01:00
file stats: LOC: 1.934   Methods: 93
NCLOC: 1.101   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
XMLWriter.java 59,5% 64,3% 57% 62,3%
coverage coverage
 1    /*
 2    * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
 3    *
 4    * This software is open source.
 5    * See the bottom of this file for the licence.
 6    */
 7   
 8    package org.dom4j.io;
 9   
 10    import java.io.BufferedWriter;
 11    import java.io.IOException;
 12    import java.io.OutputStream;
 13    import java.io.OutputStreamWriter;
 14    import java.io.UnsupportedEncodingException;
 15    import java.io.Writer;
 16    import java.util.HashMap;
 17    import java.util.Iterator;
 18    import java.util.List;
 19    import java.util.Map;
 20    import java.util.StringTokenizer;
 21   
 22    import org.dom4j.Attribute;
 23    import org.dom4j.CDATA;
 24    import org.dom4j.Comment;
 25    import org.dom4j.Document;
 26    import org.dom4j.DocumentType;
 27    import org.dom4j.Element;
 28    import org.dom4j.Entity;
 29    import org.dom4j.Namespace;
 30    import org.dom4j.Node;
 31    import org.dom4j.ProcessingInstruction;
 32    import org.dom4j.Text;
 33    import org.dom4j.tree.NamespaceStack;
 34   
 35    import org.xml.sax.Attributes;
 36    import org.xml.sax.InputSource;
 37    import org.xml.sax.Locator;
 38    import org.xml.sax.SAXException;
 39    import org.xml.sax.SAXNotRecognizedException;
 40    import org.xml.sax.SAXNotSupportedException;
 41    import org.xml.sax.XMLReader;
 42    import org.xml.sax.ext.LexicalHandler;
 43    import org.xml.sax.helpers.XMLFilterImpl;
 44   
 45    /**
 46    * <p>
 47    * <code>XMLWriter</code> takes a DOM4J tree and formats it to a stream as
 48    * XML. It can also take SAX events too so can be used by SAX clients as this
 49    * object implements the {@link org.xml.sax.ContentHandler}and {@link
 50    * LexicalHandler} interfaces. as well. This formatter performs typical document
 51    * formatting. The XML declaration and processing instructions are always on
 52    * their own lines. An {@link OutputFormat}object can be used to define how
 53    * whitespace is handled when printing and allows various configuration options,
 54    * such as to allow suppression of the XML declaration, the encoding declaration
 55    * or whether empty documents are collapsed.
 56    * </p>
 57    *
 58    * <p>
 59    * There are <code>write(...)</code> methods to print any of the standard
 60    * DOM4J classes, including <code>Document</code> and <code>Element</code>,
 61    * to either a <code>Writer</code> or an <code>OutputStream</code>.
 62    * Warning: using your own <code>Writer</code> may cause the writer's
 63    * preferred character encoding to be ignored. If you use encodings other than
 64    * UTF8, we recommend using the method that takes an OutputStream instead.
 65    * </p>
 66    *
 67    * @author <a href="mailto:jstrachan@apache.org">James Strachan </a>
 68    * @author Joseph Bowbeer
 69    * @version $Revision: 1.83.2.2 $
 70    */
 71    public class XMLWriter extends XMLFilterImpl implements LexicalHandler {
 72    private static final String PAD_TEXT = " ";
 73   
 74    protected static final String[] LEXICAL_HANDLER_NAMES = {
 75    "http://xml.org/sax/properties/lexical-handler",
 76    "http://xml.org/sax/handlers/LexicalHandler"};
 77   
 78    protected static final OutputFormat DEFAULT_FORMAT = new OutputFormat();
 79   
 80    /** Should entityRefs by resolved when writing ? */
 81    private boolean resolveEntityRefs = true;
 82   
 83    /**
 84    * Stores the last type of node written so algorithms can refer to the
 85    * previous node type
 86    */
 87    protected int lastOutputNodeType;
 88   
 89    /**
 90    * Stores if the last written element node was a closing tag or an opening
 91    * tag.
 92    */
 93    private boolean lastElementClosed = false;
 94   
 95    /** Stores the xml:space attribute value of preserve for whitespace flag */
 96    protected boolean preserve = false;
 97   
 98    /** The Writer used to output to */
 99    protected Writer writer;
 100   
 101    /** The Stack of namespaceStack written so far */
 102    private NamespaceStack namespaceStack = new NamespaceStack();
 103   
 104    /** The format used by this writer */
 105    private OutputFormat format;
 106   
 107    /** whether we should escape text */
 108    private boolean escapeText = true;
 109   
 110    /**
 111    * The initial number of indentations (so you can print a whole document
 112    * indented, if you like)
 113    */
 114    private int indentLevel = 0;
 115   
 116    /** buffer used when escaping strings */
 117    private StringBuffer buffer = new StringBuffer();
 118   
 119    /**
 120    * whether we have added characters before from the same chunk of characters
 121    */
 122    private boolean charsAdded = false;
 123   
 124    private char lastChar;
 125   
 126    /** Whether a flush should occur after writing a document */
 127    private boolean autoFlush;
 128   
 129    /** Lexical handler we should delegate to */
 130    private LexicalHandler lexicalHandler;
 131   
 132    /**
 133    * Whether comments should appear inside DTD declarations - defaults to
 134    * false
 135    */
 136    private boolean showCommentsInDTDs;
 137   
 138    /** Is the writer curerntly inside a DTD definition? */
 139    private boolean inDTD;
 140   
 141    /** The namespaces used for the current element when consuming SAX events */
 142    private Map namespacesMap;
 143   
 144    /**
 145    * what is the maximum allowed character code such as 127 in US-ASCII (7
 146    * bit) or 255 in ISO- (8 bit) or -1 to not escape any characters (other
 147    * than the special XML characters like &lt; &gt; &amp;)
 148    */
 149    private int maximumAllowedCharacter;
 150   
 151  18 public XMLWriter(Writer writer) {
 152  18 this(writer, DEFAULT_FORMAT);
 153    }
 154   
 155  2143 public XMLWriter(Writer writer, OutputFormat format) {
 156  2143 this.writer = writer;
 157  2143 this.format = format;
 158  2143 namespaceStack.push(Namespace.NO_NAMESPACE);
 159    }
 160   
 161  2 public XMLWriter() {
 162  2 this.format = DEFAULT_FORMAT;
 163  2 this.writer = new BufferedWriter(new OutputStreamWriter(System.out));
 164  2 this.autoFlush = true;
 165  2 namespaceStack.push(Namespace.NO_NAMESPACE);
 166    }
 167   
 168  2 public XMLWriter(OutputStream out) throws UnsupportedEncodingException {
 169  2 this.format = DEFAULT_FORMAT;
 170  2 this.writer = createWriter(out, format.getEncoding());
 171  2 this.autoFlush = true;
 172  2 namespaceStack.push(Namespace.NO_NAMESPACE);
 173    }
 174   
 175  7 public XMLWriter(OutputStream out, OutputFormat format)
 176    throws UnsupportedEncodingException {
 177  7 this.format = format;
 178  7 this.writer = createWriter(out, format.getEncoding());
 179  7 this.autoFlush = true;
 180  7 namespaceStack.push(Namespace.NO_NAMESPACE);
 181    }
 182   
 183  8 public XMLWriter(OutputFormat format) throws UnsupportedEncodingException {
 184  8 this.format = format;
 185  8 this.writer = createWriter(System.out, format.getEncoding());
 186  8 this.autoFlush = true;
 187  8 namespaceStack.push(Namespace.NO_NAMESPACE);
 188    }
 189   
 190  2 public void setWriter(Writer writer) {
 191  2 this.writer = writer;
 192  2 this.autoFlush = false;
 193    }
 194   
 195  8 public void setOutputStream(OutputStream out)
 196    throws UnsupportedEncodingException {
 197  8 this.writer = createWriter(out, format.getEncoding());
 198  8 this.autoFlush = true;
 199    }
 200   
 201    /**
 202    * DOCUMENT ME!
 203    *
 204    * @return true if text thats output should be escaped. This is enabled by
 205    * default. It could be disabled if the output format is textual,
 206    * like in XSLT where we can have xml, html or text output.
 207    */
 208  0 public boolean isEscapeText() {
 209  0 return escapeText;
 210    }
 211   
 212    /**
 213    * Sets whether text output should be escaped or not. This is enabled by
 214    * default. It could be disabled if the output format is textual, like in
 215    * XSLT where we can have xml, html or text output.
 216    *
 217    * @param escapeText
 218    * DOCUMENT ME!
 219    */
 220  1 public void setEscapeText(boolean escapeText) {
 221  1 this.escapeText = escapeText;
 222    }
 223   
 224    /**
 225    * Set the initial indentation level. This can be used to output a document
 226    * (or, more likely, an element) starting at a given indent level, so it's
 227    * not always flush against the left margin. Default: 0
 228    *
 229    * @param indentLevel
 230    * the number of indents to start with
 231    */
 232  0 public void setIndentLevel(int indentLevel) {
 233  0 this.indentLevel = indentLevel;
 234    }
 235   
 236    /**
 237    * Returns the maximum allowed character code that should be allowed
 238    * unescaped which defaults to 127 in US-ASCII (7 bit) or 255 in ISO- (8
 239    * bit).
 240    *
 241    * @return DOCUMENT ME!
 242    */
 243  484095 public int getMaximumAllowedCharacter() {
 244  484095 if (maximumAllowedCharacter == 0) {
 245  2147 maximumAllowedCharacter = defaultMaximumAllowedCharacter();
 246    }
 247   
 248  484095 return maximumAllowedCharacter;
 249    }
 250   
 251    /**
 252    * Sets the maximum allowed character code that should be allowed unescaped
 253    * such as 127 in US-ASCII (7 bit) or 255 in ISO- (8 bit) or -1 to not
 254    * escape any characters (other than the special XML characters like &lt;
 255    * &gt; &amp;) If this is not explicitly set then it is defaulted from the
 256    * encoding.
 257    *
 258    * @param maximumAllowedCharacter
 259    * The maximumAllowedCharacter to set
 260    */
 261  1 public void setMaximumAllowedCharacter(int maximumAllowedCharacter) {
 262  1 this.maximumAllowedCharacter = maximumAllowedCharacter;
 263    }
 264   
 265    /**
 266    * Flushes the underlying Writer
 267    *
 268    * @throws IOException
 269    * DOCUMENT ME!
 270    */
 271  2119 public void flush() throws IOException {
 272  2119 writer.flush();
 273    }
 274   
 275    /**
 276    * Closes the underlying Writer
 277    *
 278    * @throws IOException
 279    * DOCUMENT ME!
 280    */
 281  21 public void close() throws IOException {
 282  21 writer.close();
 283    }
 284   
 285    /**
 286    * Writes the new line text to the underlying Writer
 287    *
 288    * @throws IOException
 289    * DOCUMENT ME!
 290    */
 291  113 public void println() throws IOException {
 292  113 writer.write(format.getLineSeparator());
 293    }
 294   
 295    /**
 296    * Writes the given {@link Attribute}.
 297    *
 298    * @param attribute
 299    * <code>Attribute</code> to output.
 300    *
 301    * @throws IOException
 302    * DOCUMENT ME!
 303    */
 304  0 public void write(Attribute attribute) throws IOException {
 305  0 writeAttribute(attribute);
 306   
 307  0 if (autoFlush) {
 308  0 flush();
 309    }
 310    }
 311   
 312    /**
 313    * <p>
 314    * This will print the <code>Document</code> to the current Writer.
 315    * </p>
 316    *
 317    * <p>
 318    * Warning: using your own Writer may cause the writer's preferred character
 319    * encoding to be ignored. If you use encodings other than UTF8, we
 320    * recommend using the method that takes an OutputStream instead.
 321    * </p>
 322    *
 323    * <p>
 324    * Note: as with all Writers, you may need to flush() yours after this
 325    * method returns.
 326    * </p>
 327    *
 328    * @param doc
 329    * <code>Document</code> to format.
 330    *
 331    * @throws IOException
 332    * if there's any problem writing.
 333    */
 334  113 public void write(Document doc) throws IOException {
 335  113 writeDeclaration();
 336   
 337  113 if (doc.getDocType() != null) {
 338  1 indent();
 339  1 writeDocType(doc.getDocType());
 340    }
 341   
 342  113 for (int i = 0, size = doc.nodeCount(); i < size; i++) {
 343  116 Node node = doc.node(i);
 344  116 writeNode(node);
 345    }
 346   
 347  113 writePrintln();
 348   
 349  113 if (autoFlush) {
 350  16 flush();
 351    }
 352    }
 353   
 354    /**
 355    * <p>
 356    * Writes the <code>{@link Element}</code>, including its <code>{@link
 357    * Attribute}</code>
 358    * s, and its value, and all its content (child nodes) to the current
 359    * Writer.
 360    * </p>
 361    *
 362    * @param element
 363    * <code>Element</code> to output.
 364    *
 365    * @throws IOException
 366    * DOCUMENT ME!
 367    */
 368  2043 public void write(Element element) throws IOException {
 369  2043 writeElement(element);
 370   
 371  2043 if (autoFlush) {
 372  2 flush();
 373    }
 374    }
 375   
 376    /**
 377    * Writes the given {@link CDATA}.
 378    *
 379    * @param cdata
 380    * <code>CDATA</code> to output.
 381    *
 382    * @throws IOException
 383    * DOCUMENT ME!
 384    */
 385  0 public void write(CDATA cdata) throws IOException {
 386  0 writeCDATA(cdata.getText());
 387   
 388  0 if (autoFlush) {
 389  0 flush();
 390    }
 391    }
 392   
 393    /**
 394    * Writes the given {@link Comment}.
 395    *
 396    * @param comment
 397    * <code>Comment</code> to output.
 398    *
 399    * @throws IOException
 400    * DOCUMENT ME!
 401    */
 402  0 public void write(Comment comment) throws IOException {
 403  0 writeComment(comment.getText());
 404   
 405  0 if (autoFlush) {
 406  0 flush();
 407    }
 408    }
 409   
 410    /**
 411    * Writes the given {@link DocumentType}.
 412    *
 413    * @param docType
 414    * <code>DocumentType</code> to output.
 415    *
 416    * @throws IOException
 417    * DOCUMENT ME!
 418    */
 419  0 public void write(DocumentType docType) throws IOException {
 420  0 writeDocType(docType);
 421   
 422  0 if (autoFlush) {
 423  0 flush();
 424    }
 425    }
 426   
 427    /**
 428    * Writes the given {@link Entity}.
 429    *
 430    * @param entity
 431    * <code>Entity</code> to output.
 432    *
 433    * @throws IOException
 434    * DOCUMENT ME!
 435    */
 436  0 public void write(Entity entity) throws IOException {
 437  0 writeEntity(entity);
 438   
 439  0 if (autoFlush) {
 440  0 flush();
 441    }
 442    }
 443   
 444    /**
 445    * Writes the given {@link Namespace}.
 446    *
 447    * @param namespace
 448    * <code>Namespace</code> to output.
 449    *
 450    * @throws IOException
 451    * DOCUMENT ME!
 452    */
 453  0 public void write(Namespace namespace) throws IOException {
 454  0 writeNamespace(namespace);
 455   
 456  0 if (autoFlush) {
 457  0 flush();
 458    }
 459    }
 460   
 461    /**
 462    * Writes the given {@link ProcessingInstruction}.
 463    *
 464    * @param processingInstruction
 465    * <code>ProcessingInstruction</code> to output.
 466    *
 467    * @throws IOException
 468    * DOCUMENT ME!
 469    */
 470  0 public void write(ProcessingInstruction processingInstruction)
 471    throws IOException {
 472  0 writeProcessingInstruction(processingInstruction);
 473   
 474  0 if (autoFlush) {
 475  0 flush();
 476    }
 477    }
 478   
 479    /**
 480    * <p>
 481    * Print out a {@link String}, Perfoms the necessary entity escaping and
 482    * whitespace stripping.
 483    * </p>
 484    *
 485    * @param text
 486    * is the text to output
 487    *
 488    * @throws IOException
 489    * DOCUMENT ME!
 490    */
 491  0 public void write(String text) throws IOException {
 492  0 writeString(text);
 493   
 494  0 if (autoFlush) {
 495  0 flush();
 496    }
 497    }
 498   
 499    /**
 500    * Writes the given {@link Text}.
 501    *
 502    * @param text
 503    * <code>Text</code> to output.
 504    *
 505    * @throws IOException
 506    * DOCUMENT ME!
 507    */
 508  0 public void write(Text text) throws IOException {
 509  0 writeString(text.getText());
 510   
 511  0 if (autoFlush) {
 512  0 flush();
 513    }
 514    }
 515   
 516    /**
 517    * Writes the given {@link Node}.
 518    *
 519    * @param node
 520    * <code>Node</code> to output.
 521    *
 522    * @throws IOException
 523    * DOCUMENT ME!
 524    */
 525  1 public void write(Node node) throws IOException {
 526  1 writeNode(node);
 527   
 528  1 if (autoFlush) {
 529  0 flush();
 530    }
 531    }
 532   
 533    /**
 534    * Writes the given object which should be a String, a Node or a List of
 535    * Nodes.
 536    *
 537    * @param object
 538    * is the object to output.
 539    *
 540    * @throws IOException
 541    * DOCUMENT ME!
 542    */
 543  1 public void write(Object object) throws IOException {
 544  1 if (object instanceof Node) {
 545  1 write((Node) object);
 546  0 } else if (object instanceof String) {
 547  0 write((String) object);
 548  0 } else if (object instanceof List) {
 549  0 List list = (List) object;
 550   
 551  0 for (int i = 0, size = list.size(); i < size; i++) {
 552  0 write(list.get(i));
 553    }
 554  0 } else if (object != null) {
 555  0 throw new IOException("Invalid object: " + object);
 556    }
 557    }
 558   
 559    /**
 560    * <p>
 561    * Writes the opening tag of an {@link Element}, including its {@link
 562    * Attribute}s but without its content.
 563    * </p>
 564    *
 565    * @param element
 566    * <code>Element</code> to output.
 567    *
 568    * @throws IOException
 569    * DOCUMENT ME!
 570    */
 571  0 public void writeOpen(Element element) throws IOException {
 572  0 writer.write("<");
 573  0 writer.write(element.getQualifiedName());
 574  0 writeAttributes(element);
 575  0 writer.write(">");
 576    }
 577   
 578    /**
 579    * <p>
 580    * Writes the closing tag of an {@link Element}
 581    * </p>
 582    *
 583    * @param element
 584    * <code>Element</code> to output.
 585    *
 586    * @throws IOException
 587    * DOCUMENT ME!
 588    */
 589  0 public void writeClose(Element element) throws IOException {
 590  0 writeClose(element.getQualifiedName());
 591    }
 592   
 593    // XMLFilterImpl methods
 594    // -------------------------------------------------------------------------
 595  0 public void parse(InputSource source) throws IOException, SAXException {
 596  0 installLexicalHandler();
 597  0 super.parse(source);
 598    }
 599   
 600  0 public void setProperty(String name, Object value)
 601    throws SAXNotRecognizedException, SAXNotSupportedException {
 602  0 for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
 603  0 if (LEXICAL_HANDLER_NAMES[i].equals(name)) {
 604  0 setLexicalHandler((LexicalHandler) value);
 605   
 606  0 return;
 607    }
 608    }
 609   
 610  0 super.setProperty(name, value);
 611    }
 612   
 613  0 public Object getProperty(String name) throws SAXNotRecognizedException,
 614    SAXNotSupportedException {
 615  0 for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
 616  0 if (LEXICAL_HANDLER_NAMES[i].equals(name)) {
 617  0 return getLexicalHandler();
 618    }
 619    }
 620   
 621  0 return super.getProperty(name);
 622    }
 623   
 624  0 public void setLexicalHandler(LexicalHandler handler) {
 625  0 if (handler == null) {
 626  0 throw new NullPointerException("Null lexical handler");
 627    } else {
 628  0 this.lexicalHandler = handler;
 629    }
 630    }
 631   
 632  0 public LexicalHandler getLexicalHandler() {
 633  0 return lexicalHandler;
 634    }
 635   
 636    // ContentHandler interface
 637    // -------------------------------------------------------------------------
 638  1 public void setDocumentLocator(Locator locator) {
 639  1 super.setDocumentLocator(locator);
 640    }
 641   
 642  4 public void startDocument() throws SAXException {
 643  4 try {
 644  4 writeDeclaration();
 645  4 super.startDocument();
 646    } catch (IOException e) {
 647  0 handleException(e);
 648    }
 649    }
 650   
 651  4 public void endDocument() throws SAXException {
 652  4 super.endDocument();
 653   
 654  4 if (autoFlush) {
 655  0 try {
 656  0 flush();
 657    } catch (IOException e) {
 658    }
 659    }
 660    }
 661   
 662  0 public void startPrefixMapping(String prefix, String uri)
 663    throws SAXException {
 664  0 if (namespacesMap == null) {
 665  0 namespacesMap = new HashMap();
 666    }
 667   
 668  0 namespacesMap.put(prefix, uri);
 669  0 super.startPrefixMapping(prefix, uri);
 670    }
 671   
 672  0 public void endPrefixMapping(String prefix) throws SAXException {
 673  0 super.endPrefixMapping(prefix);
 674    }
 675   
 676  8 public void startElement(String namespaceURI, String localName,
 677    String qName, Attributes attributes) throws SAXException {
 678  8 try {
 679  8 charsAdded = false;
 680   
 681  8 writePrintln();
 682  8 indent();
 683  8 writer.write("<");
 684  8 writer.write(qName);
 685  8 writeNamespaces();
 686  8 writeAttributes(attributes);
 687  8 writer.write(">");
 688  8 ++indentLevel;
 689  8 lastOutputNodeType = Node.ELEMENT_NODE;
 690  8 lastElementClosed = false;
 691   
 692  8 super.startElement(namespaceURI, localName, qName, attributes);
 693    } catch (IOException e) {
 694  0 handleException(e);
 695    }
 696    }
 697   
 698  8 public void endElement(String namespaceURI, String localName, String qName)
 699    throws SAXException {
 700  8 try {
 701  8 charsAdded = false;
 702  8 --indentLevel;
 703   
 704  8 if (lastElementClosed) {
 705  4 writePrintln();
 706  4 indent();
 707    }
 708   
 709    // XXXX: need to determine this using a stack and checking for
 710    // content / children
 711  8 boolean hadContent = true;
 712   
 713  8 if (hadContent) {
 714  8 writeClose(qName);
 715    } else {
 716  0 writeEmptyElementClose(qName);
 717    }
 718   
 719  8 lastOutputNodeType = Node.ELEMENT_NODE;
 720  8 lastElementClosed = true;
 721   
 722  8 super.endElement(namespaceURI, localName, qName);
 723    } catch (IOException e) {
 724  0 handleException(e);
 725    }
 726    }
 727   
 728  16 public void characters(char[] ch, int start, int length)
 729    throws SAXException {
 730  16 if ((ch == null) || (ch.length == 0) || (length <= 0)) {
 731  1 return;
 732    }
 733   
 734  15 try {
 735    /*
 736    * we can't use the writeString method here because it's possible we
 737    * don't receive all characters at once and calling writeString
 738    * would cause unwanted spaces to be added in between these chunks
 739    * of character arrays.
 740    */
 741  15 String string = String.valueOf(ch, start, length);
 742   
 743  15 if (escapeText) {
 744  14 string = escapeElementEntities(string);
 745    }
 746   
 747  15 if (format.isTrimText()) {
 748  14 if ((lastOutputNodeType == Node.TEXT_NODE) && !charsAdded) {
 749  0 writer.write(' ');
 750  14 } else if (charsAdded && Character.isWhitespace(lastChar)) {
 751  1 writer.write(' ');
 752  13 } else if (lastOutputNodeType == Node.ELEMENT_NODE
 753    && format.isPadText() && lastElementClosed
 754    && Character.isWhitespace(ch[0])) {
 755  1 writer.write(PAD_TEXT);
 756    }
 757   
 758  14 String delim = "";
 759  14 StringTokenizer tokens = new StringTokenizer(string);
 760   
 761  14 while (tokens.hasMoreTokens()) {
 762  17 writer.write(delim);
 763  17 writer.write(tokens.nextToken());
 764  17 delim = " ";
 765    }
 766    } else {
 767  1 writer.write(string);
 768    }
 769   
 770  15 charsAdded = true;
 771  15 lastChar = ch[(start + length) - 1];
 772  15 lastOutputNodeType = Node.TEXT_NODE;
 773   
 774  15 super.characters(ch, start, length);
 775    } catch (IOException e) {
 776  0 handleException(e);
 777    }
 778    }
 779   
 780  0 public void ignorableWhitespace(char[] ch, int start, int length)
 781    throws SAXException {
 782  0 super.ignorableWhitespace(ch, start, length);
 783    }
 784   
 785  0 public void processingInstruction(String target, String data)
 786    throws SAXException {
 787  0 try {
 788  0 indent();
 789  0 writer.write("<?");
 790  0 writer.write(target);
 791  0 writer.write(" ");
 792  0 writer.write(data);
 793  0 writer.write("?>");
 794  0 writePrintln();
 795  0 lastOutputNodeType = Node.PROCESSING_INSTRUCTION_NODE;
 796   
 797  0 super.processingInstruction(target, data);
 798    } catch (IOException e) {
 799  0 handleException(e);
 800    }
 801    }
 802   
 803    // DTDHandler interface
 804    // -------------------------------------------------------------------------
 805  0 public void notationDecl(String name, String publicID, String systemID)
 806    throws SAXException {
 807  0 super.notationDecl(name, publicID, systemID);
 808    }
 809   
 810  0 public void unparsedEntityDecl(String name, String publicID,
 811    String systemID, String notationName) throws SAXException {
 812  0 super.unparsedEntityDecl(name, publicID, systemID, notationName);
 813    }
 814   
 815    // LexicalHandler interface
 816    // -------------------------------------------------------------------------
 817  0 public void startDTD(String name, String publicID, String systemID)
 818    throws SAXException {
 819  0 inDTD = true;
 820   
 821  0 try {
 822  0 writeDocType(name, publicID, systemID);
 823    } catch (IOException e) {
 824  0 handleException(e);
 825    }
 826   
 827  0 if (lexicalHandler != null) {
 828  0 lexicalHandler.startDTD(name, publicID, systemID);
 829    }
 830    }
 831   
 832  0 public void endDTD() throws SAXException {
 833  0 inDTD = false;
 834   
 835  0 if (lexicalHandler != null) {
 836  0 lexicalHandler.endDTD();
 837    }
 838    }
 839   
 840  0 public void startCDATA() throws SAXException {
 841  0 try {
 842  0 writer.write("<![CDATA[");
 843    } catch (IOException e) {
 844  0 handleException(e);
 845    }
 846   
 847  0 if (lexicalHandler != null) {
 848  0 lexicalHandler.startCDATA();
 849    }
 850    }
 851   
 852  0 public void endCDATA() throws SAXException {
 853  0 try {
 854  0 writer.write("]]>");
 855    } catch (IOException e) {
 856  0 handleException(e);
 857    }
 858   
 859  0 if (lexicalHandler != null) {
 860  0 lexicalHandler.endCDATA();
 861    }
 862    }
 863   
 864  0 public void startEntity(String name) throws SAXException {
 865  0 try {
 866  0 writeEntityRef(name);
 867    } catch (IOException e) {
 868  0 handleException(e);
 869    }
 870   
 871  0 if (lexicalHandler != null) {
 872  0 lexicalHandler.startEntity(name);
 873    }
 874    }
 875   
 876  0 public void endEntity(String name) throws SAXException {
 877  0 if (lexicalHandler != null) {
 878  0 lexicalHandler.endEntity(name);
 879    }
 880    }
 881   
 882  0 public void comment(char[] ch, int start, int length) throws SAXException {
 883  0 if (showCommentsInDTDs || !inDTD) {
 884  0 try {
 885  0 charsAdded = false;
 886  0 writeComment(new String(ch, start, length));
 887    } catch (IOException e) {
 888  0 handleException(e);
 889    }
 890    }
 891   
 892  0 if (lexicalHandler != null) {
 893  0 lexicalHandler.comment(ch, start, length);
 894    }
 895    }
 896   
 897    // Implementation methods
 898    // -------------------------------------------------------------------------
 899  20077 protected void writeElement(Element element) throws IOException {
 900  20077 int size = element.nodeCount();
 901  20077 String qualifiedName = element.getQualifiedName();
 902   
 903  20077 writePrintln();
 904  20077 indent();
 905   
 906  20077 writer.write("<");
 907  20077 writer.write(qualifiedName);
 908   
 909  20077 int previouslyDeclaredNamespaces = namespaceStack.size();
 910  20077 Namespace ns = element.getNamespace();
 911   
 912  20077 if (isNamespaceDeclaration(ns)) {
 913  602 namespaceStack.push(ns);
 914  602 writeNamespace(ns);
 915    }
 916   
 917    // Print out additional namespace declarations
 918  20077 boolean textOnly = true;
 919   
 920  20077 for (int i = 0; i < size; i++) {
 921  56305 Node node = element.node(i);
 922   
 923  56305 if (node instanceof Namespace) {
 924  198 Namespace additional = (Namespace) node;
 925   
 926  198 if (isNamespaceDeclaration(additional)) {
 927  16 namespaceStack.push(additional);
 928  16 writeNamespace(additional);
 929    }
 930  56107 } else if (node instanceof Element) {
 931  17921 textOnly = false;
 932  38186 } else if (node instanceof Comment) {
 933  445 textOnly = false;
 934    }
 935    }
 936   
 937  20077 writeAttributes(element);
 938   
 939  20077 lastOutputNodeType = Node.ELEMENT_NODE;
 940   
 941  20077 if (size <= 0) {
 942  300 writeEmptyElementClose(qualifiedName);
 943    } else {
 944  19777 writer.write(">");
 945   
 946  19777 if (textOnly) {
 947    // we have at least one text node so lets assume
 948    // that its non-empty
 949  14954 writeElementContent(element);
 950    } else {
 951    // we know it's not null or empty from above
 952  4823 ++indentLevel;
 953   
 954  4823 writeElementContent(element);
 955   
 956  4823 --indentLevel;
 957   
 958  4823 writePrintln();
 959  4823 indent();
 960    }
 961   
 962  19777 writer.write("</");
 963  19777 writer.write(qualifiedName);
 964  19777 writer.write(">");
 965    }
 966   
 967    // remove declared namespaceStack from stack
 968  20077 while (namespaceStack.size() > previouslyDeclaredNamespaces) {
 969  620 namespaceStack.pop();
 970    }
 971   
 972  20077 lastOutputNodeType = Node.ELEMENT_NODE;
 973    }
 974   
 975    /**
 976    * Determines if element is a special case of XML elements where it contains
 977    * an xml:space attribute of "preserve". If it does, then retain whitespace.
 978    *
 979    * @param element
 980    * DOCUMENT ME!
 981    *
 982    * @return DOCUMENT ME!
 983    */
 984  93 protected final boolean isElementSpacePreserved(Element element) {
 985  93 final Attribute attr = (Attribute) element.attribute("space");
 986  93 boolean preserveFound = preserve; // default to global state
 987   
 988  93 if (attr != null) {
 989  5 if ("xml".equals(attr.getNamespacePrefix())
 990    && "preserve".equals(attr.getText())) {
 991  4 preserveFound = true;
 992    } else {
 993  1 preserveFound = false;
 994    }
 995    }
 996   
 997  93 return preserveFound;
 998    }
 999   
 1000    /**
 1001    * Outputs the content of the given element. If whitespace trimming is
 1002    * enabled then all adjacent text nodes are appended together before the
 1003    * whitespace trimming occurs to avoid problems with multiple text nodes
 1004    * being created due to text content that spans parser buffers in a SAX
 1005    * parser.
 1006    *
 1007    * @param element
 1008    * DOCUMENT ME!
 1009    *
 1010    * @throws IOException
 1011    * DOCUMENT ME!
 1012    */
 1013  19777 protected void writeElementContent(Element element) throws IOException {
 1014  19777 boolean trim = format.isTrimText();
 1015  19777 boolean oldPreserve = preserve;
 1016   
 1017  19777 if (trim) { // verify we have to before more expensive test
 1018  93 preserve = isElementSpacePreserved(element);
 1019  93 trim = !preserve;
 1020    }
 1021   
 1022  19777 if (trim) {
 1023    // concatenate adjacent text nodes together
 1024    // so that whitespace trimming works properly
 1025  82 Text lastTextNode = null;
 1026  82 StringBuffer buff = null;
 1027  82 boolean textOnly = true;
 1028   
 1029  82 for (int i = 0, size = element.nodeCount(); i < size; i++) {
 1030  146 Node node = element.node(i);
 1031   
 1032  146 if (node instanceof Text) {
 1033  76 if (lastTextNode == null) {
 1034  53 lastTextNode = (Text) node;
 1035    } else {
 1036  23 if (buff == null) {
 1037  9 buff = new StringBuffer(lastTextNode.getText());
 1038    }
 1039   
 1040  23 buff.append(((Text) node).getText());
 1041    }
 1042    } else {
 1043  70 if (!textOnly && format.isPadText()) {
 1044    // only add the PAD_TEXT if the text itself starts with
 1045    // whitespace
 1046  7 char firstChar = 'a';
 1047  7 if (buff != null) {
 1048  0 firstChar = buff.charAt(0);
 1049  7 } else if (lastTextNode != null) {
 1050  0 firstChar = lastTextNode.getText().charAt(0);
 1051    }
 1052   
 1053  7 if (Character.isWhitespace(firstChar)) {
 1054  0 writer.write(PAD_TEXT);
 1055    }
 1056    }
 1057   
 1058  70 if (lastTextNode != null) {
 1059  17 if (buff != null) {
 1060  2 writeString(buff.toString());
 1061  2 buff = null;
 1062    } else {
 1063  15 writeString(lastTextNode.getText());
 1064    }
 1065   
 1066  17 if (format.isPadText()) {
 1067    // only add the PAD_TEXT if the text itself ends
 1068    // with whitespace
 1069  10 char lastTextChar = 'a';
 1070  10 if (buff != null) {
 1071  0 lastTextChar = buff.charAt(buff.length() - 1);
 1072  10 } else if (lastTextNode != null) {
 1073  10 String txt = lastTextNode.getText();
 1074  10 lastTextChar = txt.charAt(txt.length() - 1);
 1075    }
 1076   
 1077  10 if (Character.isWhitespace(lastTextChar)) {
 1078  1 writer.write(PAD_TEXT);
 1079    }
 1080    }
 1081   
 1082  17 lastTextNode = null;
 1083    }
 1084   
 1085  70 textOnly = false;
 1086  70 writeNode(node);
 1087    }
 1088    }
 1089   
 1090  82 if (lastTextNode != null) {
 1091  36 if (!textOnly && format.isPadText()) {
 1092    // only add the PAD_TEXT if the text itself starts with
 1093    // whitespace
 1094  3 char firstChar = 'a';
 1095  3 if (buff != null) {
 1096  0 firstChar = buff.charAt(0);
 1097    } else {
 1098  3 firstChar = lastTextNode.getText().charAt(0);
 1099    }
 1100   
 1101  3 if (Character.isWhitespace(firstChar)) {
 1102  2 writer.write(PAD_TEXT);
 1103    }
 1104    }
 1105   
 1106  36 if (buff != null) {
 1107  7 writeString(buff.toString());
 1108  7 buff = null;
 1109    } else {
 1110  29 writeString(lastTextNode.getText());
 1111    }
 1112   
 1113  36 lastTextNode = null;
 1114    }
 1115    } else {
 1116  19695 Node lastTextNode = null;
 1117   
 1118  19695 for (int i = 0, size = element.nodeCount(); i < size; i++) {
 1119  56159 Node node = element.node(i);
 1120   
 1121  56159 if (node instanceof Text) {
 1122  37643 writeNode(node);
 1123  37643 lastTextNode = node;
 1124    } else {
 1125  18516 if ((lastTextNode != null) && format.isPadText()) {
 1126    // only add the PAD_TEXT if the text itself ends with
 1127    // whitespace
 1128  0 String txt = lastTextNode.getText();
 1129  0 char lastTextChar = txt.charAt(txt.length() - 1);
 1130   
 1131  0 if (Character.isWhitespace(lastTextChar)) {
 1132  0 writer.write(PAD_TEXT);
 1133    }
 1134    }
 1135   
 1136  18516 writeNode(node);
 1137   
 1138    // if ((lastTextNode != null) && format.isPadText()) {
 1139    // writer.write(PAD_TEXT);
 1140    // }
 1141   
 1142  18516 lastTextNode = null;
 1143    }
 1144    }
 1145    }
 1146   
 1147  19777 preserve = oldPreserve;
 1148    }
 1149   
 1150  21 protected void writeCDATA(String text) throws IOException {
 1151  21 writer.write("<![CDATA[");
 1152   
 1153  21 if (text != null) {
 1154  17 writer.write(text);
 1155    }
 1156   
 1157  21 writer.write("]]>");
 1158   
 1159  21 lastOutputNodeType = Node.CDATA_SECTION_NODE;
 1160    }
 1161   
 1162  1 protected void writeDocType(DocumentType docType) throws IOException {
 1163  1 if (docType != null) {
 1164  1 docType.write(writer);
 1165  1 writePrintln();
 1166    }
 1167    }
 1168   
 1169  618 protected void writeNamespace(Namespace namespace) throws IOException {
 1170  618 if (namespace != null) {
 1171  618 writeNamespace(namespace.getPrefix(), namespace.getURI());
 1172    }
 1173    }
 1174   
 1175    /**
 1176    * Writes the SAX namepsaces
 1177    *
 1178    * @throws IOException
 1179    * DOCUMENT ME!
 1180    */
 1181  8 protected void writeNamespaces() throws IOException {
 1182  8 if (namespacesMap != null) {
 1183  0 for (Iterator iter = namespacesMap.entrySet().iterator(); iter
 1184    .hasNext();) {
 1185  0 Map.Entry entry = (Map.Entry) iter.next();
 1186  0 String prefix = (String) entry.getKey();
 1187  0 String uri = (String) entry.getValue();
 1188  0 writeNamespace(prefix, uri);
 1189    }
 1190   
 1191  0 namespacesMap = null;
 1192    }
 1193    }
 1194   
 1195    /**
 1196    * Writes the SAX namepsaces
 1197    *
 1198    * @param prefix
 1199    * the prefix
 1200    * @param uri
 1201    * the namespace uri
 1202    *
 1203    * @throws IOException
 1204    */
 1205  620 protected void writeNamespace(String prefix, String uri)
 1206    throws IOException {
 1207  620 if ((prefix != null) && (prefix.length() > 0)) {
 1208  566 writer.write(" xmlns:");
 1209  566 writer.write(prefix);
 1210  566 writer.write("=\"");
 1211    } else {
 1212  54 writer.write(" xmlns=\"");
 1213    }
 1214   
 1215  620 writer.write(uri);
 1216  620 writer.write("\"");
 1217    }
 1218   
 1219  1 protected void writeProcessingInstruction(ProcessingInstruction pi)
 1220    throws IOException {
 1221    // indent();
 1222  1 writer.write("<?");
 1223  1 writer.write(pi.getName());
 1224  1 writer.write(" ");
 1225  1 writer.write(pi.getText());
 1226  1 writer.write("?>");
 1227  1 writePrintln();
 1228   
 1229  1 lastOutputNodeType = Node.PROCESSING_INSTRUCTION_NODE;
 1230    }
 1231   
 1232  53 protected void writeString(String text) throws IOException {
 1233  53 if ((text != null) && (text.length() > 0)) {
 1234  51 if (escapeText) {
 1235  51 text = escapeElementEntities(text);
 1236    }
 1237   
 1238    // if (format.isPadText()) {
 1239    // if (lastOutputNodeType == Node.ELEMENT_NODE) {
 1240    // writer.write(PAD_TEXT);
 1241    // }
 1242    // }
 1243  51 if (format.isTrimText()) {
 1244  51 boolean first = true;
 1245  51 StringTokenizer tokenizer = new StringTokenizer(text);
 1246   
 1247  51 while (tokenizer.hasMoreTokens()) {
 1248  134 String token = tokenizer.nextToken();
 1249   
 1250  134 if (first) {
 1251  47 first = false;
 1252   
 1253  47 if (lastOutputNodeType == Node.TEXT_NODE) {
 1254  0 writer.write(" ");
 1255    }
 1256    } else {
 1257  87 writer.write(" ");
 1258    }
 1259   
 1260  134 writer.write(token);
 1261  134 lastOutputNodeType = Node.TEXT_NODE;
 1262  134 lastChar = token.charAt(token.length() - 1);
 1263    }
 1264    } else {
 1265  0 lastOutputNodeType = Node.TEXT_NODE;
 1266  0 writer.write(text);
 1267  0 lastChar = text.charAt(text.length() - 1);
 1268    }
 1269    }
 1270    }
 1271   
 1272    /**
 1273    * This method is used to write out Nodes that contain text and still allow
 1274    * for xml:space to be handled properly.
 1275    *
 1276    * @param node
 1277    * DOCUMENT ME!
 1278    *
 1279    * @throws IOException
 1280    * DOCUMENT ME!
 1281    */
 1282  37643 protected void writeNodeText(Node node) throws IOException {
 1283  37643 String text = node.getText();
 1284   
 1285  37643 if ((text != null) && (text.length() > 0)) {
 1286  37643 if (escapeText) {
 1287  37643 text = escapeElementEntities(text);
 1288    }
 1289   
 1290  37643 lastOutputNodeType = Node.TEXT_NODE;
 1291  37643 writer.write(text);
 1292  37643 lastChar = text.charAt(text.length() - 1);
 1293    }
 1294    }
 1295   
 1296  56346 protected void writeNode(Node node) throws IOException {
 1297  56346 int nodeType = node.getNodeType();
 1298   
 1299  56346 switch (nodeType) {
 1300  18034 case Node.ELEMENT_NODE:
 1301  18034 writeElement((Element) node);
 1302   
 1303  18034 break;
 1304   
 1305  0 case Node.ATTRIBUTE_NODE:
 1306  0 writeAttribute((Attribute) node);
 1307   
 1308  0 break;
 1309   
 1310  37643 case Node.TEXT_NODE:
 1311  37643 writeNodeText(node);
 1312   
 1313    // write((Text) node);
 1314  37643 break;
 1315   
 1316  22 case Node.CDATA_SECTION_NODE:
 1317  22 writeCDATA(node.getText());
 1318   
 1319  22 break;
 1320   
 1321  0 case Node.ENTITY_REFERENCE_NODE:
 1322  0 writeEntity((Entity) node);
 1323   
 1324  0 break;
 1325   
 1326  1 case Node.PROCESSING_INSTRUCTION_NODE:
 1327  1 writeProcessingInstruction((ProcessingInstruction) node);
 1328   
 1329  1 break;
 1330   
 1331  447 case Node.COMMENT_NODE:
 1332  447 writeComment(node.getText());
 1333   
 1334  447 break;
 1335   
 1336  1 case Node.DOCUMENT_NODE:
 1337  1 write((Document) node);
 1338   
 1339  1 break;
 1340   
 1341  0 case Node.DOCUMENT_TYPE_NODE:
 1342  0 writeDocType((DocumentType) node);
 1343   
 1344  0 break;
 1345   
 1346  198 case Node.NAMESPACE_NODE:
 1347   
 1348    // Will be output with attributes
 1349    // write((Namespace) node);
 1350  198 break;
 1351   
 1352  0 default:
 1353  0 throw new IOException("Invalid node type: " + node);
 1354    }
 1355    }
 1356   
 1357  0 protected void installLexicalHandler() {
 1358  0 XMLReader parent = getParent();
 1359   
 1360  0 if (parent == null) {
 1361  0 throw new NullPointerException("No parent for filter");
 1362    }
 1363   
 1364    // try to register for lexical events
 1365  0 for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
 1366  0 try {
 1367  0 parent.setProperty(LEXICAL_HANDLER_NAMES[i], this);
 1368   
 1369  0 break;
 1370    } catch (SAXNotRecognizedException ex) {
 1371    // ignore
 1372    } catch (SAXNotSupportedException ex) {
 1373    // ignore
 1374    }
 1375    }
 1376    }
 1377   
 1378  0 protected void writeDocType(String name, String publicID, String systemID)
 1379    throws IOException {
 1380  0 boolean hasPublic = false;
 1381   
 1382  0 writer.write("<!DOCTYPE ");
 1383  0 writer.write(name);
 1384   
 1385  0 if ((publicID != null) && (!publicID.equals(""))) {
 1386  0 writer.write(" PUBLIC \"");
 1387  0 writer.write(publicID);
 1388  0 writer.write("\"");
 1389  0 hasPublic = true;
 1390    }
 1391   
 1392  0 if ((systemID != null) && (!systemID.equals(""))) {
 1393  0 if (!hasPublic) {
 1394  0 writer.write(" SYSTEM");
 1395    }
 1396   
 1397  0 writer.write(" \"");
 1398  0 writer.write(systemID);
 1399  0 writer.write("\"");
 1400    }
 1401   
 1402  0 writer.write(">");
 1403  0 writePrintln();
 1404    }
 1405   
 1406  0 protected void writeEntity(Entity entity) throws IOException {
 1407  0 if (!resolveEntityRefs()) {
 1408  0 writeEntityRef(entity.getName());
 1409    } else {
 1410  0 writer.write(entity.getText());
 1411    }
 1412    }
 1413   
 1414  0 protected void writeEntityRef(String name) throws IOException {
 1415  0 writer.write("&");
 1416  0 writer.write(name);
 1417  0 writer.write(";");
 1418   
 1419  0 lastOutputNodeType = Node.ENTITY_REFERENCE_NODE;
 1420    }
 1421   
 1422  447 protected void writeComment(String text) throws IOException {
 1423  447 if (format.isNewlines()) {
 1424  3 println();
 1425  3 indent();
 1426    }
 1427   
 1428  447 writer.write("<!--");
 1429  447 writer.write(text);
 1430  447 writer.write("-->");
 1431   
 1432  447 lastOutputNodeType = Node.COMMENT_NODE;
 1433    }
 1434   
 1435    /**
 1436    * Writes the attributes of the given element
 1437    *
 1438    * @param element
 1439    * DOCUMENT ME!
 1440    *
 1441    * @throws IOException
 1442    * DOCUMENT ME!
 1443    */
 1444  20077 protected void writeAttributes(Element element) throws IOException {
 1445    // I do not yet handle the case where the same prefix maps to
 1446    // two different URIs. For attributes on the same element
 1447    // this is illegal; but as yet we don't throw an exception
 1448    // if someone tries to do this
 1449  20077 for (int i = 0, size = element.attributeCount(); i < size; i++) {
 1450  4276 Attribute attribute = element.attribute(i);
 1451  4276 Namespace ns = attribute.getNamespace();
 1452   
 1453  4276 if ((ns != null) && (ns != Namespace.NO_NAMESPACE)
 1454    && (ns != Namespace.XML_NAMESPACE)) {
 1455  14 String prefix = ns.getPrefix();
 1456  14 String uri = namespaceStack.getURI(prefix);
 1457   
 1458  14 if (!ns.getURI().equals(uri)) {
 1459  0 writeNamespace(ns);
 1460  0 namespaceStack.push(ns);
 1461    }
 1462    }
 1463   
 1464    // If the attribute is a namespace declaration, check if we have
 1465    // already written that declaration elsewhere (if that's the case,
 1466    // it must be in the namespace stack
 1467  4276 String attName = attribute.getName();
 1468   
 1469  4276 if (attName.startsWith("xmlns:")) {
 1470  3 String prefix = attName.substring(6);
 1471   
 1472  3 if (namespaceStack.getNamespaceForPrefix(prefix) == null) {
 1473  2 String uri = attribute.getValue();
 1474  2 namespaceStack.push(prefix, uri);
 1475  2 writeNamespace(prefix, uri);
 1476    }
 1477  4273 } else if (attName.equals("xmlns")) {
 1478  1 if (namespaceStack.getDefaultNamespace() == null) {
 1479  0 String uri = attribute.getValue();
 1480  0 namespaceStack.push(null, uri);
 1481  0 writeNamespace(null, uri);
 1482    }
 1483    } else {
 1484  4272 char quote = format.getAttributeQuoteCharacter();
 1485  4272 writer.write(" ");
 1486  4272 writer.write(attribute.getQualifiedName());
 1487  4272 writer.write("=");
 1488  4272 writer.write(quote);
 1489  4272 writeEscapeAttributeEntities(attribute.getValue());
 1490  4272 writer.write(quote);
 1491    }
 1492    }
 1493    }
 1494   
 1495  0 protected void writeAttribute(Attribute attribute) throws IOException {
 1496  0 writer.write(" ");
 1497  0 writer.write(attribute.getQualifiedName());
 1498  0 writer.write("=");
 1499   
 1500  0 char quote = format.getAttributeQuoteCharacter();
 1501  0 writer.write(quote);
 1502   
 1503  0 writeEscapeAttributeEntities(attribute.getValue());
 1504   
 1505  0 writer.write(quote);
 1506  0 lastOutputNodeType = Node.ATTRIBUTE_NODE;
 1507    }
 1508   
 1509  8 protected void writeAttributes(Attributes attributes) throws IOException {
 1510  8 for (int i = 0, size = attributes.getLength(); i < size; i++) {
 1511  5 writeAttribute(attributes, i);
 1512    }
 1513    }
 1514   
 1515  5 protected void writeAttribute(Attributes attributes, int index)
 1516    throws IOException {
 1517  5 char quote = format.getAttributeQuoteCharacter();
 1518  5 writer.write(" ");
 1519  5 writer.write(attributes.getQName(index));
 1520  5 writer.write("=");
 1521  5 writer.write(quote);
 1522  5 writeEscapeAttributeEntities(attributes.getValue(index));
 1523  5 writer.write(quote);
 1524    }
 1525   
 1526  24916 protected void indent() throws IOException {
 1527  24916 String indent = format.getIndent();
 1528   
 1529  24916 if ((indent != null) && (indent.length() > 0)) {
 1530  151 for (int i = 0; i < indentLevel; i++) {
 1531  172 writer.write(indent);
 1532    }
 1533    }
 1534    }
 1535   
 1536    /**
 1537    * <p>
 1538    * This will print a new line only if the newlines flag was set to true
 1539    * </p>
 1540    *
 1541    * @throws IOException
 1542    * DOCUMENT ME!
 1543    */
 1544  25027 protected void writePrintln() throws IOException {
 1545  25027 if (format.isNewlines()) {
 1546  179 String seperator = format.getLineSeparator();
 1547  179 if (lastChar != seperator.charAt(seperator.length() - 1)) {
 1548  176 writer.write(format.getLineSeparator());
 1549    }
 1550    }
 1551    }
 1552   
 1553    /**
 1554    * Get an OutputStreamWriter, use preferred encoding.
 1555    *
 1556    * @param outStream
 1557    * DOCUMENT ME!
 1558    * @param encoding
 1559    * DOCUMENT ME!
 1560    *
 1561    * @return DOCUMENT ME!
 1562    *
 1563    * @throws UnsupportedEncodingException
 1564    * DOCUMENT ME!
 1565    */
 1566  25 protected Writer createWriter(OutputStream outStream, String encoding)
 1567    throws UnsupportedEncodingException {
 1568  25 return new BufferedWriter(new OutputStreamWriter(outStream, encoding));
 1569    }
 1570   
 1571    /**
 1572    * <p>
 1573    * This will write the declaration to the given Writer. Assumes XML version
 1574    * 1.0 since we don't directly know.
 1575    * </p>
 1576    *
 1577    * @throws IOException
 1578    * DOCUMENT ME!
 1579    */
 1580  114 protected void writeDeclaration() throws IOException {
 1581  114 String encoding = format.getEncoding();
 1582   
 1583    // Only print of declaration is not suppressed
 1584  114 if (!format.isSuppressDeclaration()) {
 1585    // Assume 1.0 version
 1586  110 if (encoding.equals("UTF8")) {
 1587  0 writer.write("<?xml version=\"1.0\"");
 1588   
 1589  0 if (!format.isOmitEncoding()) {
 1590  0 writer.write(" encoding=\"UTF-8\"");
 1591    }
 1592   
 1593  0 writer.write("?>");
 1594    } else {
 1595  110 writer.write("<?xml version=\"1.0\"");
 1596   
 1597  110 if (!format.isOmitEncoding()) {
 1598  110 writer.write(" encoding=\"" + encoding + "\"");
 1599    }
 1600   
 1601  110 writer.write("?>");
 1602    }
 1603   
 1604  110 if (format.isNewLineAfterDeclaration()) {
 1605  110 println();
 1606    }
 1607    }
 1608    }
 1609   
 1610  8 protected void writeClose(String qualifiedName) throws IOException {
 1611  8 writer.write("</");
 1612  8 writer.write(qualifiedName);
 1613  8 writer.write(">");
 1614    }
 1615   
 1616  299 protected void writeEmptyElementClose(String qualifiedName)
 1617    throws IOException {
 1618    // Simply close up
 1619  299 if (!format.isExpandEmptyElements()) {
 1620  295 writer.write("/>");
 1621    } else {
 1622  4 writer.write("></");
 1623  4 writer.write(qualifiedName);
 1624  4 writer.write(">");
 1625    }
 1626    }
 1627   
 1628  0 protected boolean isExpandEmptyElements() {
 1629  0 return format.isExpandEmptyElements();
 1630    }
 1631   
 1632    /**
 1633    * This will take the pre-defined entities in XML 1.0 and convert their
 1634    * character representation to the appropriate entity reference, suitable
 1635    * for XML attributes.
 1636    *
 1637    * @param text
 1638    * DOCUMENT ME!
 1639    *
 1640    * @return DOCUMENT ME!
 1641    */
 1642  37708 protected String escapeElementEntities(String text) {
 1643  37708 char[] block = null;
 1644  37708 int i;
 1645  37708 int last = 0;
 1646  37708 int size = text.length();
 1647   
 1648  37708 for (i = 0; i < size; i++) {
 1649  467325 String entity = null;
 1650  467325 char c = text.charAt(i);
 1651   
 1652  467325 switch (c) {
 1653  93 case '<':
 1654  93 entity = "&lt;";
 1655   
 1656  93 break;
 1657   
 1658  84 case '>':
 1659  84 entity = "&gt;";
 1660   
 1661  84 break;
 1662   
 1663  63 case '&':
 1664  63 entity = "&amp;";
 1665   
 1666  63 break;
 1667   
 1668  3455 case '\t':
 1669  27550 case '\n':
 1670  0 case '\r':
 1671   
 1672    // don't encode standard whitespace characters
 1673  31005 if (preserve) {
 1674  2 entity = String.valueOf(c);
 1675    }
 1676   
 1677  31005 break;
 1678   
 1679  436080 default:
 1680   
 1681  436080 if ((c < 32) || shouldEncodeChar(c)) {
 1682  1 entity = "&#" + (int) c + ";";
 1683    }
 1684   
 1685  436080 break;
 1686    }
 1687   
 1688  467325 if (entity != null) {
 1689  243 if (block == null) {
 1690  145 block = text.toCharArray();
 1691    }
 1692   
 1693  243 buffer.append(block, last, i - last);
 1694  243 buffer.append(entity);
 1695  243 last = i + 1;
 1696    }
 1697    }
 1698   
 1699  37708 if (last == 0) {
 1700  37563 return text;
 1701    }
 1702   
 1703  145 if (last < size) {
 1704  93 if (block == null) {
 1705  0 block = text.toCharArray();
 1706    }
 1707   
 1708  93 buffer.append(block, last, i - last);
 1709    }
 1710   
 1711  145 String answer = buffer.toString();
 1712  145 buffer.setLength(0);
 1713   
 1714  145 return answer;
 1715    }
 1716   
 1717  4277 protected void writeEscapeAttributeEntities(String txt) throws IOException {
 1718  4277 if (txt != null) {
 1719  4277 String escapedText = escapeAttributeEntities(txt);
 1720  4277 writer.write(escapedText);
 1721    }
 1722    }
 1723   
 1724    /**
 1725    * This will take the pre-defined entities in XML 1.0 and convert their
 1726    * character representation to the appropriate entity reference, suitable
 1727    * for XML attributes.
 1728    *
 1729    * @param text
 1730    * DOCUMENT ME!
 1731    *
 1732    * @return DOCUMENT ME!
 1733    */
 1734  4277 protected String escapeAttributeEntities(String text) {
 1735  4277 char quote = format.getAttributeQuoteCharacter();
 1736   
 1737  4277 char[] block = null;
 1738  4277 int i;
 1739  4277 int last = 0;
 1740  4277 int size = text.length();
 1741   
 1742  4277 for (i = 0; i < size; i++) {
 1743  48016 String entity = null;
 1744  48016 char c = text.charAt(i);
 1745   
 1746  48016 switch (c) {
 1747  0 case '<':
 1748  0 entity = "&lt;";
 1749   
 1750  0 break;
 1751   
 1752  0 case '>':
 1753  0 entity = "&gt;";
 1754   
 1755  0 break;
 1756   
 1757  1 case '\'':
 1758   
 1759  1 if (quote == '\'') {
 1760  0 entity = "&apos;";
 1761    }
 1762   
 1763  1 break;
 1764   
 1765  0 case '\"':
 1766   
 1767  0 if (quote == '\"') {
 1768  0 entity = "&quot;";
 1769    }
 1770   
 1771  0 break;
 1772   
 1773  0 case '&':
 1774  0 entity = "&amp;";
 1775   
 1776  0 break;
 1777   
 1778  0 case '\t':
 1779  0 case '\n':
 1780  0 case '\r':
 1781   
 1782    // don't encode standard whitespace characters
 1783  0 break;
 1784   
 1785  48015 default:
 1786   
 1787  48015 if ((c < 32) || shouldEncodeChar(c)) {
 1788  0 entity = "&#" + (int) c + ";";
 1789    }
 1790   
 1791  48015 break;
 1792    }
 1793   
 1794  48016 if (entity != null) {
 1795  0 if (block == null) {
 1796  0 block = text.toCharArray();
 1797    }
 1798   
 1799  0 buffer.append(block, last, i - last);
 1800  0 buffer.append(entity);
 1801  0 last = i + 1;
 1802    }
 1803    }
 1804   
 1805  4277 if (last == 0) {
 1806  4277 return text;
 1807    }
 1808   
 1809  0 if (last < size) {
 1810  0 if (block == null) {
 1811  0 block = text.toCharArray();
 1812    }
 1813   
 1814  0 buffer.append(block, last, i - last);
 1815    }
 1816   
 1817  0 String answer = buffer.toString();
 1818  0 buffer.setLength(0);
 1819   
 1820  0 return answer;
 1821    }
 1822   
 1823    /**
 1824    * Should the given character be escaped. This depends on the encoding of
 1825    * the document.
 1826    *
 1827    * @param c
 1828    * DOCUMENT ME!
 1829    *
 1830    * @return boolean
 1831    */
 1832  484095 protected boolean shouldEncodeChar(char c) {
 1833  484095 int max = getMaximumAllowedCharacter();
 1834   
 1835  484095 return (max > 0) && (c > max);
 1836    }
 1837   
 1838    /**
 1839    * Returns the maximum allowed character code that should be allowed
 1840    * unescaped which defaults to 127 in US-ASCII (7 bit) or 255 in ISO- (8
 1841    * bit).
 1842    *
 1843    * @return DOCUMENT ME!
 1844    */
 1845  2147 protected int defaultMaximumAllowedCharacter() {
 1846  2147 String encoding = format.getEncoding();
 1847   
 1848  2147 if (encoding != null) {
 1849  2147 if (encoding.equals("US-ASCII")) {
 1850  0 return 127;
 1851    }
 1852    }
 1853   
 1854    // no encoding for things like ISO-*, UTF-8 or UTF-16
 1855  2147 return -1;
 1856    }
 1857   
 1858  20275 protected boolean isNamespaceDeclaration(Namespace ns) {
 1859  20275 if ((ns != null) && (ns != Namespace.XML_NAMESPACE)) {
 1860  20275 String uri = ns.getURI();
 1861   
 1862  20275 if (uri != null) {
 1863  20275 if (!namespaceStack.contains(ns)) {
 1864  618 return true;
 1865    }
 1866    }
 1867    }
 1868   
 1869  19657 return false;
 1870    }
 1871   
 1872  0 protected void handleException(IOException e) throws SAXException {
 1873  0 throw new SAXException(e);
 1874    }
 1875   
 1876    // Laramie Crocker 4/8/2002 10:38AM
 1877   
 1878    /**
 1879    * Lets subclasses get at the current format object, so they can call
 1880    * setTrimText, setNewLines, etc. Put in to support the HTMLWriter, in the
 1881    * way that it pushes the current newline/trim state onto a stack and
 1882    * overrides the state within preformatted tags.
 1883    *
 1884    * @return DOCUMENT ME!
 1885    */
 1886  7 protected OutputFormat getOutputFormat() {
 1887  7 return format;
 1888    }
 1889   
 1890  0 public boolean resolveEntityRefs() {
 1891  0 return resolveEntityRefs;
 1892    }
 1893   
 1894  0 public void setResolveEntityRefs(boolean resolve) {
 1895  0 this.resolveEntityRefs = resolve;
 1896    }
 1897    }
 1898   
 1899    /*
 1900    * Redistribution and use of this software and associated documentation
 1901    * ("Software"), with or without modification, are permitted provided that the
 1902    * following conditions are met:
 1903    *
 1904    * 1. Redistributions of source code must retain copyright statements and
 1905    * notices. Redistributions must also contain a copy of this document.
 1906    *
 1907    * 2. Redistributions in binary form must reproduce the above copyright notice,
 1908    * this list of conditions and the following disclaimer in the documentation
 1909    * and/or other materials provided with the distribution.
 1910    *
 1911    * 3. The name "DOM4J" must not be used to endorse or promote products derived
 1912    * from this Software without prior written permission of MetaStuff, Ltd. For
 1913    * written permission, please contact dom4j-info@metastuff.com.
 1914    *
 1915    * 4. Products derived from this Software may not be called "DOM4J" nor may
 1916    * "DOM4J" appear in their names without prior written permission of MetaStuff,
 1917    * Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
 1918    *
 1919    * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
 1920    *
 1921    * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND
 1922    * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 1923    * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 1924    * ARE DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE
 1925    * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 1926    * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 1927    * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 1928    * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 1929    * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 1930    * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 1931    * POSSIBILITY OF SUCH DAMAGE.
 1932    *
 1933    * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
 1934    */