com.sun.xml.bind.marshaller.XMLWriter Maven / Gradle / Ivy
Show all versions of jaxb-impl Show documentation
// @@3RD PARTY CODE@@
// XMLWriter.java - serialize an XML document.
// Written by David Megginson, [email protected]
// NO WARRANTY! This class is in the public domain.
// Id: XMLWriter.java,v 1.5 2000/09/17 01:08:16 david Exp
package com.sun.xml.bind.marshaller;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.XMLFilterImpl;
/**
* Filter to write an XML document from a SAX event stream.
*
* 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:
*
* {@code
*
*
* Hello, world!
* }
*
* 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
*
* {@code
* - 1
- 3
- 3
* }
*
* You need to invoke one of the characters methods
* explicitly to add newlines or indentation. Alternatively, you
* can use {@link DataWriter}, which
* is derived from this class -- it is optimized for writing
* purely data-oriented (or field-oriented) XML, and does automatic
* linebreaks and indentation (but does not support mixed content
* properly).
*
*
* 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:
*
* {@code
*
*
* <_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
*
*
* 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).
*
* The resulting document will look like this:
*
* {@code
*
*
*
* }
*
* 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:
*
* {@code
*
*
*
* }
*
* 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:
*
* {@code
*
*
*
*
* A Dark Night
* Jane Smith
* 2000-09-09
*
*
* }
*
* 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:
*
* {@code
*
*
*
*
* A Dark Night
* Jane Smith
* 2000-09-09
*
*
* }
*
* This approach is also useful for declaring Namespace prefixes
* that be used by qualified names appearing in attribute values or
* character data.
*
* @author David Megginson, [email protected]
* @version 0.2
* @since JAXB 1.0
* @see org.xml.sax.XMLFilter
* @see org.xml.sax.ContentHandler
*/
public class XMLWriter extends XMLFilterImpl
{
////////////////////////////////////////////////////////////////////
// Constructors.
////////////////////////////////////////////////////////////////////
/**
* Create a new XML writer.
*
* Write to the writer provided.
*
* @param writer
* The output destination, or null to use standard output.
* @param encoding
* If non-null string is specified, it is written as a part
* of the XML declaration.
*/
public XMLWriter (Writer writer, String encoding, CharacterEscapeHandler _escapeHandler )
{
init(writer,encoding);
this.escapeHandler = _escapeHandler;
}
public XMLWriter (Writer writer, String encoding ) {
this( writer, encoding, DumbEscapeHandler.theInstance );
}
/**
* 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,String encoding)
{
setOutput(writer,encoding);
}
////////////////////////////////////////////////////////////////////
// Public methods.
////////////////////////////////////////////////////////////////////
/**
* 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 ()
{
elementLevel = 0;
startTagIsClosed = true;
}
/**
* 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
{
output.flush();
}
/**
* 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,String _encoding)
{
if (writer == null) {
output = new OutputStreamWriter(System.out);
} else {
output = writer;
}
encoding = _encoding;
}
/**
* Set whether the writer should print out the XML declaration
* ({@code }).
*
* This option is set to true by default.
*/
public void setXmlDecl( boolean _writeXmlDecl ) {
this.writeXmlDecl = _writeXmlDecl;
}
/**
* Sets the header string.
*
* This string will be written right after the xml declaration
* without any escaping. Useful for generating a boiler-plate
* DOCTYPE decl, PIs, and comments.
*
* @param _header
* passing null will work as if the empty string is passed.
*/
public void setHeader( String _header ) {
this.header = _header;
}
private final HashMap locallyDeclaredPrefix = new HashMap();
public void startPrefixMapping( String prefix, String uri ) throws SAXException {
locallyDeclaredPrefix.put(prefix,uri);
}
////////////////////////////////////////////////////////////////////
// Methods from org.xml.sax.ContentHandler.
////////////////////////////////////////////////////////////////////
/**
* 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 handler further down
* the filter chain raises an exception.
* @see org.xml.sax.ContentHandler#startDocument()
*/
public void startDocument ()
throws SAXException
{
try {
reset();
if(writeXmlDecl) {
String e="";
if(encoding!=null)
e = " encoding=\""+encoding+'\"';
writeXmlDecl("");
}
if(header!=null)
write(header);
super.startDocument();
} catch( IOException e ) {
throw new SAXException(e);
}
}
protected void writeXmlDecl(String decl) throws IOException {
write(decl);
}
/**
* 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 handler further down
* the filter chain raises an exception.
* @see org.xml.sax.ContentHandler#endDocument()
*/
public void endDocument ()
throws SAXException
{
try {
super.endDocument();
flush();
} catch( IOException e ) {
throw new SAXException(e);
}
}
/**
* 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 handler further down
* the filter chain raises an exception.
* @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
*/
public void startElement (String uri, String localName,
String qName, Attributes atts)
throws SAXException
{
try {
if (!startTagIsClosed) {
write(">");
}
elementLevel++;
// nsSupport.pushContext();
write('<');
write(qName);
writeAttributes(atts);
// declare namespaces specified by the startPrefixMapping methods
if(!locallyDeclaredPrefix.isEmpty()) {
for (Map.Entry e : locallyDeclaredPrefix.entrySet()) {
String p = e.getKey();
String u = e.getValue();
if (u == null) {
u = "";
}
write(' ');
if ("".equals(p)) {
write("xmlns=\"");
} else {
write("xmlns:");
write(p);
write("=\"");
}
char ch[] = u.toCharArray();
writeEsc(ch, 0, ch.length, true);
write('\"');
}
locallyDeclaredPrefix.clear(); // clear the contents
}
// if (elementLevel == 1) {
// forceNSDecls();
// }
// writeNSDecls();
super.startElement(uri, localName, qName, atts);
startTagIsClosed = false;
} catch( IOException e ) {
throw new SAXException(e);
}
}
/**
* 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 handler further down
* the filter chain raises an exception.
* @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
*/
public void endElement (String uri, String localName, String qName)
throws SAXException
{
try {
if (startTagIsClosed) {
write("");
write(qName);
write('>');
} else {
write("/>");
startTagIsClosed = true;
}
super.endElement(uri, localName, qName);
// nsSupport.popContext();
elementLevel--;
} catch( IOException e ) {
throw new SAXException(e);
}
}
/**
* 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 handler further down
* the filter chain raises an exception.
* @see org.xml.sax.ContentHandler#characters(char[], int, int)
*/
public void characters (char ch[], int start, int len)
throws SAXException
{
try {
if (!startTagIsClosed) {
write('>');
startTagIsClosed = true;
}
writeEsc(ch, start, len, false);
super.characters(ch, start, len);
} catch( IOException e ) {
throw new SAXException(e);
}
}
/**
* 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 handler further down
* the filter chain raises an exception.
* @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
*/
public void ignorableWhitespace (char ch[], int start, int length)
throws SAXException
{
try {
writeEsc(ch, start, length, false);
super.ignorableWhitespace(ch, start, length);
} catch( IOException e ) {
throw new SAXException(e);
}
}
/**
* 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 handler further down
* the filter chain raises an exception.
* @see org.xml.sax.ContentHandler#processingInstruction(java.lang.String, java.lang.String)
*/
public void processingInstruction (String target, String data)
throws SAXException
{
try {
if (!startTagIsClosed) {
write('>');
startTagIsClosed = true;
}
write("");
write(target);
write(' ');
write(data);
write("?>");
if (elementLevel < 1) {
write('\n');
}
super.processingInstruction(target, data);
} catch( IOException e ) {
throw new SAXException(e);
}
}
////////////////////////////////////////////////////////////////////
// Convenience methods.
////////////////////////////////////////////////////////////////////
/**
* 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 handler 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, "", EMPTY_ATTS);
}
/**
* 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 handler further down
* the filter chain raises an exception.
* @see #startElement(String, String, String, Attributes)
*/
public void startElement (String localName)
throws SAXException
{
startElement("", localName, "", EMPTY_ATTS);
}
/**
* 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 handler 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, "");
}
/**
* 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 handler further down
* the filter chain raises an exception.
* @see #endElement(String, String, String)
*/
public void endElement (String localName)
throws SAXException
{
endElement("", localName, "");
}
/**
* 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 handler 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);
}
/**
* 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 handler 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, "", EMPTY_ATTS, content);
}
/**
* 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 handler 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, "", EMPTY_ATTS, content);
}
/**
* 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 handler further down
* the filter chain raises an exception.
* @see #characters(char[], int, int)
*/
public void characters (String data) throws SAXException {
try {
if (!startTagIsClosed) {
write('>');
startTagIsClosed = true;
}
char ch[] = data.toCharArray();
characters(ch, 0, ch.length);
} catch( IOException e ) {
throw new SAXException(e);
}
}
////////////////////////////////////////////////////////////////////
// Internal methods.
////////////////////////////////////////////////////////////////////
/**
* Write a raw character.
*
* @param c The character to write.
*/
protected final void write (char c) throws IOException {
output.write(c);
}
/**
* Write a raw string.
*/
protected final void write(String s) throws IOException {
output.write(s);
}
/**
* Write out an attribute list, escaping values.
*
* The names will have prefixes added to them.
*
* @param atts The attribute list to write.
*/
private void writeAttributes (Attributes atts) throws IOException {
int len = atts.getLength();
for (int i = 0; i < len; i++) {
char ch[] = atts.getValue(i).toCharArray();
write(' ');
write(atts.getQName(i));
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.
*/
private void writeEsc (char ch[], int start,
int length, boolean isAttVal)
throws IOException
{
escapeHandler.escape(ch, start, length, isAttVal, output);
}
////////////////////////////////////////////////////////////////////
// Constants.
////////////////////////////////////////////////////////////////////
private final Attributes EMPTY_ATTS = new AttributesImpl();
////////////////////////////////////////////////////////////////////
// Internal state.
////////////////////////////////////////////////////////////////////
private int elementLevel = 0;
private Writer output;
private String encoding;
private boolean writeXmlDecl = true;
/**
* This string will be written right after the xml declaration
* without any escaping. Useful for generating a boiler-plate DOCTYPE decl
* , PIs, and comments.
*/
private String header=null;
private final CharacterEscapeHandler escapeHandler;
private boolean startTagIsClosed = true;
}
// end of XMLWriter.java