Project dom4j 1.5.2 [5/2/05 10:13 PM]
 
Coverage - org/dom4j/io/XMLWriter.java
  /*
   * Copyright 2001-2004 (C) MetaStuff, Ltd. All Rights Reserved.
   *
   * This software is open source.
   * See the bottom of this file for the licence.
   *
   * $Id: XMLWriter.java,v 1.77 2004/09/24 12:25:36 maartenc Exp $
   */
 
  package org.dom4j.io;
 
  import java.io.BufferedWriter;
  import java.io.IOException;
  import java.io.OutputStream;
  import java.io.OutputStreamWriter;
  import java.io.UnsupportedEncodingException;
  import java.io.Writer;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.StringTokenizer;
 
  import org.dom4j.Attribute;
  import org.dom4j.CDATA;
  import org.dom4j.Comment;
  import org.dom4j.Document;
  import org.dom4j.DocumentType;
  import org.dom4j.Element;
  import org.dom4j.Entity;
  import org.dom4j.Namespace;
  import org.dom4j.Node;
  import org.dom4j.ProcessingInstruction;
  import org.dom4j.Text;
  import org.dom4j.tree.NamespaceStack;
  import org.xml.sax.Attributes;
  import org.xml.sax.InputSource;
  import org.xml.sax.Locator;
  import org.xml.sax.SAXException;
  import org.xml.sax.SAXNotRecognizedException;
  import org.xml.sax.SAXNotSupportedException;
  import org.xml.sax.XMLReader;
  import org.xml.sax.ext.LexicalHandler;
  import org.xml.sax.helpers.XMLFilterImpl;
 
  /**<p><code>XMLWriter</code> takes a DOM4J tree and formats it to a
    * stream as XML.
    * It can also take SAX events too so can be used by SAX clients as this object
    * implements the {@link org.xml.sax.ContentHandler} and {@link LexicalHandler} interfaces.
    * as well. This formatter performs typical document
    * formatting.  The XML declaration and processing instructions are
    * always on their own lines. An {@link OutputFormat} object can be
    * used to define how whitespace is handled when printing and allows various
    * configuration options, such as to allow suppression of the XML declaration,
    * the encoding declaration or whether empty documents are collapsed.</p>
    *
    * <p> There are <code>write(...)</code> methods to print any of the
    * standard DOM4J classes, including <code>Document</code> and
    * <code>Element</code>, to either a <code>Writer</code> or an
    * <code>OutputStream</code>.  Warning: using your own
    * <code>Writer</code> may cause the writer's preferred character
    * encoding to be ignored.  If you use encodings other than UTF8, we
    * recommend using the method that takes an OutputStream instead.
    * </p>
    *
    * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
    * @author Joseph Bowbeer
    * @version $Revision: 1.77 $
    */
  public class XMLWriter extends XMLFilterImpl implements LexicalHandler {
      
      private static final String PAD_TEXT = " ";
 
      protected static final String[] LEXICAL_HANDLER_NAMES = {
          "http://xml.org/sax/properties/lexical-handler",
          "http://xml.org/sax/handlers/LexicalHandler"
      };
 
      protected static final OutputFormat DEFAULT_FORMAT = new OutputFormat();
 
      /** Should entityRefs by resolved when writing ? */
4172x       private boolean resolveEntityRefs = true;
 
      /** Stores the last type of node written so algorithms can refer to the
        * previous node type */
      protected int lastOutputNodeType;
 
      /** Stores the xml:space attribute value of preserve for whitespace flag */
4172x       protected boolean preserve=false;
 
      /** The Writer used to output to */
      protected Writer writer;
 
      /** The Stack of namespaceStack written so far */
4172x       private NamespaceStack namespaceStack = new NamespaceStack();
 
      /** The format used by this writer */
      private OutputFormat format;
 
      /** whether we should escape text */
4172x       private boolean escapeText = true;
      /** The initial number of indentations (so you can print a whole
          document indented, if you like) **/
4172x       private int indentLevel = 0;
 
      /** buffer used when escaping strings */
4172x       private StringBuffer buffer = new StringBuffer();
      
      /** whether we have added characters before from the same chunk of characters */
4172x       private boolean charactersAdded = false;
      private char lastChar;
 
      /** Whether a flush should occur after writing a document */
      private boolean autoFlush;
 
      /** Lexical handler we should delegate to */
      private LexicalHandler lexicalHandler;
 
      /** Whether comments should appear inside DTD declarations - defaults to false */
      private boolean showCommentsInDTDs;
 
      /** Is the writer curerntly inside a DTD definition? */
      private boolean inDTD;
 
      /** The namespaces used for the current element when consuming SAX events */
      private Map namespacesMap;
 
      /**
       * what is the maximum allowed character code
       * such as 127 in US-ASCII (7 bit) or 255 in ISO-* (8 bit)
       * or -1 to not escape any characters (other than the special XML characters like < > &)
       */
      private int maximumAllowedCharacter;
 
 
      public XMLWriter(Writer writer) {
10x           this( writer, DEFAULT_FORMAT );
10x       }
 
4172x       public XMLWriter(Writer writer, OutputFormat format) {
4172x           this.writer = writer;
4172x           this.format = format;
4172x           namespaceStack.push(Namespace.NO_NAMESPACE);
4172x       }
 
2x       public XMLWriter() {
2x           this.format = DEFAULT_FORMAT;
2x           this.writer = new BufferedWriter( new OutputStreamWriter( System.out ) );
2x           this.autoFlush = true;
2x           namespaceStack.push(Namespace.NO_NAMESPACE);
2x       }
 
4x       public XMLWriter(OutputStream out) throws UnsupportedEncodingException {
4x           this.format = DEFAULT_FORMAT;
4x           this.writer = createWriter(out, format.getEncoding());
4x           this.autoFlush = true;
4x           namespaceStack.push(Namespace.NO_NAMESPACE);
4x       }
 
60x       public XMLWriter(OutputStream out, OutputFormat format) throws UnsupportedEncodingException {
60x           this.format = format;
60x           this.writer = createWriter(out, format.getEncoding());
60x           this.autoFlush = true;
60x           namespaceStack.push(Namespace.NO_NAMESPACE);
60x       }
 
10x       public XMLWriter(OutputFormat format) throws UnsupportedEncodingException {
10x           this.format = format;
10x           this.writer = createWriter( System.out, format.getEncoding() );
10x           this.autoFlush = true;
10x           namespaceStack.push(Namespace.NO_NAMESPACE);
10x       }
 
      public void setWriter(Writer writer) {
2x           this.writer = writer;
2x           this.autoFlush = false;
2x       }
 
      public void setOutputStream(OutputStream out) throws UnsupportedEncodingException {
10x           this.writer = createWriter(out, format.getEncoding());
10x           this.autoFlush = true;
10x       }
 
      /**
       * @return true if text thats output should be escaped.
       * This is enabled by default. It could be disabled if
       * the output format is textual, like in XSLT where we can have
       * xml, html or text output.
       */
      public boolean isEscapeText() {
0x           return escapeText;
      }
 
      /**
       * Sets whether text output should be escaped or not.
       * This is enabled by default. It could be disabled if
       * the output format is textual, like in XSLT where we can have
       * xml, html or text output.
       */
      public void setEscapeText(boolean escapeText) {
2x           this.escapeText = escapeText;
2x       }
 
 
      /** Set the initial indentation level.  This can be used to output
        * a document (or, more likely, an element) starting at a given
        * indent level, so it's not always flush against the left margin.
        * Default: 0
        *
        * @param indentLevel the number of indents to start with
        */
      public void setIndentLevel(int indentLevel) {
0x           this.indentLevel = indentLevel;
0x       }
 
      /**
       * Returns the maximum allowed character code that should be allowed
       * unescaped which defaults to 127 in US-ASCII (7 bit) or
       * 255 in ISO-* (8 bit).
       */
      public int getMaximumAllowedCharacter() {
2/2 60942x           if (maximumAllowedCharacter == 0) {
4218x               maximumAllowedCharacter = defaultMaximumAllowedCharacter();
          }
60942x           return maximumAllowedCharacter;
      }
      
      /**
       * Sets the maximum allowed character code that should be allowed
       * unescaped
       * such as 127 in US-ASCII (7 bit) or 255 in ISO-* (8 bit)
       * or -1 to not escape any characters (other than the special XML characters like < > &)
       *
       * If this is not explicitly set then it is defaulted from the encoding.
       *
       * @param maximumAllowedCharacter The maximumAllowedCharacter to set
       */
      public void setMaximumAllowedCharacter(int maximumAllowedCharacter) {
2x           this.maximumAllowedCharacter = maximumAllowedCharacter;
2x       }
      
      /** Flushes the underlying Writer */
      public void flush() throws IOException {
76x           writer.flush();
76x       }
 
      /** Closes the underlying Writer */
      public void close() throws IOException {
34x           writer.close();
34x       }
 
      /** Writes the new line text to the underlying Writer */
      public void println() throws IOException {
116x           writer.write( format.getLineSeparator() );
116x       }
 
      /** Writes the given {@link Attribute}.
        *
        * @param attribute <code>Attribute</code> to output.
        */
      public void write(Attribute attribute) throws IOException {
0x           writeAttribute(attribute);
 
0/2 0x           if ( autoFlush ) {
0x               flush();
          }
0x       }
 
 
      /** <p>This will print the <code>Document</code> to the current Writer.</p>
       *
       * <p> Warning: using your own Writer may cause the writer's
       * preferred character encoding to be ignored.  If you use
       * encodings other than UTF8, we recommend using the method that
       * takes an OutputStream instead.  </p>
       *
       * <p>Note: as with all Writers, you may need to flush() yours
       * after this method returns.</p>
       *
       * @param doc <code>Document</code> to format.
       * @throws <code>IOException</code> - if there's any problem writing.
       **/
      public void write(Document doc) throws IOException {
112x           writeDeclaration();
 
2/2 112x           if (doc.getDocType() != null) {
2x               indent();
2x               writeDocType(doc.getDocType());
          }
 
2/2 224x           for ( int i = 0, size = doc.nodeCount(); i < size; i++ ) {
112x               Node node = doc.node(i);
112x               writeNode( node );
          }
112x           writePrintln();
 
2/2 112x           if ( autoFlush ) {
72x               flush();
          }
112x       }
 
      /** <p>Writes the <code>{@link Element}</code>, including
        * its <code>{@link Attribute}</code>s, and its value, and all
        * its content (child nodes) to the current Writer.</p>
        *
        * @param element <code>Element</code> to output.
        */
      public void write(Element element) throws IOException {
4126x           writeElement(element);
 
2/2 4126x           if ( autoFlush ) {
4x               flush();
          }
4126x       }
 
 
      /** Writes the given {@link CDATA}.
        *
        * @param cdata <code>CDATA</code> to output.
        */
      public void write(CDATA cdata) throws IOException {
0x           writeCDATA( cdata.getText() );
 
0/2 0x           if ( autoFlush ) {
0x               flush();
          }
0x       }
 
      /** Writes the given {@link Comment}.
        *
        * @param comment <code>Comment</code> to output.
        */
      public void write(Comment comment) throws IOException {
0x           writeComment( comment.getText() );
 
0/2 0x           if ( autoFlush ) {
0x               flush();
          }
0x       }
 
      /** Writes the given {@link DocumentType}.
        *
        * @param docType <code>DocumentType</code> to output.
        */
      public void write(DocumentType docType) throws IOException {
0x           writeDocType(docType);
 
0/2 0x           if ( autoFlush ) {
0x               flush();
          }
0x       }
 
 
      /** Writes the given {@link Entity}.
        *
        * @param entity <code>Entity</code> to output.
        */
      public void write(Entity entity) throws IOException {
0x           writeEntity( entity );
 
0/2 0x           if ( autoFlush ) {
0x               flush();
          }
0x       }
 
 
      /** Writes the given {@link Namespace}.
        *
        * @param namespace <code>Namespace</code> to output.
        */
      public void write(Namespace namespace) throws IOException {
0x           writeNamespace(namespace);
 
0/2 0x           if ( autoFlush ) {
0x               flush();
          }
0x       }
 
      /** Writes the given {@link ProcessingInstruction}.
        *
        * @param processingInstruction <code>ProcessingInstruction</code> to output.
        */
      public void write(ProcessingInstruction processingInstruction) throws IOException {
0x           writeProcessingInstruction(processingInstruction);
 
0/2 0x           if ( autoFlush ) {
0x               flush();
          }
0x       }
 
      /** <p>Print out a {@link String}, Perfoms
        * the necessary entity escaping and whitespace stripping.</p>
        *
        * @param text is the text to output
        */
      public void write(String text) throws IOException {
0x           writeString(text);
 
0/2 0x           if ( autoFlush ) {
0x               flush();
          }
0x       }
 
      /** Writes the given {@link Text}.
        *
        * @param text <code>Text</code> to output.
        */
      public void write(Text text) throws IOException {
0x           writeString(text.getText());
 
0/2 0x           if ( autoFlush ) {
0x               flush();
          }
0x       }
 
      /** Writes the given {@link Node}.
        *
        * @param node <code>Node</code> to output.
        */
      public void write(Node node) throws IOException {
2x           writeNode(node);
 
1/2 2x           if ( autoFlush ) {
0x               flush();
          }
2x       }
 
      /** Writes the given object which should be a String, a Node or a List
        * of Nodes.
        *
        * @param object is the object to output.
        */
      public void write(Object object) throws IOException {
1/2 2x           if (object instanceof Node) {
2x               write((Node) object);
          }
0/2 0x           else if (object instanceof String) {
0x               write((String) object);
          }
0/2 0x           else if (object instanceof List) {
0x               List list = (List) object;
0/2 0x               for ( int i = 0, size = list.size(); i < size; i++ ) {
0x                   write( list.get(i) );
              }
          }
0/2 0x           else if (object != null) {
0x               throw new IOException( "Invalid object: " + object );
          }
2x       }
 
 
      /** <p>Writes the opening tag of an {@link Element},
        * including its {@link Attribute}s
        * but without its content.</p>
        *
        * @param element <code>Element</code> to output.
        */
      public void writeOpen(Element element) throws IOException {
0x           writer.write("<");
0x           writer.write( element.getQualifiedName() );
0x           writeAttributes(element);
0x           writer.write(">");
0x       }
 
      /** <p>Writes the closing tag of an {@link Element}</p>
        *
        * @param element <code>Element</code> to output.
        */
      public void writeClose(Element element) throws IOException {
0x           writeClose( element.getQualifiedName() );
0x       }
 
 
      // XMLFilterImpl methods
      //-------------------------------------------------------------------------
      public void parse(InputSource source) throws IOException, SAXException {
0x           installLexicalHandler();
0x           super.parse(source);
0x       }
 
 
      public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException {
0/2 0x           for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
0/2 0x               if (LEXICAL_HANDLER_NAMES[i].equals(name)) {
0x                   setLexicalHandler((LexicalHandler) value);
0x                   return;
              }
          }
0x           super.setProperty(name, value);
0x       }
 
      public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
0/2 0x           for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
0/2 0x               if (LEXICAL_HANDLER_NAMES[i].equals(name)) {
0x                   return getLexicalHandler();