XML Java

/*
 * Copyright Aduna (http://www.aduna-software.com/) (c) 1997-2006.
 *
 * Licensed under the Aduna BSD-style license.
 */
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.LinkedHashMap;
import java.util.Map.Entry;
/**
 * A utility class offering convenience methods for writing XML. This class
 * takes care of character escaping, identation, etc. This class does not verify
 * that the written data is legal XML. It is the callers responsibility to make
 * sure that elements are properly nested, etc.
 *
 * 

Example:


 * 


 * To write the following XML:
 * 


 * <?xml version='1.0' encoding='UTF-8'?>
 * <xml-doc>
 *  <foo a="1" b="2&amp;3"/>
 *  <bar>Hello World!</bar>
 * </xml-doc>
 *

 * 


 * One can use the following code:
 * 


 * XMLWriter xmlWriter = new XMLWriter(myWriter);
 * xmlWriter.setPrettyPrint(true);
 *
 * xmlWriter.startDocument();
 * xmlWriter.startTag("xml-doc");
 *
 * xmlWriter.setAttribute("a", 1);
 * xmlWriter.setAttribute("b", "2&3");
 * xmlWriter.simpleTag("foo");
 *
 * xmlWriter.textTag("bar", "Hello World!");
 *
 * xmlWriter.endTag("xml-doc");
 * xmlWriter.endDocument();
 * 

 */
public class XMLWriter {
  /*-----------*
   * Constants *
   *-----------*/
  /**
   * The (platform-dependent) line separator.
   */
  private static final String LINE_SEPARATOR = System.getProperty("line.separator");
  /*-----------*
   * Variables *
   *-----------*/
  /**
   * The writer to write the XML to.
   */
  private Writer _writer;
  /**
   * The required character encoding of the written data.
   */
  private String _charEncoding;
  /**
   * Flag indicating whether the output should be printed pretty, i.e. adding
   * newlines and indentation.
   */
  private boolean _prettyPrint = false;
  /**
   * The current indentation level, i.e. the number of tabs to indent a start
   * or end tag.
   */
  protected int _indentLevel = 0;
  /**
   * The string to use for indentation, e.g. a tab or a number of spaces.
   */
  private String _indentString = "\t";
  /**
   * A mapping from attribute names to values for the next start tag.
   */
  private HashMap _attributes = new LinkedHashMap();
  /*--------------*
   * Constructors *
   *--------------*/
  /**
   * Creates a new XMLWriter that will write its data to the supplied Writer.
   * Character encoding issues are left to the supplier of the Writer.
   *
   * @param writer The Writer to write the XML to.
   */
  public XMLWriter(Writer writer) {
    _writer = writer;
  }
  /**
   * Creates a new XMLWriter that will write its data to the supplied
   * OutputStream in the default UTF-8 character encoding.
   *
   * @param outputStream The OutputStream to write the XML to.
   */
  public XMLWriter(OutputStream outputStream) {
    try {
      _charEncoding = "UTF-8";
      _writer = new OutputStreamWriter(outputStream, _charEncoding);
    }
    catch (UnsupportedEncodingException e) {
      // UTF-8 must be supported by all compliant JVM's,
      // this exception should never be thrown.
      throw new RuntimeException(
          "UTF-8 character encoding not supported on this platform");
    }
  }
  /**
   * Creates a new XMLWriter that will write its data to the supplied
   * OutputStream in specified character encoding.
   *
   * @param outputStream The OutputStream to write the XML to.
   */
  public XMLWriter(OutputStream outputStream, String charEncoding)
    throws UnsupportedEncodingException
  {
    _charEncoding = charEncoding;
    _writer = new OutputStreamWriter(outputStream, _charEncoding);
  }
  /*---------*
   * Methods *
   *---------*/
  /**
   * Enables or disables pretty-printing. If pretty-printing is enabled, the
   * XMLWriter will add newlines and indentation to the written data.
   * Pretty-printing is disabled by default.
   *
   * @param prettyPrint Flag indicating whether pretty-printing should be
   * enabled.
   */
  public void setPrettyPrint(boolean prettyPrint) {
    _prettyPrint = prettyPrint;
  }
  /**
   * Checks whether pretty-printing is enabled.
   *
   * @return true if pretty-printing is enabled, false
   * otherwise.
   */
  public boolean prettyPrintEnabled() {
    return _prettyPrint;
  }
  /**
   * Sets the string that should be used for indentation when pretty-printing
   * is enabled. The default indentation string is a tab character.
   *
   * @param indentString The indentation string, e.g. a tab or a number of
   * spaces.
   */
  public void setIndentString(String indentString) {
    _indentString = indentString;
  }
  /**
   * Gets the string used for indentation.
   *
   * @return the indentation string.
   */
  public String getIndentString() {
    return _indentString;
  }
  /**
   * Writes the XML header for the XML file.
   *
   * @throws IOException If an I/O error occurs.
   */
  public void startDocument()
    throws IOException
  {
    _write("    if (_charEncoding != null) {
      _write(" encoding='" + _charEncoding + "'");
    }
    _writeLn("?>");
  }
  /**
   * Finishes writing and flushes the OutputStream or Writer that this
   * XMLWriter is writing to.
   */
  public void endDocument()
    throws IOException
  {
    _writer.flush();
    _writer = null;
  }
  /**
   * Sets an attribute for the next start tag.
   *
   * @param name The name of the attribute.
   * @param value The value of the attribute.
   */
  public void setAttribute(String name, String value) {
    _attributes.put(name, value);
  }
  /**
   * Sets an attribute for the next start element.
   *
   * @param name The name of the attribute.
   * @param value The value of the attribute. The integer value will be
   * transformed to a string using the method String.valueOf(int).
   * @see java.lang.String#valueOf(int)
   */
  public void setAttribute(String name, int value) {
    setAttribute(name, String.valueOf(value));
  }
  /**
   * Sets an attribute for the next start element.
   *
   * @param name The name of the attribute.
   * @param value The value of the attribute. The boolean value will be
   * transformed to a string using the method
   * String.valueOf(boolean).
   * @see java.lang.String#valueOf(boolean)
   */
  public void setAttribute(String name, boolean value) {
    setAttribute(name, String.valueOf(value));
  }
  /**
   * Writes a start tag containing the previously set attributes.
   *
   * @param elName The element name.
   * @see #setAttribute(java.lang.String,java.lang.String)
   */
  public void startTag(String elName)
    throws IOException
  {
    _writeIndent();
    _write("<" + elName);
    _writeAtts();
    _writeLn(">");
    _indentLevel++;
  }
  /**
   * Writes an end tag.
   *
   * @param elName The element name.
   */
  public void endTag(String elName)
    throws IOException
  {
    _indentLevel--;
    _writeIndent();
    _writeLn("");
  }
  /**
   * Writes an 'empty' element, e.g. <foo/>. The tag will
   * contain any previously set attributes.
   *
   * @param elName The element name.
   * @see #setAttribute(java.lang.String,java.lang.String)
   */
  public void emptyElement(String elName)
    throws IOException
  {
    _writeIndent();
    _write("<" + elName);
    _writeAtts();
    _writeLn("/>");
  }
  /**
   * Writes a start and end tag with the supplied text between them. The start
   * tag will contain any previously set attributes.
   *
   * @param elName The element name.
   * @param text The text.
   * @see #setAttribute(java.lang.String,java.lang.String)
   */
  public void textElement(String elName, String text)
    throws IOException
  {
    _writeIndent();
    _write("<" + elName);
    _writeAtts();
    _write(">");
    text(text);
    _writeLn("");
  }
  /**
   * Writes a start and end tag with the supplied text between them, without
   * the usual escape rules. The start tag will contain any previously set
   * attributes.
   *
   * @param elName The element name.
   * @param text The text.
   * @see #setAttribute(java.lang.String,java.lang.String)
   */
  public void unescapedTextElement(String elName, String text)
    throws IOException
  {
    _writeIndent();
    _write("<" + elName);
    _writeAtts();
    _write(">");
    _write(text);
    _writeLn("");
  }
  /**
   * Writes a start and end tag with the supplied value between them. The
   * start tag will contain any previously set attributes.
   *
   * @param elName The element name.
   * @param value The value. The integer value will be transformed to a string
   * using the method String.valueOf(int).
   * @see java.lang.String#valueOf(int)
   */
  public void textElement(String elName, int value)
    throws IOException
  {
    textElement(elName, String.valueOf(value));
  }
  /**
   * Writes a start and end tag with the supplied boolean value between them.
   * The start tag will contain any previously set attributes.
   *
   * @param elName The element name.
   * @param value The boolean value. The integer value will be transformed to
   * a string using the method String.valueOf(boolean).
   * @see java.lang.String#valueOf(boolean)
   */
  public void textElement(String elName, boolean value)
    throws IOException
  {
    textElement(elName, String.valueOf(value));
  }
  /**
   * Writes a piece of text.
   *
   * @param text The text.
   */
  public void text(String text)
    throws IOException
  {
    _write(text);
  }
  
  /**
   * Writes a comment.
   *
   * @param comment The comment.
   */
  public void comment(String comment)
    throws IOException
  {
    _writeIndent();
    _writeLn("");
  }
  /**
   * Writes an empty line. A call to this method will be ignored when
   * pretty-printing is disabled.
   *
   * @see #setPrettyPrint
   */
  public void emptyLine()
    throws IOException
  {
    _writeLn("");
  }
  /**
   * Writes any set attributes and clears them afterwards.
   */
  private void _writeAtts()
    throws IOException
  {
    for (Entry entry : _attributes.entrySet()) {
      String name = entry.getKey();
      String value = entry.getValue();
      _write(" " + name + "='");
      if (value != null) {
        _write(value);
      }
      _write("'");
    }
    
    _attributes.clear();
  }
  /**
   * Writes a string.
   */
  protected void _write(String s)
    throws IOException
  {
    _writer.write(s);
  }
  /**
   * Writes a string followed by a line-separator. The line-separator is not
   * written when pretty-printing is disabled.
   */
  protected void _writeLn(String s)
    throws IOException
  {
    _write(s);
    if (_prettyPrint) {
      _write(LINE_SEPARATOR);
    }
  }
  /**
   * Writes as much indentation strings as appropriate for the current
   * indentation level. A call to this method is ignored when pretty-printing
   * is disabled.
   */
  protected void _writeIndent()
    throws IOException
  {
    if (_prettyPrint) {
      for (int i = 0; i < _indentLevel; i++) {
        _write(_indentString);
      }
    }
  }
}