All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.xml.txw2.output.XMLWriter Maven / Gradle / Ivy


// @@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.txw2.output;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.XMLFilterImpl;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;


/**
 * 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
 * 133
 * }
* *

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:

* *
    *
  1. the qualified name
  2. *
  3. the {@link #startPrefixMapping(String, String)} method.
  4. *
* *

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:

* *
{@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 implements LexicalHandler { //////////////////////////////////////////////////////////////////// // 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 _commit 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; } public void setEncoding(String encoding) { this.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+"\""; write("\n"); } if(header!=null) write(header); super.startDocument(); } catch( IOException e ) { throw new SAXException(e); } } /** * 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 { if (!startTagIsClosed) { write("/>"); startTagIsClosed = true; } write('\n'); super.endDocument(); try { flush(); } catch (IOException e) { throw new SAXException(e); } } 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('<'); writeName(uri, localName, qName, true); writeAttributes(atts); // declare namespaces specified by the startPrefixMapping methods if(!locallyDeclaredPrefix.isEmpty()) { Iterator itr = locallyDeclaredPrefix.entrySet().iterator(); while(itr.hasNext()) { Map.Entry e = (Map.Entry)itr.next(); String p = (String)e.getKey(); String u = (String)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("'); } else { write("/>"); startTagIsClosed = true; } if (elementLevel == 1) { write('\n'); } 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; } if(inCDATA) output.write(ch,start,len); else 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(""); 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); } } public void startDTD(String name, String publicId, String systemId) throws SAXException { } public void endDTD() throws SAXException { } public void startEntity(String name) throws SAXException { } public void endEntity(String name) throws SAXException { } public void startCDATA() throws SAXException { try { if (!startTagIsClosed) { write('>'); startTagIsClosed = true; } write(""); } catch (IOException e) { throw new SAXException(e); } } public void comment(char ch[], int start, int length) throws SAXException { try { output.write(""); } catch (IOException e) { throw new SAXException(e); } } //////////////////////////////////////////////////////////////////// // Internal methods. //////////////////////////////////////////////////////////////////// /** * Write a raw character. * * @param c The character to write. */ private void write (char c) throws IOException { output.write(c); } /** * Write a raw string. */ private 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. * @exception 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 IOException, SAXException { int len = atts.getLength(); for (int i = 0; i < len; i++) { 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 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, IOException { escapeHandler.escape(ch, start, length, isAttVal, output); } /** * 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. */ private void writeName (String uri, String localName, String qName, boolean isElement) throws IOException { write(qName); } //////////////////////////////////////////////////////////////////// // Constants. //////////////////////////////////////////////////////////////////// private final Attributes EMPTY_ATTS = new AttributesImpl(); //////////////////////////////////////////////////////////////////// // Internal state. //////////////////////////////////////////////////////////////////// private boolean inCDATA = false; 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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy