XMLOutputter.java |
/* * $Id: XMLOutputter.java,v 1.106 2003/10/01 12:02:52 znerd Exp $ */ package org.znerd.xmlenc; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.io.Writer; /** * Stream-based XML outputter. Instances of this class are able to write XML * output to {@link Writer Writers}. * * <h3>Standards compliance</h3> * * This class is intended to produce output that conforms to the * <a href="http://www.w3.org/TR/2000/REC-xml-20001006">XML 1.0 * Specification</a>. However, not all applicable restrictions are validated. * For example, it is currently not checked if names contain characters that * are invalid within a <em>Name</em> production. * * <p />Furthermore, not all possible XML documents can be produced. The * following limitations apply: * * <ul> * <li>The name of the applicable encoding is always printed in the XML * declaration, even though it may not be necessary.</li> * <li>The <code>standalone</code> attribute is not supported in the XML * declaration.</li> * <li>Internal DTD subsets are not supported.</li> * <li>Spacing is fixed, whitespace is always kept to the minimum.</li> * </ul> * * <h3>Supported encodings</h3> * * The following encodings are supported: * * <ul> * <li>UTF-8</li> * <li>UTF-16</li> * <li>ISO-10646-UCS-2</li> * <li>ISO-10646-UCS-4</li> * <li>ISO-10646-UTF-1</li> * <li>US-ASCII (also known as ASCII)</li> * <li>ISO-8859-<em>n</em>, where <em>n</em> is the part number</li> * </ul> * * <h3>Multi-threading</h3> * * This class is <em>not</em> thread-safe. * * <h3>Exceptions</h3> * * Note that all methods check the state first and then check the * arguments. This means that if the state is incorrect and the arguments are * incorrect, then an {@link IllegalStateException} will be thrown. * * <p />If any of the writing methods generates an {@link IOException}, then * the state will be set to {@link #ERROR_STATE} and no more output can be * performed. * * <h3>Performance hints</h3> * * It is usually a good idea to let <code>XMLOutputter</code> instances * write to buffered {@link Writer Writers}. This typically improves * performance on large documents or relatively slow or blocking output * streams. * * <p />Instances of this class can be cached in a pool to reduce object * creations. Call {@link #reset()} (with no arguments) when storing an * instance in the pool. Use {@link #reset(Writer,String)} (with 2 arguments) * to re-initialize the instance after fetching it from the pool. * * @version $Revision: 1.106 $ $Date: 2003/10/01 12:02:52 $ * @author Ernst de Haan (<a href="mailto:znerd@FreeBSD.org">znerd@FreeBSD.org</a>) * @author Jochen Schwoerer (j.schwoerer [at] web.de) * * @since xmlenc 0.19 */ public class XMLOutputter extends Object implements StatefulXMLEventListener { //------------------------------------------------------------------------- // Class functions //------------------------------------------------------------------------- //------------------------------------------------------------------------- // Class fields //------------------------------------------------------------------------- /** * Default indentation. This is the empty string, <code>""</code>, since by * default no indentation is performed. */ public static final String DEFAULT_INDENTATION = ""; //------------------------------------------------------------------------- // Constructor //------------------------------------------------------------------------- /** * Constructs a new <code>XMLOutputter</code>. This sets the state to * {@link #UNINITIALIZED}. */ public XMLOutputter() { _elementStack = new String[16]; } /** * Constructs a new <code>XMLOutputter</code> for the specified * <code>Writer</code> and encoding. This sets the state to * {@link #BEFORE_XML_DECLARATION}. * * <p />The encoding will be stored exactly as passed, leaving the case * intact. * * @param out * the output stream to write to, not <code>null</code>. * * @param encoding * the encoding, not <code>null</code>. * * @throws IllegalStateException * if <code>getState() != {@link #UNINITIALIZED} && * getState() != {@link #AFTER_ROOT_ELEMENT} && * getState() != {@link #ERROR_STATE}</code>. * * @throws IllegalArgumentException * if <code>out == null || encoding == null</code>. * * @throws UnsupportedEncodingException * if the specified encoding is not supported. */ public XMLOutputter(Writer out, String encoding) throws IllegalStateException, IllegalArgumentException, UnsupportedEncodingException { this(); // Initialize reset(out, encoding); } /** * Constructs a new <code>XMLOutputter</code> for the specified * <code>Writer</code> and <code>encoder</code>. This sets the state to * {@link #BEFORE_XML_DECLARATION}. * * @param out * the output stream to write to, not <code>null</code>. * * @param encoder * the encoder, not <code>null</code>. * * @throws IllegalStateException * if <code>getState() != {@link #UNINITIALIZED} && * getState() != {@link #AFTER_ROOT_ELEMENT} && * getState() != {@link #ERROR_STATE}</code>. * * @throws IllegalArgumentException * if <code>out == null || encoder == null</code>. * * @throws UnsupportedEncodingException * if the specified encoding is not supported. */ public XMLOutputter(Writer out, XMLEncoder encoder) throws IllegalStateException, IllegalArgumentException, UnsupportedEncodingException { this(); // Initialize reset(out, encoder); } //------------------------------------------------------------------------- // Fields //------------------------------------------------------------------------- /** * The output stream this outputter will write to. * * <p>This field is initialized by the constructor. It can never be * <code>null</code>. * * <p />The value of this field is returned by {@link #getWriter()}. */ private Writer _out; /** * The encoder used to actually encode character streams. */ private XMLEncoder _encoder; /** * The state of this outputter. */ private XMLEventListenerState _state = UNINITIALIZED; /** * Stack of open elements. * * <p>This field is initialized by the constructor. It can never be * <code>null</code>. * * @since xmlenc 0.22 */ private String[] _elementStack; /** * The size of the element stack. The actual capacity is * {@link #_elementStack}<code>.length</code>. * * @since xmlenc 0.22 */ private int _elementStackSize; /** * Indicates if an apostrophe is used as the quotation mark. If this field * is set to <code>false</code>, then the double quote character * <code>'"'</code> will be used as the quotation mark. If it is set to * <code>true</code>, then the apostrophe character <code>'\''</code> will * be used as the quotation mark. * * <p />The value of this field can be set using * {@link #setQuotationMark(char)} and can be retrieved * using {@link #getQuotationMark()}. */ private boolean _quotationMarkApostrophe; /** * Flag that indicates if ampersands should be escaped. */ private boolean _escapeAmpersands = true; /** * The line break that is currently in use. Should never become * <code>null</code>. */ private LineBreak _lineBreak = LineBreak.NONE; /** * The currently used indentation string. Should never become * <code>null</code>. */ private String _indentation; //------------------------------------------------------------------------- // Methods //------------------------------------------------------------------------- /** * Writes the indentation to the output stream. */ private final void writeIndentation() throws IOException { // TODO: Cache if indentation is available in a boolean field ? if (_indentation.length() != 0) { int count = _elementStackSize - 1; for (int i = 0; i < count; i++) { _out.write(_indentation); } } } /** * Returns the output stream this outputter uses. * * @return * the output stream of this encoding, only <code>null</code> if and * only if the state is {@link #UNINITIALIZED}. */ public final Writer getWriter() { return _out; } /** * Returns the encoding of this outputter. * * @return * the encoding used by this outputter, only <code>null</code> if and * only if the state is {@link #UNINITIALIZED}. */ public final String getEncoding() { // XXX: Are there no cases where _encoder can be null ? if (_encoder == null) { return null; } else { return _encoder.getEncoding(); } } /** * Resets this <code>XMLOutputter</code>. The <code>Writer</code> and the * encoding will be set to <code>null</code>, the element stack will be * cleared, the state will be set to {@link #UNINITIALIZED}, the line break * will be set to {@link LineBreak#NONE} and the indentation will be set to * {@link #DEFAULT_INDENTATION} (an empty string). */ public void reset() { _out = null; _encoder = null; _elementStackSize = 0; _state = UNINITIALIZED; _lineBreak = LineBreak.NONE; _indentation = DEFAULT_INDENTATION; } // TODO: Document this method private final void reset(Writer out) throws IllegalArgumentException, UnsupportedEncodingException { // Check preconditions if (out == null) { throw new IllegalArgumentException("out == null"); } // Reset the fields _out = out; _state = BEFORE_XML_DECLARATION; _elementStackSize = 0; _lineBreak = LineBreak.NONE; _indentation = DEFAULT_INDENTATION; } /** * Resets this <code>XMLOutputter</code> and configures it for the * specified output stream and encoding. This resets the state to * {@link #BEFORE_XML_DECLARATION} and clears the stack of open elements. * * @param out * the output stream to write to, not <code>null</code>. * * @param encoding * the encoding, not <code>null</code>. * * @throws IllegalArgumentException * if <code>out == null || encoding == null</code>. * * @throws UnsupportedEncodingException * if the specified encoding is not supported. */ public final void reset(Writer out, String encoding) throws IllegalArgumentException, UnsupportedEncodingException { // Check arguments if (encoding == null) { throw new IllegalArgumentException("encoding == null"); } reset(out); // Store the fields _encoder = XMLEncoder.getEncoder(encoding); } /** * Resets this <code>XMLOutputter</code> and configures it for the * specified output stream and encoder. This resets the state to * {@link #BEFORE_XML_DECLARATION} and clears the stack of open elements. * * @param out * the output stream to write to, not <code>null</code>. * * @param encoder * the encoder, not <code>null</code>. * * @throws IllegalArgumentException * if <code>out == null || encoder == null</code>. * * @throws UnsupportedEncodingException * if the specified encoding is not supported. */ public final void reset(Writer out, XMLEncoder encoder) throws IllegalArgumentException, UnsupportedEncodingException { // Check arguments if (encoder == null) { throw new IllegalArgumentException("encoder == null"); } reset(out); // Store the fields _encoder = encoder; } /** * Sets the state of this outputter. Normally, it is not necessary to call * this method. * * <p />Calling this method with {@link #UNINITIALIZED} as the state is * equivalent to calling {@link #reset()}. * * <p />Caution: This method can be used to let this class generate invalid * XML. * * @param newState * the new state, not <code>null</code>. * * @param newElementStack * the new element stack, if <code>newState == START_TAG_OPEN * || newState == WITHIN_ELEMENT</code> then it should be * non-<code>null</code> and containing no <code>null</code> elements, * otherwise it must be <code>null</code>. * * @throws IllegalArgumentException * if <code>newState == null * || (newState == {@link #START_TAG_OPEN} && newElementStack == null) * || (newState == {@link #WITHIN_ELEMENT} && newElementStack == null) * || (newState != {@link #START_TAG_OPEN} && newState != {@link #WITHIN_ELEMENT} && newElementStack != null) * || newElementStack[<i>n</i>] == null</code> (where <code>0 <= <i>n</i> < newElementStack.length</code>). * * @since xmlenc 0.22 */ public final void setState(XMLEventListenerState newState, String[] newElementStack) throws IllegalArgumentException { // Check arguments if (newState == null) { throw new IllegalArgumentException("newState == null"); } else if (newState == START_TAG_OPEN && newElementStack == null) { throw new IllegalArgumentException("newState == START_TAG_OPEN && newElementStack == null"); } else if (newState == WITHIN_ELEMENT && newElementStack == null) { throw new IllegalArgumentException("newState == WITHIN_ELEMENT && newElementStack == null"); } else if (newState != START_TAG_OPEN && newState != WITHIN_ELEMENT && newElementStack != null) { throw new IllegalArgumentException("newState != START_TAG_OPEN && newState != WITHIN_ELEMENT && newElementStack != null"); } if (newElementStack != null) { for (int i = 0; i < newElementStack.length; i++) { if (newElementStack[i] == null) { throw new IllegalArgumentException("newElementStack[" + i + "] == null"); } } if (newElementStack.length > _elementStack.length) { try { _elementStack = new String[newElementStack.length + 16]; } catch (OutOfMemoryError error) { _elementStack = new String[newElementStack.length]; } } System.arraycopy(newElementStack, 0, _elementStack, 0, newElementStack.length); } if (newState == UNINITIALIZED) { reset(); } else { _state = newState; _elementStackSize = newElementStack == null ? 0 : newElementStack.length; } } /** * Returns the current state of this outputter. * * @return * the current state, cannot be <code>null</code>. */ public final XMLEventListenerState getState() { return _state; } /** * Checks if escaping is currently enabled. If escaping is enabled, then * all ampersand characters (<code>'&'</code>) are replaced by the * character entity reference <code>"&amp;"</code>. This affects * PCDATA string printing ({@link #pcdata(String)} and * {@link #pcdata(char[],int,int)}) and attribute value printing * ({@link #attribute(String,String)}). * * @return * <code>true</code> if escaping is enabled, <code>false</code> * otherwise. */ public final boolean isEscaping() { return _escapeAmpersands; } /** * Sets if ampersands should be escaped. This affects PCDATA string * printing ({@link #pcdata(String)} and * {@link #pcdata(char[],int,int)}) and attribute value printing * ({@link #attribute(String,String)}). * * <p />If ampersands are not escaped, then entity references can be * printed. * * @param escapeAmpersands * <code>true</code> if ampersands should be escaped, <code>false</code> * otherwise. * * @since xmlenc 0.24 */ public final void setEscaping(boolean escapeAmpersands) { // TODO: Check if this really only applies to pcdata() and attribute() // methods _escapeAmpersands = escapeAmpersands; } /** * Returns a copy of the element stack. The returned array will be a new * array. The size of the array will be equal to the element stack size * (see {@link #getElementStackSize()}. * * @return * a newly constructed array that contains all the element types * currently on the element stack, or <code>null</code> if there are no * elements on the stack. * * @since xmlenc 0.22 */ public final String[] getElementStack() { if (_elementStackSize == 0) { return null; } else { String[] newStack = new String[_elementStackSize]; System.arraycopy(_elementStack, 0, newStack, 0, _elementStackSize); return newStack; } } /** * Returns the current depth of open elements. * * @return * the open element depth, always >= 0. * * @since xmlenc 0.22 */ public final int getElementStackSize() { return _elementStackSize; } /** * Returns the current capacity for the stack of open elements. * * @return * the open element stack capacity, always >= * {@link #getElementStackSize()}. * * @since xmlenc 0.28 */ public final int getElementStackCapacity() { return _elementStack.length; } /** * Sets the capacity for the stack of open elements. The new capacity must * at least allow the stack to contain the current open elements. * * @param newCapacity * the new capacity, >= {@link #getElementStackSize()}. * * @throws IllegalArgumentException * if <code>newCapacity < {@link #getElementStackSize()}</code>. * * @throws OutOfMemoryError * if a new array cannot be allocated; this object will still be usable, * but the capacity will remain unchanged. */ public final void setElementStackCapacity(int newCapacity) throws IllegalArgumentException, OutOfMemoryError { // Check argument if (newCapacity < _elementStack.length) { throw new IllegalArgumentException("newCapacity < getElementStackSize()"); } int currentCapacity = _elementStack.length; // Short-circuit if possible if (currentCapacity == newCapacity) { return; } String[] newStack = new String[newCapacity]; System.arraycopy(_elementStack, 0, newStack, 0, _elementStackSize); _elementStack = newStack; } /** * Sets the quotation mark character to use. This character is printed * before and after an attribute value. It can be either the single or the * double quote character. * * <p />The default quotation mark character is <code>'"'</code>. * * @param c * the character to put around attribute values, either * <code>'\''</code> or <code>'"'</code>. * * @throws IllegalArgumentException * if <code>c != '\'' && c != '"'</code>. */ public final void setQuotationMark(char c) throws IllegalArgumentException { if (c == '\'') { _quotationMarkApostrophe = true; } else if (c == '"') { _quotationMarkApostrophe = false; } else { throw new IllegalArgumentException("c != '\\'' && c != '\"'"); } } /** * Gets the quotation mark character. This character is used to mark the * start and end of an attribute value. * * <p />The default quotation mark character is <code>'"'</code>. * * @return * the character to put around attribute values, either * <code>'\''</code> or <code>'"'</code>. */ public final char getQuotationMark() { return _quotationMarkApostrophe ? '\'' : '"'; } /** * Sets the type of line break to use. * * @param lineBreak * the line break to use; specifying <code>null</code> as the argument * is equivalent to specifying {@link LineBreak#NONE}. */ public final void setLineBreak(LineBreak lineBreak) { _lineBreak = lineBreak != null ? lineBreak : LineBreak.NONE; } /** * Returns the currently used line break. * * @return * the currently used line break, never <code>null</code>. */ public final LineBreak getLineBreak() { return _lineBreak; } /** * Sets the string to be used for indentation. * * @param indentation * the character string used for indentation, or <code>null</code> if * {@link #DEFAULT_INDENTATION the default indentation} should be used. */ public final void setIndentation(String indentation) { _indentation = indentation != null ? indentation : DEFAULT_INDENTATION; } /** * Returns the string currently used for indentation. * * @return * the character string used for indentation, never <code>null</code>. */ public final String getIndentation() { return _indentation; } /** * Closes an open start tag. * * @throws IOException * if an I/O error occurs. */ private void closeStartTag() throws IOException { _out.write('>'); } /** * Writes the XML declaration. This method always prints the name of the * encoding. The case of the encoding is as it was specified during * initialization (or re-initialization). * * <p />If the encoding is set to <code>"ISO-8859-1"</code>, then this * method will produce the following output: * * <blockquote><code><?xml version="1.0" encoding="ISO-8859-1"?gt;</code></blockquote> * * @throws IllegalStateException * if <code>getState() != BEFORE_XML_DECLARATION</code>. * * @throws IOException * if an I/O error occurs; this will set the state to * {@link #ERROR_STATE}. */ public final void declaration() throws IllegalStateException, IOException { // Check state if (_state != BEFORE_XML_DECLARATION) { throw new IllegalStateException("getState() == " + _state); } // Temporarily set the state to ERROR_STATE, unless an exception is // thrown in the write methods, it will be reset to a valid state _state = ERROR_STATE; // Write the output _encoder.declaration(_out); _out.write(_lineBreak.toString()); // Change the state _state = BEFORE_DTD_DECLARATION; } /** * Writes a document type declaration. * * <p />An external subset can be specified using either a * <em>system identifier</em> (alone), or using both a * <em>public identifier</em> and a <em>system identifier</em>. It can * never be specified using a <em>public identifier</em> alone. * * <p />For example, for XHTML 1.0 the public identifier is: * * <blockquote><code>-//W3C//DTD XHTML 1.0 Transitional//EN</code></blockquote> * * <p />while the system identifier is: * * <blockquote><code>http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd</code></blockquote> * * <p />The output is typically similar to this: * * <blockquote><code><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"></code></blockquote> * * or alternatively, if only the <em>system identifier</em> is specified: * * <blockquote><code><!DOCTYPE html SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"></code></blockquote> * * @param name * the name of the document type, not <code>null</code>. * * @param publicID * the public identifier, can be <code>null</code>. * * @param systemID * the system identifier, can be <code>null</code>, but otherwise * it should be a properly formatted URL, see * <a href="http://www.w3.org/TR/2000/REC-xml-20001006#sec-external-ent">section 4.2.2 External Entities</a> * in the XML 1.0 Specification. * * @throws IllegalStateException * if <code>getState() != {@link #BEFORE_XML_DECLARATION} && * getState() != {@link #BEFORE_DTD_DECLARATION}</code>. * * @throws IllegalArgumentException * if <code>name == null || * (publicID != null && systemID == null)</code>. * * @throws IOException * if an I/O error occurs; this will set the state to * {@link #ERROR_STATE}. */ public final void dtd(String name, String publicID, String systemID) throws IllegalStateException, IllegalArgumentException, IOException { // Check state if (_state != BEFORE_XML_DECLARATION && _state != BEFORE_DTD_DECLARATION) { throw new IllegalStateException("getState() == " + _state); } // Check arguments if (name == null) { throw new IllegalArgumentException("name == null"); } else if (publicID != null && systemID == null) { throw new IllegalArgumentException("Found public identifier, but no system identifier."); } // Temporarily set the state to ERROR_STATE, unless an exception is // thrown in the write methods, it will be reset to a valid state _state = ERROR_STATE; // Write the DTD reference _out.write("<!DOCTYPE "); _out.write(name); if (publicID != null) { _out.write(" PUBLIC \""); _out.write(publicID); _out.write("\" \""); _out.write(systemID); _out.write('"'); } else if (systemID != null) { _out.write(" SYSTEM \""); _out.write(systemID); _out.write('"'); } closeStartTag(); // Change the state _state = BEFORE_ROOT_ELEMENT; } /** * Writes an element start tag. The element type name will be stored in the * internal element stack. If necessary, the capacity of this stack will be * extended. * * @param type * the type of the tag to start, not <code>null</code>. * * @throws IllegalStateException * if <code>getState() != {@link #BEFORE_XML_DECLARATION} && * getState() != {@link #BEFORE_DTD_DECLARATION} && * getState() != {@link #BEFORE_ROOT_ELEMENT} && * getState() != {@link #START_TAG_OPEN} && * getState() != {@link #WITHIN_ELEMENT}</code>. * * @throws IllegalArgumentException * if <code>type == null</code>. * * @throws IOException * if an I/O error occurs; this will set the state to * {@link #ERROR_STATE}. */ public final void startTag(String type) throws IllegalStateException, IllegalArgumentException, IOException { // Check state if (_state != BEFORE_XML_DECLARATION && _state != BEFORE_DTD_DECLARATION && _state != BEFORE_ROOT_ELEMENT && _state != START_TAG_OPEN && _state != WITHIN_ELEMENT) { throw new IllegalStateException("getState() == " + _state); // Check arguments } else if (type == null) { throw new IllegalArgumentException("type == null"); } boolean startTagOpen = _state == START_TAG_OPEN; // Temporarily set the state to ERROR_STATE, unless an exception is // thrown in the write methods, it will be reset to a valid state _state = ERROR_STATE; // Increase the stack size with 100% if necessary if (_elementStackSize == _elementStack.length) { String[] newStack; try { newStack = new String[(_elementStackSize + 1) * 2]; } catch (OutOfMemoryError error) { newStack = new String[_elementStackSize + 1]; } System.arraycopy(_elementStack, 0, newStack, 0, _elementStackSize); _elementStack = newStack; } // Store the element type name on the stack _elementStack[_elementStackSize] = type; _elementStackSize++; // Close start tag if necessary and then write a raw '<' // followed by the type if (startTagOpen) { _out.write('>'); } _out.write(_lineBreak.toString()); writeIndentation(); _out.write('<'); // Escape the element name, if necessary _out.write(type); // Change the state _state = START_TAG_OPEN; } /** * Adds an attribute to the current element. There must currently be an * open element. * * <p />The attribute value is surrounded by single quotes. * * @param name * the name of the attribute, not <code>null</code>. * * @param value * the value of the attribute, not <code>null</code>. * * @throws IllegalStateException * if <code>getState() != {@link #START_TAG_OPEN}</code>. * * @throws IllegalArgumentException * if <code>name == null || value == null</code>. * * @throws IOException * if an I/O error occurs; this will set the state to * {@link #ERROR_STATE}. */ public final void attribute(String name, String value) throws IllegalStateException, IllegalArgumentException, IOException { // Check state if (getState() != START_TAG_OPEN) { throw new IllegalStateException("getState() == " + _state); // Check arguments } else if (name == null || value == null) { if (name == null && value == null) { throw new IllegalArgumentException("name == null && value == null"); } else if (name == null) { throw new IllegalArgumentException("name == null"); } else { throw new IllegalArgumentException("value == null"); } } // Temporarily set the state to ERROR_STATE, unless an exception is // thrown in the write methods, it will be reset to a valid state _state = ERROR_STATE; // Write output _encoder.attribute(_out, name, value, _quotationMarkApostrophe, _escapeAmpersands); // Reset the state _state = START_TAG_OPEN; } /** * Writes an element end tag. * * @throws IllegalStateException * if <code>getState() != {@link #START_TAG_OPEN} && * getState() != {@link #WITHIN_ELEMENT}</code> * * @throws IOException * if an I/O error occurs; this will set the state to * {@link #ERROR_STATE}. */ public final void endTag() throws IllegalStateException, IOException { // Check state if (_state != WITHIN_ELEMENT && _state != START_TAG_OPEN) { throw new IllegalStateException("getState() == " + _state); } boolean startTagOpen = _state == START_TAG_OPEN; // Temporarily set the state to ERROR_STATE, unless an exception is // thrown in the write methods, it will be reset to a valid state _state = ERROR_STATE; String type = _elementStack[_elementStackSize-1]; // Write output if (startTagOpen) { _out.write("/>"); _out.write(_lineBreak.toString()); } else { _out.write(_lineBreak.toString()); if (_lineBreak != LineBreak.NONE) { writeIndentation(); } _out.write("</"); _out.write(type); closeStartTag(); } _elementStackSize--; // Change the state if (_elementStackSize == 0) { _state = AFTER_ROOT_ELEMENT; } else { _state = WITHIN_ELEMENT; } } /** * Writes the specified <code>String</code> as PCDATA. * * @param text * the PCDATA text to be written, not <code>null</code>. * * @throws IllegalStateException * if <code>getState() != {@link #START_TAG_OPEN} && * getState() != {@link #WITHIN_ELEMENT}</code> * * @throws IllegalArgumentException * if <code>text == null</code>. * * @throws InvalidXMLException * if the specified text contains an invalid character. * * @throws IOException * if an I/O error occurs; this will set the state to * {@link #ERROR_STATE}. */ public final void pcdata(String text) throws IllegalStateException, IllegalArgumentException, InvalidXMLException, IOException { // Check state if (_state != START_TAG_OPEN && _state != WITHIN_ELEMENT) { throw new IllegalStateException("getState() == " + _state); // Check arguments } else if (text == null) { throw new IllegalArgumentException("text == null"); } boolean startTagOpen = _state == START_TAG_OPEN; // Temporarily set the state to ERROR_STATE, unless an exception is // thrown in the write methods, it will be reset to a valid state _state = ERROR_STATE; // Write output if (startTagOpen) { closeStartTag(); _out.write(_lineBreak.toString()); } _encoder.text(_out, text, _escapeAmpersands); // Change the state _state = WITHIN_ELEMENT; } /** * Writes the specified character array as PCDATA. * * @param ch * the character array containing the text to be written, not * <code>null</code>. * * @param start * the start index in the array, must be >= 0 and it must be < * <code>ch.length</code>. * * @param length * the number of characters to read from the array, must be > 0. * * @throws IllegalStateException * if <code>getState() != {@link #START_TAG_OPEN} && * getState() != {@link #WITHIN_ELEMENT}</code> * * @throws IllegalArgumentException * if <code>ch == null * || start < 0 * || start >= ch.length * || length < 0</code>. * * @throws IndexOutOfBoundsException * if <code>start + length > ch.length</code>. * * @throws InvalidXMLException * if the specified text contains an invalid character. * * @throws IOException * if an I/O error occurs; this will set the state to * {@link #ERROR_STATE}. */ public final void pcdata(char[] ch, int start, int length) throws IllegalStateException, IllegalArgumentException, IndexOutOfBoundsException, InvalidXMLException, IOException { // Check state if (_state != START_TAG_OPEN && _state != WITHIN_ELEMENT) { throw new IllegalStateException("getState() == " + _state); // Check arguments } else if (ch == null) { throw new IllegalArgumentException("ch == null"); } else if (start < 0) { throw new IllegalArgumentException("start (" + start + ") < 0"); } else if (start >= ch.length) { throw new IllegalArgumentException("start (" + start + ") >= ch.length (" + ch.length + ')'); } else if (length < 0) { throw new IllegalArgumentException("length < 0"); } boolean startTagOpen = _state == START_TAG_OPEN; // Temporarily set the state to ERROR_STATE, unless an exception is // thrown in the write methods, it will be reset to a valid state _state = ERROR_STATE; // Write output if (startTagOpen) { closeStartTag(); } _encoder.text(_out, ch, start, length, _escapeAmpersands); // Change the state _state = WITHIN_ELEMENT; } /** * Writes the specified ignorable whitespace. Ignorable whitespace may be * written anywhere in XML output stream, except above the XML declaration. * * <p />If the state equals {@link #BEFORE_XML_DECLARATION}, then it will be set to * {@link #BEFORE_DTD_DECLARATION}, otherwise if the state is * {@link #START_TAG_OPEN} then it will be set to {@link #WITHIN_ELEMENT}, * otherwise the state will not be changed. * * @param whitespace * the ignorable whitespace to be written, not <code>null</code>. * * @throws IllegalStateException * if <code>getState() != {@link #BEFORE_XML_DECLARATION} && * getState() != {@link #BEFORE_DTD_DECLARATION} && * getState() != {@link #BEFORE_ROOT_ELEMENT} && * getState() != {@link #START_TAG_OPEN} && * getState() != {@link #WITHIN_ELEMENT} && * getState() != {@link #AFTER_ROOT_ELEMENT}</code>. * * @throws IllegalArgumentException * if <code>whitespace == null</code>. * * @throws InvalidXMLException * if the specified character string contains a character that is * invalid as whitespace. * * @throws IOException * if an I/O error occurs; this will set the state to * {@link #ERROR_STATE}. */ public final void whitespace(String whitespace) throws IllegalStateException, IllegalArgumentException, InvalidXMLException, IOException { // Check state if (_state != BEFORE_XML_DECLARATION && _state != BEFORE_DTD_DECLARATION && _state != BEFORE_ROOT_ELEMENT && _state != START_TAG_OPEN && _state != WITHIN_ELEMENT && _state != AFTER_ROOT_ELEMENT) { throw new IllegalStateException("getState() == " + _state); // Check arguments } else if (whitespace == null) { throw new IllegalArgumentException("whitespace == null"); } XMLEventListenerState oldState = _state; // Temporarily set the state to ERROR_STATE, unless an exception is // thrown in the write methods, it will be reset to a valid state _state = ERROR_STATE; // Write output if (oldState == START_TAG_OPEN) { closeStartTag(); } // Do the actual output _encoder.whitespace(_out, whitespace); // Change state if (oldState == BEFORE_XML_DECLARATION) { _state = BEFORE_DTD_DECLARATION; } else if (oldState == START_TAG_OPEN) { _state = WITHIN_ELEMENT; } else { _state = oldState; } } /** * Writes text from the specified character array as ignorable whitespace. * Ignorable whitespace may be written anywhere in XML output stream, * except above the XML declaration. * * <p />This method does not check if the string actually contains * whitespace. * * <p />If the state equals {@link #BEFORE_XML_DECLARATION}, then it will be set to * {@link #BEFORE_DTD_DECLARATION}, otherwise if the state is * {@link #START_TAG_OPEN} then it will be set to {@link #WITHIN_ELEMENT}, * otherwise the state will not be changed. * * @param ch * the character array containing the text to be written, not * <code>null</code>. * * @param start * the start index in the array, must be >= 0 and it must be < * <code>ch.length</code>. * * @param length * the number of characters to read from the array, must be > 0. * * @throws IllegalStateException * if <code>getState() != {@link #BEFORE_XML_DECLARATION} && * getState() != {@link #BEFORE_DTD_DECLARATION} && * getState() != {@link #BEFORE_ROOT_ELEMENT} && * getState() != {@link #START_TAG_OPEN} && * getState() != {@link #WITHIN_ELEMENT} && * getState() != {@link #AFTER_ROOT_ELEMENT}</code>. * * @throws IllegalArgumentException * if <code>ch == null * || start < 0 * || start >= ch.length * || length < 0</code>. * * @throws IndexOutOfBoundsException * if <code>start + length > ch.length</code>. * * @throws InvalidXMLException * if the specified character string contains a character that is * invalid as whitespace. * * @throws IOException * if an I/O error occurs; this will set the state to * {@link #ERROR_STATE}. */ public final void whitespace(char[] ch, int start, int length) throws IllegalStateException, IllegalArgumentException, IndexOutOfBoundsException, InvalidXMLException, IOException { // Check state if (_state != BEFORE_XML_DECLARATION && _state != BEFORE_DTD_DECLARATION && _state != BEFORE_ROOT_ELEMENT && _state != START_TAG_OPEN && _state != WITHIN_ELEMENT && _state != AFTER_ROOT_ELEMENT) { throw new IllegalStateException("getState() == " + _state); // Check arguments } else if (ch == null) { throw new IllegalArgumentException("ch == null"); } else if (start < 0) { throw new IllegalArgumentException("start (" + start + ") < 0"); } else if (start >= ch.length) { throw new IllegalArgumentException("start (" + start + ") >= ch.length (" + ch.length + ')'); } else if (length < 0) { throw new IllegalArgumentException("length < 0"); } XMLEventListenerState oldState = _state; // Temporarily set the state to ERROR_STATE, unless an exception is // thrown in the write methods, it will be reset to a valid state _state = ERROR_STATE; // Write output if (oldState == START_TAG_OPEN) { closeStartTag(); } // Do the actual output _encoder.whitespace(_out, ch, start, length); // Change state if (oldState == BEFORE_XML_DECLARATION) { _state = BEFORE_DTD_DECLARATION; } else if (oldState == START_TAG_OPEN) { _state = WITHIN_ELEMENT; } else { _state = oldState; } } /** * Writes the specified comment. The comment should not contain the string * <code>"--"</code>. * * <p />If the state equals {@link #BEFORE_XML_DECLARATION}, then it will be set to * {@link #BEFORE_DTD_DECLARATION}, otherwise if the state is * {@link #START_TAG_OPEN} then it will be set to {@link #WITHIN_ELEMENT}, * otherwise the state will not be changed. * * @param text * the text for the comment be written, not <code>null</code>. * * @throws IllegalStateException * if <code>getState() != {@link #BEFORE_XML_DECLARATION} && * getState() != {@link #BEFORE_DTD_DECLARATION} && * getState() != {@link #BEFORE_ROOT_ELEMENT} && * getState() != {@link #START_TAG_OPEN} && * getState() != {@link #WITHIN_ELEMENT} && * getState() != {@link #AFTER_ROOT_ELEMENT}</code>. * * @throws IllegalArgumentException * if <code>text == null</code>. * * @throws InvalidXMLException * if the specified text contains an invalid character. * * @throws IOException * if an I/O error occurs; this will set the state to * {@link #ERROR_STATE}. */ public final void comment(String text) throws IllegalStateException, IllegalArgumentException, InvalidXMLException, IOException { // Check arguments if (_state != BEFORE_XML_DECLARATION && _state != BEFORE_DTD_DECLARATION && _state != BEFORE_ROOT_ELEMENT && _state != START_TAG_OPEN && _state != WITHIN_ELEMENT && _state != AFTER_ROOT_ELEMENT) { throw new IllegalStateException("getState() == " + _state); // Check arguments } else if (text == null) { throw new IllegalArgumentException("text == null"); } XMLEventListenerState oldState = _state; // Temporarily set the state to ERROR_STATE, unless an exception is // thrown in the write methods, it will be reset to a valid state _state = ERROR_STATE; // Write output if (oldState == START_TAG_OPEN) { _out.write("><!--"); } else { _out.write("<!--"); } _encoder.text(_out, text, _escapeAmpersands); _out.write("-->"); _out.write(_lineBreak.toString()); // Change state if (oldState == BEFORE_XML_DECLARATION) { _state = BEFORE_DTD_DECLARATION; } else if (oldState == START_TAG_OPEN) { _state = WITHIN_ELEMENT; } else { _state = oldState; } } /** * Writes a processing instruction. A target and an optional instruction * should be specified. * * <p />A processing instruction can appear above and below the root * element, and between elements. It cannot appear inside an element start * or end tag, nor inside a comment. Processing instructions cannot be * nested. * * <p />If the state equals {@link #BEFORE_XML_DECLARATION}, then it will be set to * {@link #BEFORE_DTD_DECLARATION}, otherwise the state will not be * changed. * * @param target * an identification of the application at which the instruction is * targeted, not <code>null</code>. * * @param instruction * the instruction, can be <code>null</code>, which is equivalent to an * empty string. * * @throws IllegalStateException * if <code>getState() != {@link #BEFORE_XML_DECLARATION} && * getState() != {@link #BEFORE_DTD_DECLARATION} && * getState() != {@link #BEFORE_ROOT_ELEMENT} && * getState() != {@link #START_TAG_OPEN} && * getState() != {@link #WITHIN_ELEMENT} && * getState() != {@link #AFTER_ROOT_ELEMENT}</code>. * * @throws IllegalArgumentException * if <code>target == null</code>. * * @throws IOException * if an I/O error occurs; this will set the state to * {@link #ERROR_STATE}. */ public final void pi(String target, String instruction) throws IllegalStateException, IllegalArgumentException, IOException { // Check state if (_state != BEFORE_XML_DECLARATION && _state != BEFORE_DTD_DECLARATION && _state != BEFORE_ROOT_ELEMENT && _state != START_TAG_OPEN && _state != WITHIN_ELEMENT && _state != AFTER_ROOT_ELEMENT) { throw new IllegalStateException("getState() == " + _state); // Check arguments } else if (target == null) { throw new IllegalArgumentException("target == null"); } XMLEventListenerState oldState = _state; // Temporarily set the state to ERROR_STATE, unless an exception is // thrown in the write methods, it will be reset to a valid state _state = ERROR_STATE; // Complete the start tag if necessary if (oldState == START_TAG_OPEN) { closeStartTag(); } // Write the Processing Instruction _out.write("<?"); _out.write(target); if (instruction != null) { _out.write(' '); _out.write(instruction); } _out.write("?>"); // Change the state if (oldState == BEFORE_XML_DECLARATION) { _state = BEFORE_DTD_DECLARATION; } else if (oldState == START_TAG_OPEN) { _state = WITHIN_ELEMENT; } else { _state = oldState; } } /** * Writes a CDATA section. * * <p />A CDATA section can contain any string, except * <code>"]]>"</code>. This will, however, not be checked by this * method. * * <p />Left angle brackets and ampersands will be output in their literal * form; they need not (and cannot) be escaped using * <code>"&lt;"</code> and <code>"&amp;"</code>. * * <p />If the specified string is empty (i.e. * <code>"".equals(text)</code>, then nothing will be output. * * <p />If the specified string contains characters that cannot be printed * in this encoding, then the result is undefined. * * @param text * the contents of the CDATA section, not <code>null</code>. * * @throws IllegalStateException * if <code>getState() != {@link #START_TAG_OPEN} && * getState() != {@link #WITHIN_ELEMENT}</code> * * @throws IllegalArgumentException * if <code>text == null</code>. * * @throws IOException * if an I/O error occurs; this will set the state to * {@link #ERROR_STATE}. */ public final void cdata(String text) throws IllegalStateException, IllegalArgumentException, IOException { // Check state if (_state != START_TAG_OPEN && _state != WITHIN_ELEMENT) { throw new IllegalStateException("getState() == " + _state); // Check arguments } else if (text == null) { throw new IllegalArgumentException("text == null"); } boolean startTagOpen = _state == START_TAG_OPEN; // Temporarily set the state to ERROR_STATE, unless an exception is // thrown in the write methods, it will be reset to a valid state _state = ERROR_STATE; // Complete the start tag if necessary if (startTagOpen) { closeStartTag(); } _out.write("<![CDATA["); _out.write(text); _out.write("]]>"); // Change state _state = WITHIN_ELEMENT; } /** * Closes all open elements. After calling this method, only the * {@link #whitespace(String)} method can be called. * * <p />If you would like to flush the output stream as well, call * {@link #endDocument()} instead. * * @throws IllegalStateException * if <code>getState() != {@link #START_TAG_OPEN} && * getState() != {@link #WITHIN_ELEMENT} && * getState() != {@link #AFTER_ROOT_ELEMENT}</code> * * @throws IOException * if an I/O error occurs; this will set the state to * {@link #ERROR_STATE}. */ public final void close() throws IllegalStateException, IOException { // Check state if (_state != START_TAG_OPEN && _state != WITHIN_ELEMENT && _state != AFTER_ROOT_ELEMENT) { throw new IllegalStateException("getState() == " + _state); } while (_elementStackSize > 0) { endTag(); } } /** * Ends the XML output. All open elements will be closed and the underlying * output stream will be flushed using * {@link #getWriter()}.{@link java.io.Writer#flush() flush()}. * * <p />After calling this method, no more output can be * written until this outputter is reset. * * @throws IllegalStateException * if <code>getState() != {@link #START_TAG_OPEN} && * getState() != {@link #WITHIN_ELEMENT} && * getState() != {@link #AFTER_ROOT_ELEMENT}</code>. * * @throws IOException * if an I/O error occurs; this will set the state to * {@link #ERROR_STATE}. */ public final void endDocument() throws IllegalStateException, IOException { // Check state if (_state != START_TAG_OPEN && _state != WITHIN_ELEMENT && _state != AFTER_ROOT_ELEMENT) { throw new IllegalStateException("getState() == " + _state); } // Close all open elements close(); // Flush the output stream _out.flush(); // Finally change the state _state = DOCUMENT_ENDED; } }