| 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;
}
}