/*
* 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&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("" + elName + ">");
}
/**
* 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("" + elName + ">");
}
/**
* 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("" + elName + ">");
}
/**
* 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);
}
}
}
}