Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.restlet.util.XmlWriter Maven / Gradle / Ivy
Go to download
This OSGi bundle wraps org.restlet, and com.noelios.restlet ${pkgVersion} jar files.
/**
* Copyright 2005-2008 Noelios Technologies.
*
* The contents of this file are subject to the terms of the following open
* source licenses: LGPL 3.0 or LGPL 2.1 or CDDL 1.0 (the "Licenses"). You can
* select the license that you prefer but you may not use this file except in
* compliance with one of these Licenses.
*
* You can obtain a copy of the LGPL 3.0 license at
* http://www.gnu.org/licenses/lgpl-3.0.html
*
* You can obtain a copy of the LGPL 2.1 license at
* http://www.gnu.org/licenses/lgpl-2.1.html
*
* You can obtain a copy of the CDDL 1.0 license at
* http://www.sun.com/cddl/cddl.html
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royaltee free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://www.noelios.com/products/restlet-engine
*
* Restlet is a registered trademark of Noelios Technologies.
*/
package org.restlet.util;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.Enumeration;
import java.util.Map;
import java.util.Stack;
import java.util.TreeMap;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.NamespaceSupport;
import org.xml.sax.helpers.XMLFilterImpl;
/**
* XML writer doing the opposite work of a SAX-based XML reader. The
* implementation is based on the work of David Megginson, the creator of SAX
* who placed the original code in the public domain.
*
*
* This class can be used by itself or as part of a SAX event stream: it takes
* as input a series of SAX2 ContentHandler events and uses the information in
* those events to write an XML document. Since this class is a filter, it can
* also pass the events on down a filter chain for further processing (you can
* use the XmlWriter to take a snapshot of the current state at any point in a
* filter chain), and it can be used directly as a ContentHandler for a SAX2
* XMLReader.
*
*
*
* The client creates a document by invoking the methods for standard SAX2
* events, always beginning with the {@link #startDocument startDocument} method
* and ending with the {@link #endDocument endDocument} method. There are
* convenience methods provided so that clients to not have to create empty
* attribute lists or provide empty strings as parameters; for example, the
* method invocation
*
*
*
* w.startElement("foo");
*
*
*
* is equivalent to the regular SAX2 ContentHandler method
*
*
*
* w.startElement("", "foo", "", new AttributesImpl());
*
*
*
* Except that it is more efficient because it does not allocate a new empty
* attribute list each time. The following code will send a simple XML document
* to standard output:
*
*
*
* XmlWriter w = new XmlWriter();
*
* w.startDocument();
* w.startElement("greeting");
* w.characters("Hello, world!");
* w.endElement("greeting");
* w.endDocument();
*
*
*
* The resulting document will look like this:
*
*
*
* <?xml version="1.0" standalone="yes"?>
*
* <greeting>Hello, world!</greeting>
*
*
*
* In fact, there is an even simpler convenience method, dataElement ,
* designed for writing elements that contain only character data, so the code
* to generate the document could be shortened to
*
*
*
* XmlWriter w = new XmlWriter();
*
* w.startDocument();
* w.dataElement("greeting", "Hello, world!");
* w.endDocument();
*
*
* Whitespace
*
*
* According to the XML Recommendation, all whitespace in an XML
* document is potentially significant to an application, so this class never
* adds newlines or indentation. If you insert three elements in a row, as in
*
*
*
* w.dataElement("item", "1");
* w.dataElement("item", "2");
* w.dataElement("item", "3");
*
*
*
* you will end up with
*
*
*
* <item>1</item><item>3</item><item>3</item>
*
*
*
* You need to invoke one of the characters methods explicitly to add
* newlines or indentation. Alternatively, you can use the data format mode (set
* the "dataFormat" property) which is optimized for writing purely
* data-oriented (or field-oriented) XML, and does automatic linebreaks and
* indentation (but does not support mixed content properly). See details below.
*
*
* Namespace Support
*
*
* The writer contains extensive support for XML Namespaces, so that a client
* application does not have to keep track of prefixes and supply
* xmlns attributes. By default, the XML writer will generate
* Namespace declarations in the form _NS1, _NS2, etc., wherever they are
* needed, as in the following example:
*
*
*
* w.startDocument();
* w.emptyElement("http://www.foo.com/ns/", "foo");
* w.endDocument();
*
*
*
* The resulting document will look like this:
*
*
*
* <?xml version="1.0" standalone="yes"?>
*
* <_NS1:foo xmlns:_NS1="http://www.foo.com/ns/"/>
*
*
*
* In many cases, document authors will prefer to choose their own prefixes
* rather than using the (ugly) default names. The XML writer allows two methods
* for selecting prefixes:
*
*
*
* the qualified name
* the {@link #setPrefix setPrefix} method.
*
*
*
* Whenever the XML writer finds a new Namespace URI, it checks to see if a
* qualified (prefixed) name is also available; if so it attempts to use the
* name's prefix (as long as the prefix is not already in use for another
* Namespace URI).
*
*
*
* Before writing a document, the client can also pre-map a prefix to a
* Namespace URI with the setPrefix method:
*
*
*
* w.setPrefix("http://www.foo.com/ns/", "foo");
* w.startDocument();
* w.emptyElement("http://www.foo.com/ns/", "foo");
* w.endDocument();
*
*
*
* The resulting document will look like this:
*
*
*
* <?xml version="1.0" standalone="yes"?>
*
* <foo:foo xmlns:foo="http://www.foo.com/ns/"/>
*
*
*
* The default Namespace simply uses an empty string as the prefix:
*
*
*
* w.setPrefix("http://www.foo.com/ns/", "");
* w.startDocument();
* w.emptyElement("http://www.foo.com/ns/", "foo");
* w.endDocument();
*
*
*
* The resulting document will look like this:
*
*
*
* <?xml version="1.0" standalone="yes"?>
*
* <foo xmlns="http://www.foo.com/ns/"/>
*
*
*
* By default, the XML writer will not declare a Namespace until it is actually
* used. Sometimes, this approach will create a large number of Namespace
* declarations, as in the following example:
*
*
*
* <xml version="1.0" standalone="yes"?>
*
* <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
* <rdf:Description about="http://www.foo.com/ids/books/12345">
* <dc:title xmlns:dc="http://www.purl.org/dc/">A Dark Night</dc:title>
* <dc:creator xmlns:dc="http://www.purl.org/dc/">Jane Smith</dc:title>
* <dc:date xmlns:dc="http://www.purl.org/dc/">2000-09-09</dc:title>
* </rdf:Description>
* </rdf:RDF>
*
*
*
* The "rdf" prefix is declared only once, because the RDF Namespace is used by
* the root element and can be inherited by all of its descendants; the "dc"
* prefix, on the other hand, is declared three times, because no higher element
* uses the Namespace. To solve this problem, you can instruct the XML writer to
* predeclare Namespaces on the root element even if they are not used there:
*
*
*
* w.forceNSDecl("http://www.purl.org/dc/");
*
*
*
* Now, the "dc" prefix will be declared on the root element even though it's
* not needed there, and can be inherited by its descendants:
*
*
*
* <xml version="1.0" standalone="yes"?>
*
* <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
* xmlns:dc="http://www.purl.org/dc/">
* <rdf:Description about="http://www.foo.com/ids/books/12345">
* <dc:title>A Dark Night</dc:title>
* <dc:creator>Jane Smith</dc:title>
* <dc:date>2000-09-09</dc:title>
* </rdf:Description>
* </rdf:RDF>
*
*
*
* This approach is also useful for declaring Namespace prefixes that be used by
* qualified names appearing in attribute values or character data.
*
*
* Data Format
*
*
* This mode, enabled by the "dataFormat" property, pretty-prints field-oriented
* XML without mixed content. All added indentation and newlines will be passed
* on down the filter chain (if any).
*
*
*
* In general, all whitespace in an XML document is potentially significant, so
* a general-purpose XML writing tool cannot add newlines or indentation.
*
*
*
* There is, however, a large class of XML documents where information is
* strictly fielded: each element contains either character data or other
* elements, but not both. For this special case, it is possible for a writing
* tool to provide automatic indentation and newlines without requiring extra
* work from the user. Note that this class will likely not yield appropriate
* results for document-oriented XML like XHTML pages, which mix character data
* and elements together.
*
*
*
* This writer mode will automatically place each start tag on a new line,
* optionally indented if an indent step is provided (by default, there is no
* indentation). If an element contains other elements, the end tag will also
* appear on a new line with leading indentation. Consider, for example, the
* following code:
*
*
*
* XmlWriter w = new XmlWriter();
* w.setDataFormat(true);
* w.setIndentStep(2);
* w.startDocument();
* w.startElement("Person");
* w.dataElement("name", "Jane Smith");
* w.dataElement("date-of-birth", "1965-05-23");
* w.dataElement("citizenship", "US");
* w.endElement("Person");
* w.endDocument();
*
*
*
* This code will produce the following document:
*
*
*
* <?xml version="1.0" standalone="yes"?>
*
* <Person>
* <name>Jane Smith</name>
* <date-of-birth>1965-05-23</date-of-birth>
* <citizenship>US</citizenship>
* </Person>
*
*
* @see org.xml.sax.XMLFilter
* @see org.xml.sax.ContentHandler
* @author David Megginson, Jerome Louvel ([email protected] )
*/
public final class XmlWriter extends XMLFilterImpl {
private static final Object SEEN_NOTHING = new Object();
private static final Object SEEN_ELEMENT = new Object();
private static final Object SEEN_DATA = new Object();
/**
* Constant representing empty attributes.
*/
private final Attributes EMPTY_ATTS = new AttributesImpl();
/**
* The prefixes table.
*/
private volatile Map prefixTable;
/**
* The forced declarations table.
*/
private volatile Map forcedDeclTable;
/**
* The document declarations table.
*/
private volatile Map doneDeclTable;
/**
* The element level.
*/
private volatile int elementLevel = 0;
/**
* The namespace support.
*/
private volatile NamespaceSupport nsSupport;
/**
* The prefix counter.
*/
private volatile int prefixCounter = 0;
/**
* The underlying writer.
*/
private volatile Writer output;
private volatile Object state = SEEN_NOTHING;
private volatile Stack stateStack = new Stack();
private volatile boolean dataFormat = false;
private volatile int indentStep = 0;
private volatile int depth = 0;
/**
* Create a new XML writer.
*
* Write to standard output.
*
*/
public XmlWriter() {
init(null);
}
/**
* Constructor.
*
* @param out
* The underlying output stream.
*/
public XmlWriter(OutputStream out) {
this(new OutputStreamWriter(out));
}
/**
* Constructor.
*
* @param out
* The underlying output stream.
*/
public XmlWriter(OutputStream out, Charset cs) {
this(new OutputStreamWriter(out, cs));
}
/**
* Constructor.
*
* @param out
* The underlying output stream.
*/
public XmlWriter(OutputStream out, CharsetEncoder enc) {
this(new OutputStreamWriter(out, enc));
}
/**
* Constructor.
*
* @param out
* The underlying output stream.
*/
public XmlWriter(OutputStream out, String charsetName)
throws UnsupportedEncodingException {
this(new OutputStreamWriter(out, charsetName));
}
/**
* Create a new XML writer.
*
* Write to the writer provided.
*
*
* @param writer
* The output destination, or null to use standard output.
*/
public XmlWriter(Writer writer) {
init(writer);
}
/**
* Create a new XML writer.
*
* Use the specified XML reader as the parent.
*
*
* @param xmlreader
* The parent in the filter chain, or null for no parent.
*/
public XmlWriter(XMLReader xmlreader) {
super(xmlreader);
init(null);
}
/**
* Create a new XML writer.
*
* Use the specified XML reader as the parent, and write to the specified
* writer.
*
*
* @param xmlreader
* The parent in the filter chain, or null for no parent.
* @param writer
* The output destination, or null to use standard output.
*/
public XmlWriter(XMLReader xmlreader, Writer writer) {
super(xmlreader);
init(writer);
}
/**
* Write character data. Pass the event on down the filter chain for further
* processing.
*
* @param ch
* The array of characters to write.
* @param start
* The starting position in the array.
* @param len
* The number of characters to write.
* @exception org.xml.sax.SAXException
* If there is an error writing the characters, or if a
* restlet further down the filter chain raises an exception.
* @see org.xml.sax.ContentHandler#characters
*/
private void characters(boolean dataFormat, char ch[], int start, int len)
throws SAXException {
if (dataFormat) {
this.state = SEEN_DATA;
}
writeEsc(ch, start, len, false);
super.characters(ch, start, len);
}
// //////////////////////////////////////////////////////////////////
// Public methods.
// //////////////////////////////////////////////////////////////////
/**
* Write a string of character data, with XML escaping.
*
* This is a convenience method that takes an XML String, converts it to a
* character array, then invokes {@link #characters(char[], int, int)}.
*
*
* @param data
* The character data.
* @exception org.xml.sax.SAXException
* If there is an error writing the string, or if a restlet
* further down the filter chain raises an exception.
* @see #characters(char[], int, int)
*/
private void characters(boolean dataFormat, String data)
throws SAXException {
final char ch[] = data.toCharArray();
characters(dataFormat, ch, 0, ch.length);
}
/**
* Write character data. Pass the event on down the filter chain for further
* processing.
*
* @param ch
* The array of characters to write.
* @param start
* The starting position in the array.
* @param len
* The number of characters to write.
* @exception org.xml.sax.SAXException
* If there is an error writing the characters, or if a
* restlet further down the filter chain raises an exception.
* @see org.xml.sax.ContentHandler#characters
*/
@Override
public void characters(char ch[], int start, int len) throws SAXException {
characters(isDataFormat(), ch, start, len);
}
/**
* Write a string of character data, with XML escaping.
*
* This is a convenience method that takes an XML String, converts it to a
* character array, then invokes {@link #characters(char[], int, int)}.
*
*
* @param data
* The character data.
* @exception org.xml.sax.SAXException
* If there is an error writing the string, or if a restlet
* further down the filter chain raises an exception.
* @see #characters(char[], int, int)
*/
public void characters(String data) throws SAXException {
characters(false, data);
}
/**
* Write an element with character data content but no attributes or
* Namespace URI.
*
*
* This is a convenience method to write a complete element with character
* data content, including the start tag and end tag. The method provides an
* empty string for the Namespace URI, and empty string for the qualified
* name, and an empty attribute list.
*
*
*
* This method invokes
* {@link #startElement(String, String, String, Attributes)}, followed by
* {@link #characters(String)}, followed by
* {@link #endElement(String, String, String)}.
*
*
* @param localName
* The element's local name.
* @param content
* The character data content.
* @exception org.xml.sax.SAXException
* If there is an error writing the empty tag, or if a
* restlet further down the filter chain raises an exception.
* @see #startElement(String, String, String, Attributes)
* @see #characters(String)
* @see #endElement(String, String, String)
*/
public void dataElement(String localName, String content)
throws SAXException {
dataElement("", localName, "", this.EMPTY_ATTS, content);
}
/**
* Write an element with character data content but no attributes.
*
*
* This is a convenience method to write a complete element with character
* data content, including the start tag and end tag. This method provides
* an empty string for the qname and an empty attribute list.
*
*
*
* This method invokes
* {@link #startElement(String, String, String, Attributes)}, followed by
* {@link #characters(String)}, followed by
* {@link #endElement(String, String, String)}.
*
*
* @param uri
* The element's Namespace URI.
* @param localName
* The element's local name.
* @param content
* The character data content.
* @exception org.xml.sax.SAXException
* If there is an error writing the empty tag, or if a
* restlet further down the filter chain raises an exception.
* @see #startElement(String, String, String, Attributes)
* @see #characters(String)
* @see #endElement(String, String, String)
*/
public void dataElement(String uri, String localName, String content)
throws SAXException {
dataElement(uri, localName, "", this.EMPTY_ATTS, content);
}
/**
* Write an element with character data content.
*
*
* This is a convenience method to write a complete element with character
* data content, including the start tag and end tag.
*
*
*
* This method invokes
* {@link #startElement(String, String, String, Attributes)}, followed by
* {@link #characters(String)}, followed by
* {@link #endElement(String, String, String)}.
*
*
* @param uri
* The element's Namespace URI.
* @param localName
* The element's local name.
* @param qName
* The element's default qualified name.
* @param atts
* The element's attributes.
* @param content
* The character data content.
* @exception org.xml.sax.SAXException
* If there is an error writing the empty tag, or if a
* restlet further down the filter chain raises an exception.
* @see #startElement(String, String, String, Attributes)
* @see #characters(String)
* @see #endElement(String, String, String)
*/
public void dataElement(String uri, String localName, String qName,
Attributes atts, String content) throws SAXException {
startElement(uri, localName, qName, atts);
characters(content);
endElement(uri, localName, qName);
}
/**
* Print indentation for the current level.
*
* @exception org.xml.sax.SAXException
* If there is an error writing the indentation characters,
* or if a filter further down the chain raises an exception.
*/
private void doIndent() throws SAXException {
if ((this.indentStep > 0) && (this.depth > 0)) {
final int n = this.indentStep * this.depth;
final char ch[] = new char[n];
for (int i = 0; i < n; i++) {
ch[i] = ' ';
}
characters(ch, 0, n);
}
}
/**
* Determine the prefix for an element or attribute name. TODO: this method
* probably needs some cleanup.
*
* @param uri
* The Namespace URI.
* @param qName
* The qualified name (optional); this will be used to indicate
* the preferred prefix if none is currently bound.
* @param isElement
* true if this is an element name, false if it is an attribute
* name (which cannot use the default Namespace).
*/
private String doPrefix(String uri, String qName, boolean isElement) {
final String defaultNS = this.nsSupport.getURI("");
if ("".equals(uri) || uri == null) {
if (isElement && (defaultNS != null)) {
this.nsSupport.declarePrefix("", "");
}
return null;
}
String prefix;
if (isElement && (defaultNS != null) && uri.equals(defaultNS)) {
prefix = "";
} else {
prefix = this.nsSupport.getPrefix(uri);
}
if (prefix != null) {
return prefix;
}
prefix = this.doneDeclTable.get(uri);
if ((prefix != null)
&& (((!isElement || (defaultNS != null)) && "".equals(prefix)) || (this.nsSupport
.getURI(prefix) != null))) {
prefix = null;
}
if (prefix == null) {
prefix = this.prefixTable.get(uri);
if ((prefix != null)
&& (((!isElement || (defaultNS != null)) && ""
.equals(prefix)) || (this.nsSupport.getURI(prefix) != null))) {
prefix = null;
}
}
if ((prefix == null) && (qName != null) && !"".equals(qName)) {
final int i = qName.indexOf(':');
if (i == -1) {
if (isElement && (defaultNS == null)) {
prefix = "";
}
} else {
prefix = qName.substring(0, i);
}
}
for (; (prefix == null) || (this.nsSupport.getURI(prefix) != null); prefix = "__NS"
+ ++this.prefixCounter) {
// Do nothing
}
this.nsSupport.declarePrefix(prefix, uri);
this.doneDeclTable.put(uri, prefix);
return prefix;
}
// //////////////////////////////////////////////////////////////////
// Methods from org.xml.sax.ContentHandler.
// //////////////////////////////////////////////////////////////////
/**
* Add an empty element without a Namespace URI, qname or attributes.
*
*
* This method will supply an empty string for the qname, and empty string
* for the Namespace URI, and an empty attribute list. It invokes
* {@link #emptyElement(String, String, String, Attributes)} directly.
*
*
* @param localName
* The element's local name.
* @exception org.xml.sax.SAXException
* If there is an error writing the empty tag, or if a
* restlet further down the filter chain raises an exception.
* @see #emptyElement(String, String, String, Attributes)
*/
public void emptyElement(String localName) throws SAXException {
emptyElement("", localName, "", this.EMPTY_ATTS);
}
/**
* Add an empty element without a qname or attributes.
*
*
* This method will supply an empty string for the qname and an empty
* attribute list. It invokes
* {@link #emptyElement(String, String, String, Attributes)} directly.
*
*
* @param uri
* The element's Namespace URI.
* @param localName
* The element's local name.
* @exception org.xml.sax.SAXException
* If there is an error writing the empty tag, or if a
* restlet further down the filter chain raises an exception.
* @see #emptyElement(String, String, String, Attributes)
*/
public void emptyElement(String uri, String localName) throws SAXException {
emptyElement(uri, localName, "", this.EMPTY_ATTS);
}
/**
* Write an empty element. This method writes an empty element tag rather
* than a start tag followed by an end tag. Both a {@link #startElement
* startElement} and an {@link #endElement endElement} event will be passed
* on down the filter chain.
*
* @param uri
* The element's Namespace URI, or the empty string if the
* element has no Namespace or if Namespace processing is not
* being performed.
* @param localName
* The element's local name (without prefix). This parameter must
* be provided.
* @param qName
* The element's qualified name (with prefix), or the empty
* string if none is available. This parameter is strictly
* advisory: the writer may or may not use the prefix attached.
* @param atts
* The element's attribute list.
* @exception org.xml.sax.SAXException
* If there is an error writing the empty tag, or if a
* restlet further down the filter chain raises an exception.
* @see #startElement
* @see #endElement
*/
public void emptyElement(String uri, String localName, String qName,
Attributes atts) throws SAXException {
if (isDataFormat()) {
this.state = SEEN_ELEMENT;
if (this.depth > 0) {
characters(false, "\n");
}
doIndent();
}
this.nsSupport.pushContext();
write('<');
writeName(uri, localName, qName, true);
writeAttributes(atts);
if (this.elementLevel == 1) {
forceNSDecls();
}
writeNSDecls();
write("/>");
super.startElement(uri, localName, qName, atts);
super.endElement(uri, localName, qName);
}
/**
* Write a newline at the end of the document. Pass the event on down the
* filter chain for further processing.
*
* @exception org.xml.sax.SAXException
* If there is an error writing the newline, or if a restlet
* further down the filter chain raises an exception.
* @see org.xml.sax.ContentHandler#endDocument
*/
@Override
public void endDocument() throws SAXException {
write('\n');
super.endDocument();
try {
flush();
} catch (IOException e) {
throw new SAXException(e);
}
}
/**
* End an element without a Namespace URI or qname.
*
*
* This method will supply an empty string for the qName and an empty string
* for the Namespace URI. It invokes
* {@link #endElement(String, String, String)} directly.
*
*
* @param localName
* The element's local name.
* @exception org.xml.sax.SAXException
* If there is an error writing the end tag, or if a restlet
* further down the filter chain raises an exception.
* @see #endElement(String, String, String)
*/
public void endElement(String localName) throws SAXException {
endElement("", localName, "");
}
/**
* End an element without a qname.
*
*
* This method will supply an empty string for the qName. It invokes
* {@link #endElement(String, String, String)} directly.
*
*
* @param uri
* The element's Namespace URI.
* @param localName
* The element's local name.
* @exception org.xml.sax.SAXException
* If there is an error writing the end tag, or if a restlet
* further down the filter chain raises an exception.
* @see #endElement(String, String, String)
*/
public void endElement(String uri, String localName) throws SAXException {
endElement(uri, localName, "");
}
/**
* Write an end tag. Pass the event on down the filter chain for further
* processing.
*
* @param uri
* The Namespace URI, or the empty string if none is available.
* @param localName
* The element's local (unprefixed) name (required).
* @param qName
* The element's qualified (prefixed) name, or the empty string
* is none is available. This method will use the qName as a
* template for generating a prefix if necessary, but it is not
* guaranteed to use the same qName.
* @exception org.xml.sax.SAXException
* If there is an error writing the end tag, or if a restlet
* further down the filter chain raises an exception.
* @see org.xml.sax.ContentHandler#endElement
*/
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
if (isDataFormat()) {
this.depth--;
if (this.state == SEEN_ELEMENT) {
characters(false, "\n");
doIndent();
}
}
write("");
writeName(uri, localName, qName, true);
write('>');
if (this.elementLevel == 1) {
write('\n');
}
super.endElement(uri, localName, qName);
this.nsSupport.popContext();
this.elementLevel--;
if (isDataFormat()) {
this.state = this.stateStack.pop();
}
}
/**
* Flush the output.
*
* This method flushes the output stream. It is especially useful when you
* need to make certain that the entire document has been written to output
* but do not want to close the output stream.
*
*
* This method is invoked automatically by the {@link #endDocument
* endDocument} method after writing a document.
*
*
* @see #reset
*/
public void flush() throws IOException {
this.output.flush();
}
// //////////////////////////////////////////////////////////////////
// Additional markup.
// //////////////////////////////////////////////////////////////////
/**
* Force a Namespace to be declared on the root element.
*
* By default, the XMLWriter will declare only the Namespaces needed for an
* element; as a result, a Namespace may be declared many places in a
* document if it is not used on the root element.
*
*
* This method forces a Namespace to be declared on the root element even if
* it is not used there, and reduces the number of xmlns attributes in the
* document.
*
*
* @param uri
* The Namespace URI to declare.
* @see #forceNSDecl(java.lang.String,java.lang.String)
* @see #setPrefix
*/
public void forceNSDecl(String uri) {
this.forcedDeclTable.put(uri, Boolean.TRUE);
}
// //////////////////////////////////////////////////////////////////
// Convenience methods.
// //////////////////////////////////////////////////////////////////
/**
* Force a Namespace declaration with a preferred prefix.
*
* This is a convenience method that invokes {@link #setPrefix setPrefix}
* then {@link #forceNSDecl(java.lang.String) forceNSDecl}.
*
*
* @param uri
* The Namespace URI to declare on the root element.
* @param prefix
* The preferred prefix for the Namespace, or "" for the default
* Namespace.
* @see #setPrefix
* @see #forceNSDecl(java.lang.String)
*/
public void forceNSDecl(String uri, String prefix) {
setPrefix(uri, prefix);
forceNSDecl(uri);
}
/**
* Force all Namespaces to be declared. This method is used on the root
* element to ensure that the predeclared Namespaces all appear.
*/
private void forceNSDecls() {
for (final String prefix : this.forcedDeclTable.keySet()) {
doPrefix(prefix, null, true);
}
}
/**
* Return the current indent step.
*
* Return the current indent step: each start tag will be indented by this
* number of spaces times the number of ancestors that the element has.
*
*
* @return The number of spaces in each indentation step, or 0 or less for
* no indentation.
*/
public int getIndentStep() {
return this.indentStep;
}
/**
* Get the current or preferred prefix for a Namespace URI.
*
* @param uri
* The Namespace URI.
* @return The preferred prefix, or "" for the default Namespace.
* @see #setPrefix
*/
public String getPrefix(String uri) {
return this.prefixTable.get(uri);
}
/**
* Returns the underlying writer.
*
* @return The underlying writer.
*/
public Writer getWriter() {
return this.output;
}
/**
* Write ignorable whitespace. Pass the event on down the filter chain for
* further processing.
*
* @param ch
* The array of characters to write.
* @param start
* The starting position in the array.
* @param length
* The number of characters to write.
* @exception org.xml.sax.SAXException
* If there is an error writing the whitespace, or if a
* restlet further down the filter chain raises an exception.
* @see org.xml.sax.ContentHandler#ignorableWhitespace
*/
@Override
public void ignorableWhitespace(char ch[], int start, int length)
throws SAXException {
writeEsc(ch, start, length, false);
super.ignorableWhitespace(ch, start, length);
}
/**
* Internal initialization method.
*
*
* All of the public constructors invoke this method.
*
* @param writer
* The output destination, or null to use standard output.
*/
private void init(Writer writer) {
setOutput(writer);
this.nsSupport = new NamespaceSupport();
this.prefixTable = new TreeMap();
this.forcedDeclTable = new TreeMap();
this.doneDeclTable = new TreeMap();
}
public boolean isDataFormat() {
return this.dataFormat;
}
/**
* Write a processing instruction. Pass the event on down the filter chain
* for further processing.
*
* @param target
* The PI target.
* @param data
* The PI data.
* @exception org.xml.sax.SAXException
* If there is an error writing the PI, or if a restlet
* further down the filter chain raises an exception.
* @see org.xml.sax.ContentHandler#processingInstruction
*/
@Override
public void processingInstruction(String target, String data)
throws SAXException {
write("");
write(target);
write(' ');
write(data);
write("?>");
if (this.elementLevel < 1) {
write('\n');
}
super.processingInstruction(target, data);
}
/**
* Reset the writer.
*
*
* This method is especially useful if the writer throws an exception before
* it is finished, and you want to reuse the writer for a new document. It
* is usually a good idea to invoke {@link #flush flush} before resetting
* the writer, to make sure that no output is lost.
*
*
*
* This method is invoked automatically by the {@link #startDocument
* startDocument} method before writing a new document.
*
*
*
* Note: this method will not clear the prefix or
* URI information in the writer or the selected output writer.
*
*
* @see #flush
*/
public void reset() {
if (isDataFormat()) {
this.depth = 0;
this.state = SEEN_NOTHING;
this.stateStack = new Stack();
}
this.elementLevel = 0;
this.prefixCounter = 0;
this.nsSupport.reset();
}
public void setDataFormat(boolean dataFormat) {
this.dataFormat = dataFormat;
}
// //////////////////////////////////////////////////////////////////
// Internal methods.
// //////////////////////////////////////////////////////////////////
/**
* Set the current indent step.
*
* @param indentStep
* The new indent step (0 or less for no indentation).
*/
public void setIndentStep(int indentStep) {
this.indentStep = indentStep;
}
/**
* Set a new output destination for the document.
*
* @param writer
* The output destination, or null to use standard output.
* @see #flush
*/
public void setOutput(Writer writer) {
if (writer == null) {
this.output = new OutputStreamWriter(System.out);
} else {
this.output = writer;
}
}
/**
* Specify a preferred prefix for a Namespace URI.
*
* Note that this method does not actually force the Namespace to be
* declared; to do that, use the {@link #forceNSDecl(java.lang.String)
* forceNSDecl} method as well.
*
*
* @param uri
* The Namespace URI.
* @param prefix
* The preferred prefix, or "" to select the default Namespace.
* @see #getPrefix
* @see #forceNSDecl(java.lang.String)
* @see #forceNSDecl(java.lang.String,java.lang.String)
*/
public void setPrefix(String uri, String prefix) {
this.prefixTable.put(uri, prefix);
}
/**
* Write the XML declaration at the beginning of the document. Pass the
* event on down the filter chain for further processing.
*
* @exception org.xml.sax.SAXException
* If there is an error writing the XML declaration, or if a
* restlet further down the filter chain raises an exception.
* @see org.xml.sax.ContentHandler#startDocument
*/
@Override
public void startDocument() throws SAXException {
reset();
write("\n\n");
super.startDocument();
}
/**
* Start a new element without a qname, attributes or a Namespace URI.
*
*
* This method will provide an empty string for the Namespace URI, and empty
* string for the qualified name, and a default empty attribute list. It
* invokes #startElement(String, String, String, Attributes)} directly.
*
*
* @param localName
* The element's local name.
* @exception org.xml.sax.SAXException
* If there is an error writing the start tag, or if a
* restlet further down the filter chain raises an exception.
* @see #startElement(String, String, String, Attributes)
*/
public void startElement(String localName) throws SAXException {
startElement("", localName, "", this.EMPTY_ATTS);
}
/**
* Start a new element without a qname or attributes.
*
*
* This method will provide a default empty attribute list and an empty
* string for the qualified name. It invokes
* {@link #startElement(String, String, String, Attributes)} directly.
*
*
* @param uri
* The element's Namespace URI.
* @param localName
* The element's local name.
* @exception org.xml.sax.SAXException
* If there is an error writing the start tag, or if a
* restlet further down the filter chain raises an exception.
* @see #startElement(String, String, String, Attributes)
*/
public void startElement(String uri, String localName) throws SAXException {
startElement(uri, localName, "", this.EMPTY_ATTS);
}
/**
* Write a start tag. Pass the event on down the filter chain for further
* processing.
*
* @param uri
* The Namespace URI, or the empty string if none is available.
* @param localName
* The element's local (unprefixed) name (required).
* @param qName
* The element's qualified (prefixed) name, or the empty string
* is none is available. This method will use the qName as a
* template for generating a prefix if necessary, but it is not
* guaranteed to use the same qName.
* @param atts
* The element's attribute list (must not be null).
* @exception org.xml.sax.SAXException
* If there is an error writing the start tag, or if a
* restlet further down the filter chain raises an exception.
* @see org.xml.sax.ContentHandler#startElement
*/
@Override
public void startElement(String uri, String localName, String qName,
Attributes atts) throws SAXException {
if (isDataFormat()) {
this.stateStack.push(SEEN_ELEMENT);
this.state = SEEN_NOTHING;
if (this.depth > 0) {
characters("\n");
}
doIndent();
}
this.elementLevel++;
this.nsSupport.pushContext();
write('<');
writeName(uri, localName, qName, true);
writeAttributes(atts);
if (this.elementLevel == 1) {
forceNSDecls();
}
writeNSDecls();
write('>');
super.startElement(uri, localName, qName, atts);
if (isDataFormat()) {
this.depth++;
}
}
/**
* Write a raw character.
*
* @param c
* The character to write.
* @exception org.xml.sax.SAXException
* If there is an error writing the character, this method
* will throw an IOException wrapped in a SAXException.
*/
private void write(char c) throws SAXException {
try {
this.output.write(c);
} catch (IOException e) {
throw new SAXException(e);
}
}
/**
* Write a raw string.
*
* @param s
* @exception org.xml.sax.SAXException
* If there is an error writing the string, this method will
* throw an IOException wrapped in a SAXException
*/
private void write(String s) throws SAXException {
try {
this.output.write(s);
} catch (IOException e) {
throw new SAXException(e);
}
}
/**
* Write out an attribute list, escaping values. The names will have
* prefixes added to them.
*
* @param atts
* The attribute list to write.
* @exception org.xml.SAXException
* If there is an error writing the attribute list, this
* method will throw an IOException wrapped in a
* SAXException.
*/
private void writeAttributes(Attributes atts) throws SAXException {
final int len = atts.getLength();
for (int i = 0; i < len; i++) {
final char ch[] = atts.getValue(i).toCharArray();
write(' ');
writeName(atts.getURI(i), atts.getLocalName(i), atts.getQName(i),
false);
write("=\"");
writeEsc(ch, 0, ch.length, true);
write('"');
}
}
/**
* Write an array of data characters with escaping.
*
* @param ch
* The array of characters.
* @param start
* The starting position.
* @param length
* The number of characters to use.
* @param isAttVal
* true if this is an attribute value literal.
* @exception org.xml.SAXException
* If there is an error writing the characters, this method
* will throw an IOException wrapped in a SAXException.
*/
private void writeEsc(char ch[], int start, int length, boolean isAttVal)
throws SAXException {
for (int i = start; i < start + length; i++) {
switch (ch[i]) {
case '&':
write("&");
break;
case '<':
write("<");
break;
case '>':
write(">");
break;
case '\"':
if (isAttVal) {
write(""");
} else {
write('\"');
}
break;
default:
if (ch[i] > '\u007f') {
write("");
write(Integer.toString(ch[i]));
write(';');
} else {
write(ch[i]);
}
}
}
}
/**
* Write an element or attribute name.
*
* @param uri
* The Namespace URI.
* @param localName
* The local name.
* @param qName
* The prefixed name, if available, or the empty string.
* @param isElement
* true if this is an element name, false if it is an attribute
* name.
* @exception org.xml.sax.SAXException
* This method will throw an IOException wrapped in a
* SAXException if there is an error writing the name.
*/
private void writeName(String uri, String localName, String qName,
boolean isElement) throws SAXException {
final String prefix = doPrefix(uri, qName, isElement);
if ((prefix != null) && !"".equals(prefix)) {
write(prefix);
write(':');
}
write(localName);
}
/**
* Write out the list of Namespace declarations.
*
* @exception org.xml.sax.SAXException
* This method will throw an IOException wrapped in a
* SAXException if there is an error writing the Namespace
* declarations.
*/
@SuppressWarnings("unchecked")
private void writeNSDecls() throws SAXException {
final Enumeration prefixes = this.nsSupport
.getDeclaredPrefixes();
while (prefixes.hasMoreElements()) {
final String prefix = prefixes.nextElement();
String uri = this.nsSupport.getURI(prefix);
if (uri == null) {
uri = "";
}
final char ch[] = uri.toCharArray();
write(' ');
if ("".equals(prefix)) {
write("xmlns=\"");
} else {
write("xmlns:");
write(prefix);
write("=\"");
}
writeEsc(ch, 0, ch.length, true);
write('\"');
}
}
}