Mega Code Archive

 
Categories / Java / XML
 

Filter to write an XML document from a SAX event stream

//XMLWriter.java - serialize an XML document. //Written by David Megginson, david@megginson.com //NO WARRANTY!  This class is in the public domain. //Modified by John Cowan and Leigh Klotz for the TagSoup project.  Still in the public domain. //New features: //  it is a LexicalHandler //  it prints a comment if the LexicalHandler#comment method is called //  it supports certain XSLT output properties using get/setOutputProperty //$Id: XMLWriter.java,v 1.1 2004/01/28 05:35:43 joe Exp $ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.Enumeration; import java.util.Hashtable; import java.util.Properties; 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; import org.xml.sax.ext.LexicalHandler; /**  * Filter to write an XML document from a SAX event stream.  *   * <p>  * 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.  * </p>  *   * <p>  * 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  * </p>  *   * <pre>  * w.startElement(&quot;foo&quot;);  * </pre>  *   * <p>  * is equivalent to the regular SAX2 ContentHandler method  * </p>  *   * <pre>  * w.startElement(&quot;&quot;, &quot;foo&quot;, &quot;&quot;, new AttributesImpl());  * </pre>  *   * <p>  * 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:  * </p>  *   * <pre>  * XMLWriter w = new XMLWriter();  *   * w.startDocument();  * w.startElement(&quot;greeting&quot;);  * w.characters(&quot;Hello, world!&quot;);  * w.endElement(&quot;greeting&quot;);  * w.endDocument();  * </pre>  *   * <p>  * The resulting document will look like this:  * </p>  *   * <pre>  *  &lt;?xml version=&quot;1.0&quot; standalone=&quot;yes&quot;?&gt;  *   *  &lt;greeting&gt;Hello, world!&lt;/greeting&gt;  * </pre>  *   * <p>  * In fact, there is an even simpler convenience method, <var>dataElement</var>,  * designed for writing elements that contain only character data, so the code  * to generate the document could be shortened to  * </p>  *   * <pre>  * XMLWriter w = new XMLWriter();  *   * w.startDocument();  * w.dataElement(&quot;greeting&quot;, &quot;Hello, world!&quot;);  * w.endDocument();  * </pre>  *   * <h2>Whitespace</h2>  *   * <p>  * According to the XML Recommendation, <em>all</em> 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  * </p>  *   * <pre>  * w.dataElement(&quot;item&quot;, &quot;1&quot;);  * w.dataElement(&quot;item&quot;, &quot;2&quot;);  * w.dataElement(&quot;item&quot;, &quot;3&quot;);  * </pre>  *   * <p>  * you will end up with  * </p>  *   * <pre>  *  &lt;item&gt;1&lt;/item&gt;&lt;item&gt;3&lt;/item&gt;&lt;item&gt;3&lt;/item&gt;  * </pre>  *   * <p>  * You need to invoke one of the <var>characters</var> methods explicitly to  * add newlines or indentation. Alternatively, you can use  * {@link com.megginson.sax.DataWriter 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).  * </p>  *   *   * <h2>Namespace Support</h2>  *   * <p>  * The writer contains extensive support for XML Namespaces, so that a client  * application does not have to keep track of prefixes and supply <var>xmlns</var>  * 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:  * </p>  *   * <pre>  * w.startDocument();  * w.emptyElement(&quot;http://www.foo.com/ns/&quot;, &quot;foo&quot;);  * w.endDocument();  * </pre>  *   * <p>  * The resulting document will look like this:  * </p>  *   * <pre>  *  &lt;?xml version=&quot;1.0&quot; standalone=&quot;yes&quot;?&gt;  *   *  &lt;_NS1:foo xmlns:_NS1=&quot;http://www.foo.com/ns/&quot;/&gt;  * </pre>  *   * <p>  * 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:  * </p>  *   * <ol>  * <li>the qualified name</li>  * <li>the {@link #setPrefix setPrefix} method.</li>  * </ol>  *   * <p>  * 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).  * </p>  *   * <p>  * Before writing a document, the client can also pre-map a prefix to a  * Namespace URI with the setPrefix method:  * </p>  *   * <pre>  * w.setPrefix(&quot;http://www.foo.com/ns/&quot;, &quot;foo&quot;);  * w.startDocument();  * w.emptyElement(&quot;http://www.foo.com/ns/&quot;, &quot;foo&quot;);  * w.endDocument();  * </pre>  *   * <p>  * The resulting document will look like this:  * </p>  *   * <pre>  *  &lt;?xml version=&quot;1.0&quot; standalone=&quot;yes&quot;?&gt;  *   *  &lt;foo:foo xmlns:foo=&quot;http://www.foo.com/ns/&quot;/&gt;  * </pre>  *   * <p>  * The default Namespace simply uses an empty string as the prefix:  * </p>  *   * <pre>  * w.setPrefix(&quot;http://www.foo.com/ns/&quot;, &quot;&quot;);  * w.startDocument();  * w.emptyElement(&quot;http://www.foo.com/ns/&quot;, &quot;foo&quot;);  * w.endDocument();  * </pre>  *   * <p>  * The resulting document will look like this:  * </p>  *   * <pre>  *  &lt;?xml version=&quot;1.0&quot; standalone=&quot;yes&quot;?&gt;  *   *  &lt;foo xmlns=&quot;http://www.foo.com/ns/&quot;/&gt;  * </pre>  *   * <p>  * 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:  * </p>  *   * <pre>  *  &lt;xml version=&quot;1.0&quot; standalone=&quot;yes&quot;?&gt;  *   *  &lt;rdf:RDF xmlns:rdf=&quot;http://www.w3.org/1999/02/22-rdf-syntax-ns#&quot;&gt;  *   &lt;rdf:Description about=&quot;http://www.foo.com/ids/books/12345&quot;&gt;  *    &lt;dc:title xmlns:dc=&quot;http://www.purl.org/dc/&quot;&gt;A Dark Night&lt;/dc:title&gt;  *    &lt;dc:creator xmlns:dc=&quot;http://www.purl.org/dc/&quot;&gt;Jane Smith&lt;/dc:title&gt;  *    &lt;dc:date xmlns:dc=&quot;http://www.purl.org/dc/&quot;&gt;2000-09-09&lt;/dc:title&gt;  *   &lt;/rdf:Description&gt;  *  &lt;/rdf:RDF&gt;  * </pre>  *   * <p>  * 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:  * </p>  *   * <pre>  * w.forceNSDecl(&quot;http://www.purl.org/dc/&quot;);  * </pre>  *   * <p>  * 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:  * </p>  *   * <pre>  *  &lt;xml version=&quot;1.0&quot; standalone=&quot;yes&quot;?&gt;  *   *  &lt;rdf:RDF xmlns:rdf=&quot;http://www.w3.org/1999/02/22-rdf-syntax-ns#&quot;  *              xmlns:dc=&quot;http://www.purl.org/dc/&quot;&gt;  *   &lt;rdf:Description about=&quot;http://www.foo.com/ids/books/12345&quot;&gt;  *    &lt;dc:title&gt;A Dark Night&lt;/dc:title&gt;  *    &lt;dc:creator&gt;Jane Smith&lt;/dc:title&gt;  *    &lt;dc:date&gt;2000-09-09&lt;/dc:title&gt;  *   &lt;/rdf:Description&gt;  *  &lt;/rdf:RDF&gt;  * </pre>  *   * <p>  * This approach is also useful for declaring Namespace prefixes that be used by  * qualified names appearing in attribute values or character data.  * </p>  *   * @author David Megginson, david@megginson.com  * @version 0.2  * @see org.xml.sax.XMLFilter  * @see org.xml.sax.ContentHandler  */ public class XMLWriter extends XMLFilterImpl implements LexicalHandler {   // //////////////////////////////////////////////////////////////////   // Constructors.   // //////////////////////////////////////////////////////////////////   /**    * Create a new XML writer.    *     * <p>    * Write to standard output.    * </p>    */   public XMLWriter() {     init(null);   }   /**    * Create a new XML writer.    *     * <p>    * Write to the writer provided.    * </p>    *     * @param writer    *            The output destination, or null to use standard output.    */   public XMLWriter(Writer writer) {     init(writer);   }   /**    * Create a new XML writer.    *     * <p>    * Use the specified XML reader as the parent.    * </p>    *     * @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.    *     * <p>    * Use the specified XML reader as the parent, and write to the specified    * writer.    * </p>    *     * @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);   }   /**    * Internal initialization method.    *     * <p>    * 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);     nsSupport = new NamespaceSupport();     prefixTable = new Hashtable();     forcedDeclTable = new Hashtable();     doneDeclTable = new Hashtable();     outputProperties = new Properties();   }   // //////////////////////////////////////////////////////////////////   // Public methods.   // //////////////////////////////////////////////////////////////////   /**    * Reset the writer.    *     * <p>    * 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.    * </p>    *     * <p>    * This method is invoked automatically by the    * {@link #startDocument startDocument} method before writing a new    * document.    * </p>    *     * <p>    * <strong>Note:</strong> this method will <em>not</em> clear the prefix    * or URI information in the writer or the selected output writer.    * </p>    *     * @see #flush    */   public void reset() {     elementLevel = 0;     prefixCounter = 0;     nsSupport.reset();   }   /**    * Flush the output.    *     * <p>    * 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.    * </p>    *     * <p>    * This method is invoked automatically by the    * {@link #endDocument endDocument} method after writing a document.    * </p>    *     * @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.    * @return The current output writer.    * @see #flush    */   public void setOutput(Writer writer) {     if (writer == null) {       output = new OutputStreamWriter(System.out);     } else {       output = writer;     }   }   /**    * Specify a preferred prefix for a Namespace URI.    *     * <p>    * 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.    * </p>    *     * @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) {     prefixTable.put(uri, prefix);   }   /**    * 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 (String) prefixTable.get(uri);   }   /**    * Force a Namespace to be declared on the root element.    *     * <p>    * 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.    * </p>    *     * <p>    * 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.    * </p>    *     * @param uri    *            The Namespace URI to declare.    * @see #forceNSDecl(java.lang.String,java.lang.String)    * @see #setPrefix    */   public void forceNSDecl(String uri) {     forcedDeclTable.put(uri, Boolean.TRUE);   }   /**    * Force a Namespace declaration with a preferred prefix.    *     * <p>    * This is a convenience method that invokes {@link #setPrefix setPrefix}    * then {@link #forceNSDecl(java.lang.String) forceNSDecl}.    * </p>    *     * @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);   }   // //////////////////////////////////////////////////////////////////   // 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 {     reset();     if (!("yes".equals(outputProperties.getProperty(OMIT_XML_DECLARATION,         "no"))))       write("<?xml version=\"1.0\" standalone=\"yes\"?>\n\n");     super.startDocument();   }   /**    * 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 {     write('\n');     super.endDocument();     try {       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    */   public void startElement(String uri, String localName, String qName,       Attributes atts) throws SAXException {     elementLevel++;     nsSupport.pushContext();     write('<');     writeName(uri, localName, qName, true);     writeAttributes(atts);     if (elementLevel == 1) {       forceNSDecls();     }     writeNSDecls();     write('>');     if ("html".equals(outputProperties.getProperty(METHOD, "xml"))         && (localName.equals("script") || localName.equals("style"))) {       cdataElement = true;     }     super.startElement(uri, localName, qName, atts);   }   /**    * 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    */   public void endElement(String uri, String localName, String qName)       throws SAXException {     if (!("html".equals(outputProperties.getProperty(METHOD, "xml"))         && uri.equals("http://www.w3.org/1999/xhtml") && (localName         .equals("area")         || localName.equals("base")         || localName.equals("basefont")         || localName.equals("br")         || localName.equals("col")         || localName.equals("frame")         || localName.equals("hr")         || localName.equals("img")         || localName.equals("input")         || localName.equals("isindex")         || localName.equals("link")         || localName.equals("meta") || localName.equals("param")))) {       write("</");       writeName(uri, localName, qName, true);       write('>');     }     if (elementLevel == 1) {       write('\n');     }     cdataElement = false;     super.endElement(uri, localName, qName);     nsSupport.popContext();     elementLevel--;   }   /**    * 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 length    *            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    */   public void characters(char ch[], int start, int len) throws SAXException {     if (!cdataElement) {       writeEsc(ch, start, len, false);     } else {       for (int i = start; i < start + len; i++) {         write(ch[i]);       }     }     super.characters(ch, start, len);   }   /**    * 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    */   public void ignorableWhitespace(char ch[], int start, int length)       throws SAXException {     writeEsc(ch, start, length, false);     super.ignorableWhitespace(ch, start, length);   }   /**    * 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    */   public void processingInstruction(String target, String data)       throws SAXException {     write("<?");     write(target);     write(' ');     write(data);     write("?>");     if (elementLevel < 1) {       write('\n');     }     super.processingInstruction(target, data);   }   // //////////////////////////////////////////////////////////////////   // Additional markup.   // //////////////////////////////////////////////////////////////////   /**    * 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    *                handler 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 {     nsSupport.pushContext();     write('<');     writeName(uri, localName, qName, true);     writeAttributes(atts);     if (elementLevel == 1) {       forceNSDecls();     }     writeNSDecls();     write("/>");     super.startElement(uri, localName, qName, atts);     super.endElement(uri, localName, qName);   }   // //////////////////////////////////////////////////////////////////   // Convenience methods.   // //////////////////////////////////////////////////////////////////   /**    * Start a new element without a qname or attributes.    *     * <p>    * 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.    * </p>    *     * @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.    *     * <p>    * 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.    * </p>    *     * @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.    *     * <p>    * This method will supply an empty string for the qName. It invokes    * {@link #endElement(String, String, String)} directly.    * </p>    *     * @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.    *     * <p>    * 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.    * </p>    *     * @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, "");   }   /**    * Add an empty element without a qname or attributes.    *     * <p>    * This method will supply an empty string for the qname and an empty    * attribute list. It invokes    * {@link #emptyElement(String, String, String, Attributes)} directly.    * </p>    *     * @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    *                handler 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, "", EMPTY_ATTS);   }   /**    * Add an empty element without a Namespace URI, qname or attributes.    *     * <p>    * 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.    * </p>    *     * @param localName    *            The element's local name.    * @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 #emptyElement(String, String, String, Attributes)    */   public void emptyElement(String localName) throws SAXException {     emptyElement("", localName, "", EMPTY_ATTS);   }   /**    * Write an element with character data content.    *     * <p>    * This is a convenience method to write a complete element with character    * data content, including the start tag and end tag.    * </p>    *     * <p>    * This method invokes    * {@link #startElement(String, String, String, Attributes)}, followed by    * {@link #characters(String)}, followed by    * {@link #endElement(String, String, String)}.    * </p>    *     * @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.    *     * <p>    * 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.    * </p>    *     * <p>    * This method invokes    * {@link #startElement(String, String, String, Attributes)}, followed by    * {@link #characters(String)}, followed by    * {@link #endElement(String, String, String)}.    * </p>    *     * @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.    *     * <p>    * 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.    * </p>    *     * <p>    * This method invokes    * {@link #startElement(String, String, String, Attributes)}, followed by    * {@link #characters(String)}, followed by    * {@link #endElement(String, String, String)}.    * </p>    *     * @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.    *     * <p>    * This is a convenience method that takes an XML String, converts it to a    * character array, then invokes {@link #characters(char[], int, int)}.    * </p>    *     * @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 {     char ch[] = data.toCharArray();     characters(ch, 0, ch.length);   }   // //////////////////////////////////////////////////////////////////   // Internal methods.   // //////////////////////////////////////////////////////////////////   /**    * 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() {     Enumeration prefixes = forcedDeclTable.keys();     while (prefixes.hasMoreElements()) {       String prefix = (String) prefixes.nextElement();       doPrefix(prefix, null, true);     }   }   /**    * 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) {     String defaultNS = nsSupport.getURI("");     if ("".equals(uri)) {       if (isElement && defaultNS != null)         nsSupport.declarePrefix("", "");       return null;     }     String prefix;     if (isElement && defaultNS != null && uri.equals(defaultNS)) {       prefix = "";     } else {       prefix = nsSupport.getPrefix(uri);     }     if (prefix != null) {       return prefix;     }     prefix = (String) doneDeclTable.get(uri);     if (prefix != null         && ((!isElement || defaultNS != null) && "".equals(prefix) || nsSupport             .getURI(prefix) != null)) {       prefix = null;     }     if (prefix == null) {       prefix = (String) prefixTable.get(uri);       if (prefix != null           && ((!isElement || defaultNS != null) && "".equals(prefix) || nsSupport               .getURI(prefix) != null)) {         prefix = null;       }     }     if (prefix == null && qName != null && !"".equals(qName)) {       int i = qName.indexOf(':');       if (i == -1) {         if (isElement && defaultNS == null) {           prefix = "";         }       } else {         prefix = qName.substring(0, i);       }     }     for (; prefix == null || nsSupport.getURI(prefix) != null; prefix = "__NS"         + ++prefixCounter)       ;     nsSupport.declarePrefix(prefix, uri);     doneDeclTable.put(uri, prefix);     return prefix;   }   /**    * 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 {       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 {       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 {     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 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("&amp;");         break;       case '<':         write("&lt;");         break;       case '>':         write("&gt;");         break;       case '\"':         if (isAttVal) {           write("&quot;");         } else {           write('\"');         }         break;       default:         write(ch[i]);       }     }   }   /**    * 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.    */   private void writeNSDecls() throws SAXException {     Enumeration prefixes = nsSupport.getDeclaredPrefixes();     while (prefixes.hasMoreElements()) {       String prefix = (String) prefixes.nextElement();       String uri = nsSupport.getURI(prefix);       if (uri == null) {         uri = "";       }       char ch[] = uri.toCharArray();       write(' ');       if ("".equals(prefix)) {         write("xmlns=\"");       } else {         write("xmlns:");         write(prefix);         write("=\"");       }       writeEsc(ch, 0, ch.length, true);       write('\"');     }   }   /**    * 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 {     String prefix = doPrefix(uri, qName, isElement);     if (prefix != null && !"".equals(prefix)) {       write(prefix);       write(':');     }     if (localName != null && !"".equals(localName)) {       write(localName);     } else {       int i = qName.indexOf(':');       write(qName.substring(i + 1, qName.length()));     }   }   // //////////////////////////////////////////////////////////////////   // Default LexicalHandler implementation   // //////////////////////////////////////////////////////////////////   public void comment(char[] ch, int start, int length) throws SAXException {     write("<!--");     for (int i = start; i < start + length; i++) {       write(ch[i]);       if (ch[i] == '-' && i + 1 <= start + length && ch[i + 1] == '-')         write(' ');     }     write("-->");   }   public void endCDATA() throws SAXException {   }   public void endDTD() throws SAXException {   }   public void endEntity(String name) throws SAXException {   }   public void startCDATA() throws SAXException {   }   public void startDTD(String name, String publicId, String systemId)       throws SAXException {   }   public void startEntity(String name) throws SAXException {   }   // //////////////////////////////////////////////////////////////////   // Output properties   // //////////////////////////////////////////////////////////////////   public String getOutputProperty(String key) {     return outputProperties.getProperty(key);   }   public void setOutputProperty(String key, String value) {     outputProperties.setProperty(key, value);   }   // //////////////////////////////////////////////////////////////////   // Constants.   // //////////////////////////////////////////////////////////////////   private final Attributes EMPTY_ATTS = new AttributesImpl();   public static final String CDATA_SECTION_ELEMENTS = "cdata-section-elements";   public static final String DOCTYPE_PUBLIC = "doctype-public";   public static final String DOCTYPE_SYSTEM = "doctype-system";   public static final String ENCODING = "encoding";   public static final String INDENT = "indent";   public static final String MEDIA_TYPE = "media-type";   public static final String METHOD = "method";   public static final String OMIT_XML_DECLARATION = "omit-xml-declaration";   public static final String STANDALONE = "standalone";   public static final String VERSION = "version";   // //////////////////////////////////////////////////////////////////   // Internal state.   // //////////////////////////////////////////////////////////////////   private Hashtable prefixTable;   private Hashtable forcedDeclTable;   private Hashtable doneDeclTable;   private int elementLevel = 0;   private Writer output;   private NamespaceSupport nsSupport;   private int prefixCounter = 0;   private Properties outputProperties;   private boolean cdataElement = false; } // end of XMLWriter.java