Convenience methods for writing XML
/* * 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. * * <h3>Example:</h3> * <p> * To write the following XML: * <pre> * <?xml version='1.0' encoding='UTF-8'?> * <xml-doc> * <foo a="1" b="2&amp;3"/> * <bar>Hello World!</bar> * </xml-doc> *</pre> * <p> * One can use the following code: * <pre> * 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(); * </pre> */ 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<String, String> _attributes = new LinkedHashMap<String, String>(); /*--------------* * 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 <tt>true</tt> if pretty-printing is enabled, <tt>false</tt> * 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("<?xml version='1.0'"); 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 <tt>String.valueOf(int)</tt>. * @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 * <tt>String.valueOf(boolean)</tt>. * @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. <tt><foo/></tt>. 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 <tt>String.valueOf(int)</tt>. * @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 <tt>String.valueOf(boolean)</tt>. * @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("<!-- " + comment + " -->"); } /** * 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<String,String> 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); } } } }