Java/XML/Writer

Материал из Java эксперт
Перейти к: навигация, поиск

Convenience methods for writing XML

  
/*
 * Copyright Aduna (http://www.aduna-software.ru/) (c) 1997-2006.
 *
 * Licensed under the Aduna BSD-style license.
 */
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
/**
 * A utility class offering convenience methods for writing XML. This class
 * takes care of character escaping, identation, etc. This class does not verify
 * that the written data is legal XML. It is the callers responsibility to make
 * sure that elements are properly nested, etc.
 *
 * <h3>Example:</h3>
 * <p>
 * To write the following XML:
 * <pre>
 * &lt;?xml version="1.0" encoding="UTF-8"?&gt;
 * &lt;xml-doc&gt;
 *  &lt;foo a="1" b="2&amp;amp;3"/&gt;
 *  &lt;bar&gt;Hello World!&lt;/bar&gt;
 * &lt;/xml-doc&gt;
 *</pre>
 * <p>
 * One can use the following code:
 * <pre>
 * XMLWriter xmlWriter = new XMLWriter(myWriter);
 * xmlWriter.setPrettyPrint(true);
 *
 * xmlWriter.startDocument();
 * xmlWriter.startTag("xml-doc");
 *
 * xmlWriter.setAttribute("a", 1);
 * xmlWriter.setAttribute("b", "2&amp;3");
 * xmlWriter.simpleTag("foo");
 *
 * xmlWriter.textTag("bar", "Hello World!");
 *
 * xmlWriter.endTag("xml-doc");
 * xmlWriter.endDocument();
 * </pre>
 */
public class XMLWriter {
  /*-----------*
   * Constants *
   *-----------*/
  /**
   * The (platform-dependent) line separator.
   */
  private static final String LINE_SEPARATOR = System.getProperty("line.separator");
  /*-----------*
   * Variables *
   *-----------*/
  /**
   * The writer to write the XML to.
   */
  private Writer _writer;
  /**
   * The required character encoding of the written data.
   */
  private String _charEncoding;
  /**
   * Flag indicating whether the output should be printed pretty, i.e. adding
   * newlines and indentation.
   */
  private boolean _prettyPrint = false;
  /**
   * The current indentation level, i.e. the number of tabs to indent a start
   * or end tag.
   */
  protected int _indentLevel = 0;
  /**
   * The string to use for indentation, e.g. a tab or a number of spaces.
   */
  private String _indentString = "\t";
  /**
   * A mapping from attribute names to values for the next start tag.
   */
  private HashMap<String, String> _attributes = new LinkedHashMap<String, String>();
  /*--------------*
   * Constructors *
   *--------------*/
  /**
   * Creates a new XMLWriter that will write its data to the supplied Writer.
   * Character encoding issues are left to the supplier of the Writer.
   *
   * @param writer The Writer to write the XML to.
   */
  public XMLWriter(Writer writer) {
    _writer = writer;
  }
  /**
   * Creates a new XMLWriter that will write its data to the supplied
   * OutputStream in the default UTF-8 character encoding.
   *
   * @param outputStream The OutputStream to write the XML to.
   */
  public XMLWriter(OutputStream outputStream) {
    try {
      _charEncoding = "UTF-8";
      _writer = new OutputStreamWriter(outputStream, _charEncoding);
    }
    catch (UnsupportedEncodingException e) {
      // UTF-8 must be supported by all compliant JVM"s,
      // this exception should never be thrown.
      throw new RuntimeException(
          "UTF-8 character encoding not supported on this platform");
    }
  }
  /**
   * Creates a new XMLWriter that will write its data to the supplied
   * OutputStream in specified character encoding.
   *
   * @param outputStream The OutputStream to write the XML to.
   */
  public XMLWriter(OutputStream outputStream, String charEncoding)
    throws UnsupportedEncodingException
  {
    _charEncoding = charEncoding;
    _writer = new OutputStreamWriter(outputStream, _charEncoding);
  }
  /*---------*
   * Methods *
   *---------*/
  /**
   * Enables or disables pretty-printing. If pretty-printing is enabled, the
   * XMLWriter will add newlines and indentation to the written data.
   * Pretty-printing is disabled by default.
   *
   * @param prettyPrint Flag indicating whether pretty-printing should be
   * enabled.
   */
  public void setPrettyPrint(boolean prettyPrint) {
    _prettyPrint = prettyPrint;
  }
  /**
   * Checks whether pretty-printing is enabled.
   *
   * @return <tt>true</tt> if pretty-printing is enabled, <tt>false</tt>
   * otherwise.
   */
  public boolean prettyPrintEnabled() {
    return _prettyPrint;
  }
  /**
   * Sets the string that should be used for indentation when pretty-printing
   * is enabled. The default indentation string is a tab character.
   *
   * @param indentString The indentation string, e.g. a tab or a number of
   * spaces.
   */
  public void setIndentString(String indentString) {
    _indentString = indentString;
  }
  /**
   * Gets the string used for indentation.
   *
   * @return the indentation string.
   */
  public String getIndentString() {
    return _indentString;
  }
  /**
   * Writes the XML header for the XML file.
   *
   * @throws IOException If an I/O error occurs.
   */
  public void startDocument()
    throws IOException
  {
    _write("<?xml version="1.0"");
    if (_charEncoding != null) {
      _write(" encoding="" + _charEncoding + """);
    }
    _writeLn("?>");
  }
  /**
   * Finishes writing and flushes the OutputStream or Writer that this
   * XMLWriter is writing to.
   */
  public void endDocument()
    throws IOException
  {
    _writer.flush();
    _writer = null;
  }
  /**
   * Sets an attribute for the next start tag.
   *
   * @param name The name of the attribute.
   * @param value The value of the attribute.
   */
  public void setAttribute(String name, String value) {
    _attributes.put(name, value);
  }
  /**
   * Sets an attribute for the next start element.
   *
   * @param name The name of the attribute.
   * @param value The value of the attribute. The integer value will be
   * transformed to a string using the method <tt>String.valueOf(int)</tt>.
   * @see java.lang.String#valueOf(int)
   */
  public void setAttribute(String name, int value) {
    setAttribute(name, String.valueOf(value));
  }
  /**
   * Sets an attribute for the next start element.
   *
   * @param name The name of the attribute.
   * @param value The value of the attribute. The boolean value will be
   * transformed to a string using the method
   * <tt>String.valueOf(boolean)</tt>.
   * @see java.lang.String#valueOf(boolean)
   */
  public void setAttribute(String name, boolean value) {
    setAttribute(name, String.valueOf(value));
  }
  /**
   * Writes a start tag containing the previously set attributes.
   *
   * @param elName The element name.
   * @see #setAttribute(java.lang.String,java.lang.String)
   */
  public void startTag(String elName)
    throws IOException
  {
    _writeIndent();
    _write("<" + elName);
    _writeAtts();
    _writeLn(">");
    _indentLevel++;
  }
  /**
   * Writes an end tag.
   *
   * @param elName The element name.
   */
  public void endTag(String elName)
    throws IOException
  {
    _indentLevel--;
    _writeIndent();
    _writeLn("</" + elName + ">");
  }
  /**
   * Writes an "empty" element, e.g. <tt>&lt;foo/&gt;</tt>. The tag will
   * contain any previously set attributes.
   *
   * @param elName The element name.
   * @see #setAttribute(java.lang.String,java.lang.String)
   */
  public void emptyElement(String elName)
    throws IOException
  {
    _writeIndent();
    _write("<" + elName);
    _writeAtts();
    _writeLn("/>");
  }
  /**
   * Writes a start and end tag with the supplied text between them. The start
   * tag will contain any previously set attributes.
   *
   * @param elName The element name.
   * @param text The text.
   * @see #setAttribute(java.lang.String,java.lang.String)
   */
  public void textElement(String elName, String text)
    throws IOException
  {
    _writeIndent();
    _write("<" + elName);
    _writeAtts();
    _write(">");
    text(text);
    _writeLn("</" + elName + ">");
  }
  /**
   * Writes a start and end tag with the supplied text between them, without
   * the usual escape rules. The start tag will contain any previously set
   * attributes.
   *
   * @param elName The element name.
   * @param text The text.
   * @see #setAttribute(java.lang.String,java.lang.String)
   */
  public void unescapedTextElement(String elName, String text)
    throws IOException
  {
    _writeIndent();
    _write("<" + elName);
    _writeAtts();
    _write(">");
    _write(text);
    _writeLn("</" + elName + ">");
  }
  /**
   * Writes a start and end tag with the supplied value between them. The
   * start tag will contain any previously set attributes.
   *
   * @param elName The element name.
   * @param value The value. The integer value will be transformed to a string
   * using the method <tt>String.valueOf(int)</tt>.
   * @see java.lang.String#valueOf(int)
   */
  public void textElement(String elName, int value)
    throws IOException
  {
    textElement(elName, String.valueOf(value));
  }
  /**
   * Writes a start and end tag with the supplied boolean value between them.
   * The start tag will contain any previously set attributes.
   *
   * @param elName The element name.
   * @param value The boolean value. The integer value will be transformed to
   * a string using the method <tt>String.valueOf(boolean)</tt>.
   * @see java.lang.String#valueOf(boolean)
   */
  public void textElement(String elName, boolean value)
    throws IOException
  {
    textElement(elName, String.valueOf(value));
  }
  /**
   * Writes a piece of text.
   *
   * @param text The text.
   */
  public void text(String text)
    throws IOException
  {
    _write(text);
  }
  
  /**
   * Writes a comment.
   *
   * @param comment The comment.
   */
  public void comment(String comment)
    throws IOException
  {
    _writeIndent();
    _writeLn("<!-- " + comment + " -->");
  }
  /**
   * Writes an empty line. A call to this method will be ignored when
   * pretty-printing is disabled.
   *
   * @see #setPrettyPrint
   */
  public void emptyLine()
    throws IOException
  {
    _writeLn("");
  }
  /**
   * Writes any set attributes and clears them afterwards.
   */
  private void _writeAtts()
    throws IOException
  {
    for (Entry<String,String> entry : _attributes.entrySet()) {
      String name = entry.getKey();
      String value = entry.getValue();
      _write(" " + name + "="");
      if (value != null) {
        _write(value);
      }
      _write(""");
    }
    
    _attributes.clear();
  }
  /**
   * Writes a string.
   */
  protected void _write(String s)
    throws IOException
  {
    _writer.write(s);
  }
  /**
   * Writes a string followed by a line-separator. The line-separator is not
   * written when pretty-printing is disabled.
   */
  protected void _writeLn(String s)
    throws IOException
  {
    _write(s);
    if (_prettyPrint) {
      _write(LINE_SEPARATOR);
    }
  }
  /**
   * Writes as much indentation strings as appropriate for the current
   * indentation level. A call to this method is ignored when pretty-printing
   * is disabled.
   */
  protected void _writeIndent()
    throws IOException
  {
    if (_prettyPrint) {
      for (int i = 0; i < _indentLevel; i++) {
        _write(_indentString);
      }
    }
  }
}





DOM writer

 
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don"t indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
/*
 * The Apache Software License, Version 1.1
 *
 *
 * Copyright (c) 1999-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Xerces" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS"" AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * 
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation and was
 * originally based on software copyright (c) 1999, International
 * Business Machines, Inc., http://www.apache.org.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
/**
 * A sample DOM writer. This sample program illustrates how to
 * traverse a DOM tree in order to print a document that is parsed.
 *
 * @author Andy Clark, IBM
 *
 * @version $Id: DOMWriter.java,v 1.2 2007/07/19 04:35:46 ofung Exp $
 */
public class DOMWriter {
    //
    // Constants
    //
    // feature ids
    /** Namespaces feature id (http://xml.org/sax/features/namespaces). */
    protected static final String NAMESPACES_FEATURE_ID = "http://xml.org/sax/features/namespaces";
    /** Validation feature id (http://xml.org/sax/features/validation). */
    protected static final String VALIDATION_FEATURE_ID = "http://xml.org/sax/features/validation";
    /** Schema validation feature id (http://apache.org/xml/features/validation/schema). */
    protected static final String SCHEMA_VALIDATION_FEATURE_ID = "http://apache.org/xml/features/validation/schema";
    /** Schema full checking feature id (http://apache.org/xml/features/validation/schema-full-checking). */
    protected static final String SCHEMA_FULL_CHECKING_FEATURE_ID = "http://apache.org/xml/features/validation/schema-full-checking";
    // property ids
    /** Lexical handler property id (http://xml.org/sax/properties/lexical-handler). */
    protected static final String LEXICAL_HANDLER_PROPERTY_ID = "http://xml.org/sax/properties/lexical-handler";
    // default settings
    /** Default parser name. */
    protected static final String DEFAULT_PARSER_NAME = "dom.wrappers.Xerces";
    /** Default namespaces support (true). */
    protected static final boolean DEFAULT_NAMESPACES = true;
    /** Default validation support (false). */
    protected static final boolean DEFAULT_VALIDATION = false;
    /** Default Schema validation support (false). */
    protected static final boolean DEFAULT_SCHEMA_VALIDATION = false;
    /** Default Schema full checking support (false). */
    protected static final boolean DEFAULT_SCHEMA_FULL_CHECKING = false;
    /** Default canonical output (false). */
    protected static final boolean DEFAULT_CANONICAL = false;
    //
    // Data
    //
    /** Print writer. */
    protected PrintWriter fOut;
    /** Canonical output. */
    protected boolean fCanonical;
    
    /** Processing XML 1.1 document. */
    protected boolean fXML11;
    //
    // Constructors
    //
    /** Default constructor. */
    public DOMWriter() {
    } // <init>()
    public DOMWriter(boolean canonical) {
        fCanonical = canonical;
    } // <init>(boolean)
    
    public DOMWriter( OutputStream out, String encoding ) throws UnsupportedEncodingException {
        this();
        setOutput(out,encoding);
    }
    //
    // Public methods
    //
    /** Sets whether output is canonical. */
    public void setCanonical(boolean canonical) {
        fCanonical = canonical;
    } // setCanonical(boolean)
    /** Sets the output stream for printing. */
    public void setOutput(OutputStream stream, String encoding)
        throws UnsupportedEncodingException {
        if (encoding == null) {
            encoding = "UTF8";
        }
        java.io.Writer writer = new OutputStreamWriter(stream, encoding);
        fOut = new PrintWriter(writer);
    } // setOutput(OutputStream,String)
    /** Sets the output writer. */
    public void setOutput(java.io.Writer writer) {
        fOut = writer instanceof PrintWriter
             ? (PrintWriter)writer : new PrintWriter(writer);
    } // setOutput(java.io.Writer)
    /** Writes the specified node, recursively. */
    public void write(Node node) {
        // is there anything to do?
        if (node == null) {
            return;
        }
        short type = node.getNodeType();
        switch (type) {
            case Node.DOCUMENT_NODE: {
                Document document = (Document)node;
                fXML11 = "1.1".equals(getVersion(document));
                if (!fCanonical) {
                    if (fXML11) {
                        fOut.println("<?xml version=\"1.1\" encoding=\"UTF-8\"?>");
                    }
                    else {
                        fOut.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
                    }
                    fOut.flush();
                    write(document.getDoctype());
                }
                write(document.getDocumentElement());
                break;
            }
            case Node.DOCUMENT_TYPE_NODE: {
                DocumentType doctype = (DocumentType)node;
                fOut.print("<!DOCTYPE ");
                fOut.print(doctype.getName());
                String publicId = doctype.getPublicId();
                String systemId = doctype.getSystemId();
                if (publicId != null) {
                    fOut.print(" PUBLIC "");
                    fOut.print(publicId);
                    fOut.print("" "");
                    fOut.print(systemId);
                    fOut.print("\"");
                }
                else if (systemId != null) {
                    fOut.print(" SYSTEM "");
                    fOut.print(systemId);
                    fOut.print("\"");
                }
                String internalSubset = doctype.getInternalSubset();
                if (internalSubset != null) {
                    fOut.println(" [");
                    fOut.print(internalSubset);
                    fOut.print("]");
                }
                fOut.println(">");
                break;
            }
            case Node.ELEMENT_NODE: {
                fOut.print("<");
                fOut.print(node.getNodeName());
                Attr attrs[] = sortAttributes(node.getAttributes());
                for (int i = 0; i < attrs.length; i++) {
                    Attr attr = attrs[i];
                    fOut.print(" ");
                    fOut.print(attr.getNodeName());
                    fOut.print("=\"");
                    normalizeAndPrint(attr.getNodeValue(), true);
                    fOut.print(""");
                }
                fOut.print(">");
                fOut.flush();
                Node child = node.getFirstChild();
                while (child != null) {
                    write(child);
                    child = child.getNextSibling();
                }
                break;
            }
            case Node.ENTITY_REFERENCE_NODE: {
                if (fCanonical) {
                    Node child = node.getFirstChild();
                    while (child != null) {
                        write(child);
                        child = child.getNextSibling();
                    }
                }
                else {
                    fOut.print("&");
                    fOut.print(node.getNodeName());
                    fOut.print(";");
                    fOut.flush();
                }
                break;
            }
            case Node.CDATA_SECTION_NODE: {
                if (fCanonical) {
                    normalizeAndPrint(node.getNodeValue(), false);
                }
                else {
                    fOut.print("<![CDATA[");
                    fOut.print(node.getNodeValue());
                    fOut.print("]]&gt;");
                }
                fOut.flush();
                break;
            }
            case Node.TEXT_NODE: {
                normalizeAndPrint(node.getNodeValue(), false);
                fOut.flush();
                break;
            }
            case Node.PROCESSING_INSTRUCTION_NODE: {
                fOut.print("<?");
                fOut.print(node.getNodeName());
                String data = node.getNodeValue();
                if (data != null && data.length() > 0) {
                    fOut.print(" ");
                    fOut.print(data);
                }
                fOut.print("?>");
                fOut.flush();
                break;
            }
            
            case Node.ruMENT_NODE: {
                if (!fCanonical) {
                    fOut.print("<!--");
                    String comment = node.getNodeValue();
                    if (comment != null && comment.length() > 0) {
                        fOut.print(comment);
                    }
                    fOut.print("-->");
                    fOut.flush();
                }
            }
        }
        if (type == Node.ELEMENT_NODE) {
            fOut.print("</");
            fOut.print(node.getNodeName());
            fOut.print(">");
            fOut.flush();
        }
    } // write(Node)
    /** Returns a sorted list of attributes. */
    protected Attr[] sortAttributes(NamedNodeMap attrs) {
        int len = (attrs != null) ? attrs.getLength() : 0;
        Attr array[] = new Attr[len];
        for (int i = 0; i < len; i++) {
            array[i] = (Attr)attrs.item(i);
        }
        for (int i = 0; i < len - 1; i++) {
            String name = array[i].getNodeName();
            int index = i;
            for (int j = i + 1; j < len; j++) {
                String curName = array[j].getNodeName();
                if (curName.rupareTo(name) < 0) {
                    name = curName;
                    index = j;
                }
            }
            if (index != i) {
                Attr temp = array[i];
                array[i] = array[index];
                array[index] = temp;
            }
        }
        return array;
    } // sortAttributes(NamedNodeMap):Attr[]
    //
    // Protected methods
    //
    /** Normalizes and prints the given string. */
    protected void normalizeAndPrint(String s, boolean isAttValue) {
        int len = (s != null) ? s.length() : 0;
        for (int i = 0; i < len; i++) {
            char c = s.charAt(i);
            normalizeAndPrint(c, isAttValue);
        }
    } // normalizeAndPrint(String,boolean)
    /** Normalizes and print the given character. */
    protected void normalizeAndPrint(char c, boolean isAttValue) {
        switch (c) {
            case "<": {
                fOut.print("&lt;");
                break;
            }
            case ">": {
                fOut.print("&gt;");
                break;
            }
            case "&": {
                fOut.print("&amp;");
                break;
            }
            case """: {
                // A """ that appears in character data 
                // does not need to be escaped.
                if (isAttValue) {
                    fOut.print("&quot;");
                }
                else {
                    fOut.print("\"");
                }
                break;
            }
            case "\r": {
                // If CR is part of the document"s content, it
                // must not be printed as a literal otherwise
                // it would be normalized to LF when the document
                // is reparsed.
                fOut.print("&#xD;");
                break;
            }
            case "\n": {
                if (fCanonical) {
                    fOut.print("&#xA;");
                    break;
                }
                // else, default print char
            }
            default: {
                // In XML 1.1, control chars in the ranges [#x1-#x1F, #x7F-#x9F] must be escaped.
                //
                // Escape space characters that would be normalized to #x20 in attribute values
                // when the document is reparsed.
                //
                // Escape NEL (0x85) and LSEP (0x2028) that appear in content 
                // if the document is XML 1.1, since they would be normalized to LF 
                // when the document is reparsed.
                if (fXML11 && ((c >= 0x01 && c <= 0x1F && c != 0x09 && c != 0x0A) 
                    || (c >= 0x7F && c <= 0x9F) || c == 0x2028)
                    || isAttValue && (c == 0x09 || c == 0x0A)) {
                    fOut.print("&#x");
                    fOut.print(Integer.toHexString(c).toUpperCase());
                    fOut.print(";");
                }
                else {
                    fOut.print(c);
                }        
            }
        }
    } // normalizeAndPrint(char,boolean)
    /** Extracts the XML version from the Document. */
    protected String getVersion(Document document) {
        if (document == null) {
            return null;
        }
        String version = null;
        Method getXMLVersion = null;
        try {
            getXMLVersion = document.getClass().getMethod("getXmlVersion", new Class[]{});
            // If Document class implements DOM L3, this method will exist.
            if (getXMLVersion != null) {
                version = (String) getXMLVersion.invoke(document, null);
            }
        } 
        catch (Exception e) { 
            // Either this locator object doesn"t have 
            // this method, or we"re on an old JDK.
        }
        return version;
    } // getVersion(Document)

    //
    // Private static methods
    //
    /** Prints the usage. */
    private static void printUsage() {
        System.err.println("usage: java dom.Writer (options) uri ...");
        System.err.println();
        System.err.println("options:");
        System.err.println("  -p name  Select parser by name.");
        System.err.println("  -n | -N  Turn on/off namespace processing.");
        System.err.println("  -v | -V  Turn on/off validation.");
        System.err.println("  -s | -S  Turn on/off Schema validation support.");
        System.err.println("           NOTE: Not supported by all parsers.");
        System.err.println("  -f  | -F Turn on/off Schema full checking.");
        System.err.println("           NOTE: Requires use of -s and not supported by all parsers.");
        System.err.println("  -c | -C  Turn on/off Canonical XML output.");
        System.err.println("           NOTE: This is not W3C canonical output.");
        System.err.println("  -h       This help screen.");
        System.err.println();
        System.err.println("defaults:");
        System.err.println("  Parser:     "+DEFAULT_PARSER_NAME);
        System.err.print("  Namespaces: ");
        System.err.println(DEFAULT_NAMESPACES ? "on" : "off");
        System.err.print("  Validation: ");
        System.err.println(DEFAULT_VALIDATION ? "on" : "off");
        System.err.print("  Schema:     ");
        System.err.println(DEFAULT_SCHEMA_VALIDATION ? "on" : "off");
        System.err.print("  Schema full checking:     ");
        System.err.println(DEFAULT_SCHEMA_FULL_CHECKING ? "on" : "off");
        System.err.print("  Canonical:  ");
        System.err.println(DEFAULT_CANONICAL ? "on" : "off");
    } // printUsage()
}  // class Writer





Makes writing XML much much easier

 
/* 
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org /)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" and
 *    "Apache Commons" must not be used to endorse or promote products
 *    derived from this software without prior written permission. For
 *    written permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    "Apache Turbine", nor may "Apache" appear in their name, without
 *    prior written permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS"" AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * 
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org />.
 */
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.Stack;

/**
 * Makes writing XML much much easier.
 * Improved from
 * 
 * @author Last changed by: $Author: gommma $
 * @version $Revision: 859 $ $Date: 2008-11-02 12:50:23 +0100 (dom, 02 nov 2008) $
 * @since 1.0
 */
public class XmlWriter
{
    /**
     * CDATA start tag: {@value}
     */
    public static final String CDATA_START = "<![CDATA[";
    /**
     * CDATA end tag: {@value}
     */
    public static final String CDATA_END = "]]&gt;";
    /**
     * Default encoding value which is {@value}
     */
    public static final String DEFAULT_ENCODING = "UTF-8";
    
    /**
     * Logger for this class
     */
    private Writer out;      // underlying writer
    private String encoding; // the encoding to be written into the XML header/metatag
    private Stack stack = new Stack();        // of xml element names
    private StringBuffer attrs; // current attribute string
    private boolean empty;      // is the current node empty
    private boolean closed = true;     // is the current node closed...
    private boolean pretty = true;    // is pretty printing enabled?
    /**
     * was text the last thing output?
     */
    private boolean wroteText = false;
    /**
     * output this to indent one level when pretty printing
     */
    private String indent = "  ";
    /**
     * output this to end a line when pretty printing
     */
    private String newline = "\n";
    
    /**
     * Create an XmlWriter on top of an existing java.io.Writer.
     */
    public XmlWriter(Writer writer)
    {
        this(writer, null);
    }
    /**
     * Create an XmlWriter on top of an existing java.io.Writer.
     */
    public XmlWriter(Writer writer, String encoding)
    {
        setWriter(writer, encoding);
    }
    /**
     * Create an XmlWriter on top of an existing {@link java.io.OutputStream}.
     * @param outputStream
     * @param encoding The encoding to be used for writing to the given output
     * stream. Can be null. If it is null the 
     * {@link #DEFAULT_ENCODING} is used.
     * @throws UnsupportedEncodingException 
     * @since 2.4
     */
    public XmlWriter(OutputStream outputStream, String encoding) 
    throws UnsupportedEncodingException
    {
        if(encoding==null)
        {
            encoding = DEFAULT_ENCODING;            
        }
        OutputStreamWriter writer = new OutputStreamWriter(outputStream, encoding);
        setWriter(writer, encoding);
    }

    /**
     * Turn pretty printing on or off.
     * Pretty printing is enabled by default, but it can be turned off
     * to generate more compact XML.
     *
     * @param enable true to enable, false to disable pretty printing.
     */
    public void enablePrettyPrint(boolean enable)
    {
        this.pretty = enable;
    }
  /**
     * Specify the string to prepend to a line for each level of indent.
     * It is 2 spaces ("  ") by default. Some may prefer a single tab ("\t")
     * or a different number of spaces. Specifying an empty string will turn
     * off indentation when pretty printing.
     *
     * @param indent representing one level of indentation while pretty printing.
     */
    public void setIndent(String indent)
    {
        this.indent = indent;
    }
    /**
     * Specify the string used to terminate each line when pretty printing.
     * It is a single newline ("\n") by default. Users who need to read
     * generated XML documents in Windows editors like Notepad may wish to
     * set this to a carriage return/newline sequence ("\r\n"). Specifying
     * an empty string will turn off generation of line breaks when pretty
     * printing.
     *
     * @param newline representing the newline sequence when pretty printing.
     */
    public void setNewline(String newline)
    {
        this.newline = newline;
    }
    /**
     * A helper method. It writes out an element which contains only text.
     *
     * @param name String name of tag
     * @param text String of text to go inside the tag
     */
    public XmlWriter writeElementWithText(String name, String text) throws IOException
    {
        writeElement(name);
        writeText(text);
        return endElement();
    }
    /**
     * A helper method. It writes out empty entities.
     *
     * @param name String name of tag
     */
    public XmlWriter writeEmptyElement(String name) throws IOException
    {
        writeElement(name);
        return endElement();
    }
    /**
     * Begin to write out an element. Unlike the helper tags, this tag
     * will need to be ended with the endElement method.
     *
     * @param name String name of tag
     */
    public XmlWriter writeElement(String name) throws IOException
    {
        return openElement(name);
    }
    /**
     * Begin to output an element.
     *
     * @param name name of element.
     */
    private XmlWriter openElement(String name) throws IOException
    {
        boolean wasClosed = this.closed;
        closeOpeningTag();
        this.closed = false;
        if (this.pretty)
        {
            //   ! wasClosed separates adjacent opening tags by a newline.
            // this.wroteText makes sure an element embedded within the text of
            // its parent element begins on a new line, indented to the proper
            // level. This solves only part of the problem of pretty printing
            // entities which contain both text and child entities.
            if (!wasClosed || this.wroteText)
            {
                this.out.write(newline);
            }
            for (int i = 0; i < this.stack.size(); i++)
            {
                this.out.write(indent); // Indent opening tag to proper level
            }
        }
        this.out.write("<");
        this.out.write(name);
        stack.add(name);
        this.empty = true;
        this.wroteText = false;
        return this;
    }
    // close off the opening tag
    private void closeOpeningTag() throws IOException
    {
        if (!this.closed)
        {
            writeAttributes();
            this.closed = true;
            this.out.write(">");
        }
    }
    // write out all current attributes
    private void writeAttributes() throws IOException
    {
        if (this.attrs != null)
        {
            this.out.write(this.attrs.toString());
            this.attrs.setLength(0);
            this.empty = false;
        }
    }
    /**
     * Write an attribute out for the current element.
     * Any XML characters in the value are escaped.
     * Currently it does not actually throw the exception, but
     * the API is set that way for future changes.
     *
     * @param attr name of attribute.
     * @param value value of attribute.
     * @see #writeAttribute(String, String, boolean)
     */
    public XmlWriter writeAttribute(String attr, String value) throws IOException
    {
        return this.writeAttribute(attr, value, false);
    }
    /**
     * Write an attribute out for the current element.
     * Any XML characters in the value are escaped.
     * Currently it does not actually throw the exception, but
     * the API is set that way for future changes.
     *
     * @param attr name of attribute.
     * @param value value of attribute.
     * @param literally If the writer should be literally on the given value
     * which means that meta characters will also be preserved by escaping them. 
     * Mainly preserves newlines and tabs.
     */
    public XmlWriter writeAttribute(String attr, String value, boolean literally) throws IOException
    {

      if(this.wroteText==true) {
        throw new IllegalStateException("The text for the current element has already been written. Cannot add attributes afterwards.");
      }
        // maintain API
        if (false) throw new IOException();
        if (this.attrs == null)
        {
            this.attrs = new StringBuffer();
        }
        this.attrs.append(" ");
        this.attrs.append(attr);
        this.attrs.append("=\"");
        String val = escapeXml(value);
        if(literally){
          val = escapeMetaCharacters(val);
        }
        this.attrs.append(val);
        this.attrs.append("\"");
        return this;
    }
    /**
     * End the current element. This will throw an exception
     * if it is called when there is not a currently open
     * element.
     */
    public XmlWriter endElement() throws IOException
    {
        if (this.stack.empty())
        {
            throw new IOException("Called endElement too many times. ");
        }
        String name = (String)this.stack.pop();
        if (name != null)
        {
            if (this.empty)
            {
                writeAttributes();
                this.out.write("/>");
            }
            else
            {
                if (this.pretty && !this.wroteText)
                {
                    for (int i = 0; i < this.stack.size(); i++)
                    {
                        this.out.write(indent); // Indent closing tag to proper level
                    }
                }
                this.out.write("</");
                this.out.write(name);
                this.out.write(">");
            }
            if (this.pretty)
                this.out.write(newline); // Add a newline after the closing tag
            this.empty = false;
            this.closed = true;
            this.wroteText = false;
        }
        return this;
    }
    /**
     * Close this writer. It does not close the underlying
     * writer, but does throw an exception if there are
     * as yet unclosed tags.
     */
    public void close() throws IOException
    {
        this.out.flush();
        if (!this.stack.empty())
        {
            throw new IOException("Tags are not all closed. " +
                    "Possibly, " + this.stack.pop() + " is unclosed. ");
        }
    }
    /**
     * Output body text. Any XML characters are escaped.
     * @param text The text to be written
     * @return This writer
     * @throws IOException
     * @see #writeText(String, boolean)
     */
    public XmlWriter writeText(String text) throws IOException
    {
        return this.writeText(text, false);
    }
    /**
     * Output body text. Any XML characters are escaped.
     * @param text The text to be written
     * @param literally If the writer should be literally on the given value
     * which means that meta characters will also be preserved by escaping them. 
     * Mainly preserves newlines and tabs.
     * @return This writer
     * @throws IOException
     */
    public XmlWriter writeText(String text, boolean literally) throws IOException
    {
        closeOpeningTag();
        this.empty = false;
        this.wroteText = true;
        String val = escapeXml(text);
        if(literally){
          val = escapeMetaCharacters(val);
        }
        this.out.write(val);
        return this;
    }
    /**
     * Write out a chunk of CDATA. This helper method surrounds the
     * passed in data with the CDATA tag.
     *
     * @param cdata of CDATA text.
     */
    public XmlWriter writeCData(String cdata) throws IOException
    {
  
        closeOpeningTag();
        
        boolean hasAlreadyEnclosingCdata = cdata.startsWith(CDATA_START) && cdata.endsWith(CDATA_END);
        
        // There may already be CDATA sections inside the data.
        // But CDATA sections can"t be nested - can"t have ]]&gt; inside a CDATA section. 
        // (See http://www.w3.org/TR/REC-xml/#NT-CDStart in the W3C specs)
        // The solutions is to replace any occurrence of "]]&gt;" by "]]]]&gt;<![CDATA[>",
        // so that the top CDATA section is split into many valid CDATA sections (you
        // can look at the "]]]]&gt;" as if it was an escape sequence for "]]&gt;").
        if(!hasAlreadyEnclosingCdata) {
            cdata = cdata.replaceAll(CDATA_END, "]]]]&gt;<![CDATA[>");
        }
        
        this.empty = false;
        this.wroteText = true;
        if(!hasAlreadyEnclosingCdata)
            this.out.write(CDATA_START);
        this.out.write(cdata);
        if(!hasAlreadyEnclosingCdata)
            this.out.write(CDATA_END);
        return this;
    }
    /**
     * Write out a chunk of comment. This helper method surrounds the
     * passed in data with the XML comment tag.
     *
     * @param comment of text to comment.
     */
    public XmlWriter writeComment(String comment) throws IOException
    {
   
        writeChunk("<!-- " + comment + " -->");
        return this;
    }
    private void writeChunk(String data) throws IOException
    {
    
        closeOpeningTag();
        this.empty = false;
        if (this.pretty && !this.wroteText)
        {
            for (int i = 0; i < this.stack.size(); i++)
            {
                this.out.write(indent);
            }
        }
        this.out.write(data);
        if (this.pretty)
        {
            this.out.write(newline);
        }
    }
    // Two example methods. They should output the same XML:
    // <person name="fred" age="12"><phone>425343</phone><bob/></person>
    static public void main(String[] args) throws IOException
    {
    
        test1();
        test2();
    }
    static public void test1() throws IOException
    {
     
        Writer writer = new java.io.StringWriter();
        XmlWriter xmlwriter = new XmlWriter(writer);
        xmlwriter.writeElement("person").writeAttribute("name", "fred").writeAttribute("age", "12").writeElement("phone").writeText("4254343").endElement().writeElement("friends").writeElement("bob").endElement().writeElement("jim").endElement().endElement().endElement();
        xmlwriter.close();
        System.err.println(writer.toString());
    }
    static public void test2() throws IOException
    {
     
        Writer writer = new java.io.StringWriter();
        XmlWriter xmlwriter = new XmlWriter(writer);
        xmlwriter.writeComment("Example of XmlWriter running");
        xmlwriter.writeElement("person");
        xmlwriter.writeAttribute("name", "fred");
        xmlwriter.writeAttribute("age", "12");
        xmlwriter.writeElement("phone");
        xmlwriter.writeText("4254343");
        xmlwriter.endElement();
        xmlwriter.writeComment("Examples of empty tags");
//        xmlwriter.setDefaultNamespace("test");
        xmlwriter.writeElement("friends");
        xmlwriter.writeEmptyElement("bob");
        xmlwriter.writeEmptyElement("jim");
        xmlwriter.endElement();
        xmlwriter.writeElementWithText("foo", "This is an example.");
        xmlwriter.endElement();
        xmlwriter.close();
        System.err.println(writer.toString());
    }
    ////////////////////////////////////////////////////////////////////////////
    // Added for DbUnit
    /**
     * Escapes some meta characters like \n, \r that should be preserved in the XML
     * so that a reader will not filter out those symbols.
     * @param str The string to be escaped
     * @return The escaped string
     * @since 2.3.0
     */
    private String escapeMetaCharacters(String str)
    {
    
        // 2. Do additional escapes. See http://www.w3.org/TR/2004/REC-xml-20040204/#AVNormalize
        str = replace(str, "\n", "&#xA;"); // linefeed (LF)
        str = replace(str, "\r", "&#xD;"); // carriage return (CR)
        return str;
    }
    
    private String escapeXml(String str)
    {
     
        str = replace(str, "&", "&amp;");
        str = replace(str, "<", "&lt;");
        str = replace(str, ">", "&gt;");
        str = replace(str, "\"", "&quot;");
        str = replace(str, """, "&apos;");
        str = replace(str, "\t", "&#09;"); // tab
        return str;
    }
    private String replace(String value, String original, String replacement)
    {
  
        StringBuffer buffer = null;
        int startIndex = 0;
        int lastEndIndex = 0;
        for (; ;)
        {
            startIndex = value.indexOf(original, lastEndIndex);
            if (startIndex == -1)
            {
                if (buffer != null)
                {
                    buffer.append(value.substring(lastEndIndex));
                }
                break;
            }
            if (buffer == null)
            {
                buffer = new StringBuffer((int)(original.length() * 1.5));
            }
            buffer.append(value.substring(lastEndIndex, startIndex));
            buffer.append(replacement);
            lastEndIndex = startIndex + original.length();
        }
        return buffer == null ? value : buffer.toString();
    }
    private void setEncoding(String encoding)
    {
     
        if (encoding == null && out instanceof OutputStreamWriter)
            encoding = ((OutputStreamWriter)out).getEncoding();
        if (encoding != null)
        {
            encoding = encoding.toUpperCase();
            // Use official encoding names where we know them,
            // avoiding the Java-only names.  When using common
            // encodings where we can easily tell if characters
            // are out of range, we"ll escape out-of-range
            // characters using character refs for safety.
            // I _think_ these are all the main synonyms for these!
            if ("UTF8".equals(encoding))
            {
                encoding = "UTF-8";
            }
            else if ("US-ASCII".equals(encoding)
                    || "ASCII".equals(encoding))
            {
//                dangerMask = (short)0xff80;
                encoding = "US-ASCII";
            }
            else if ("ISO-8859-1".equals(encoding)
                    || "8859_1".equals(encoding)
                    || "ISO8859_1".equals(encoding))
            {
//                dangerMask = (short)0xff00;
                encoding = "ISO-8859-1";
            }
            else if ("UNICODE".equals(encoding)
                    || "UNICODE-BIG".equals(encoding)
                    || "UNICODE-LITTLE".equals(encoding))
            {
                encoding = "UTF-16";
                // TODO: UTF-16BE, UTF-16LE ... no BOM; what
                // release of JDK supports those Unicode names?
            }
//            if (dangerMask != 0)
//                stringBuf = new StringBuffer();
        }
        this.encoding = encoding;
    }

    /**
     * Resets the handler to write a new text document.
     *
     * @param writer XML text is written to this writer.
     * @param encoding if non-null, and an XML declaration is written,
     *  this is the name that will be used for the character encoding.
     *
     * @exception IllegalStateException if the current
     *  document hasn"t yet ended (i.e. the output stream {@link #out} is not null)
     */
    final public void setWriter(Writer writer, String encoding)
    {
   
        if (this.out != null)
            throw new IllegalStateException(
                    "can"t change stream in mid course");
        this.out = writer;
        if (this.out != null)
            setEncoding(encoding);
//        if (!(this.out instanceof BufferedWriter))
//            this.out = new BufferedWriter(this.out);
    }
    public XmlWriter writeDeclaration() throws IOException
    {
     
        if (this.encoding != null)
        {
            this.out.write("<?xml version="1.0"");
            this.out.write(" encoding="" + this.encoding + """);
            this.out.write("?>");
            this.out.write(this.newline);
        }
        return this;
    }
    public XmlWriter writeDoctype(String systemId, String publicId) throws IOException
    {
     
        if (systemId != null || publicId != null)
        {
            this.out.write("<!DOCTYPE dataset");
            if (systemId != null)
            {
                this.out.write(" SYSTEM \"");
                this.out.write(systemId);
                this.out.write("\"");
            }
            if (publicId != null)
            {
                this.out.write(" PUBLIC \"");
                this.out.write(publicId);
                this.out.write("\"");
            }
            this.out.write(">");
            this.out.write(this.newline);
        }
        return this;
    }
}





Use DOM L3 DOMBuilder, DOMBuilderFilter DOMWriter and other DOM L3 functionality to preparse, revalidate and safe document.

 
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import org.w3c.dom.DOMConfiguration;
import org.w3c.dom.DOMError;
import org.w3c.dom.DOMErrorHandler;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSParser;
import org.w3c.dom.ls.LSParserFilter;
import org.w3c.dom.ls.LSSerializer;
import org.w3c.dom.traversal.NodeFilter;
/**
 * This sample program illustrates how to use DOM L3 DOMBuilder,
 * DOMBuilderFilter DOMWriter and other DOM L3 functionality to preparse,
 * revalidate and safe document.
 */
public class DOM3 implements DOMErrorHandler, LSParserFilter {
  /** Default namespaces support (true). */
  protected static final boolean DEFAULT_NAMESPACES = true;
  /** Default validation support (false). */
  protected static final boolean DEFAULT_VALIDATION = false;
  /** Default Schema validation support (false). */
  protected static final boolean DEFAULT_SCHEMA_VALIDATION = false;
  static LSParser builder;
  public static void main(String[] argv) {
    if (argv.length == 0) {
      printUsage();
      System.exit(1);
    }
    try {
      // get DOM Implementation using DOM Registry
      System.setProperty(DOMImplementationRegistry.PROPERTY,
          "org.apache.xerces.dom.DOMXSImplementationSourceImpl");
      DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
      DOMImplementationLS impl = (DOMImplementationLS) registry.getDOMImplementation("LS");
      // create DOMBuilder
      builder = impl.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS, null);
      DOMConfiguration config = builder.getDomConfig();
      // create Error Handler
      DOMErrorHandler errorHandler = new DOM3();
      // create filter
      LSParserFilter filter = new DOM3();
      builder.setFilter(filter);
      // set error handler
      config.setParameter("error-handler", errorHandler);
      // set validation feature
      // config.setParameter("validate", Boolean.FALSE);
      config.setParameter("validate", Boolean.TRUE);
      // set schema language
      config.setParameter("schema-type", "http://www.w3.org/2001/XMLSchema");
      // config.setParameter("psvi",Boolean.TRUE);
      // config.setParameter("schema-type","http://www.w3.org/TR/REC-xml");
      // set schema location
      config.setParameter("schema-location", "personal.xsd");
      // parse document
      System.out.println("Parsing " + argv[0] + "...");
      Document doc = builder.parseURI(argv[0]);
      // set error handler on the Document
      config = doc.getDomConfig();
      config.setParameter("error-handler", errorHandler);
      // set validation feature
      config.setParameter("validate", Boolean.TRUE);
      config.setParameter("schema-type", "http://www.w3.org/2001/XMLSchema");
      // config.setParameter("schema-type","http://www.w3.org/TR/REC-xml");
      config.setParameter("schema-location", "data/personal.xsd");
      // remove comments from the document
      config.setParameter("comments", Boolean.FALSE);
      System.out.println("Normalizing document... ");
      doc.normalizeDocument();
      // create DOMWriter
      LSSerializer domWriter = impl.createLSSerializer();
      System.out.println("Serializing document... ");
      config = domWriter.getDomConfig();
      config.setParameter("xml-declaration", Boolean.FALSE);
      // config.setParameter("validate",errorHandler);
      // serialize document to standard output
      // domWriter.writeNode(System.out, doc);
      LSOutput dOut = impl.createLSOutput();
      dOut.setByteStream(System.out);
      domWriter.write(doc, dOut);
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
  private static void printUsage() {
    System.err.println("usage: java dom.DOM3 uri ...");
    System.err.println();
    System.err.println("NOTE: You can only validate DOM tree against XML Schemas.");
  } // printUsage()
  public boolean handleError(DOMError error) {
    short severity = error.getSeverity();
    if (severity == DOMError.SEVERITY_ERROR) {
      System.out.println("[dom3-error]: " + error.getMessage());
    }
    if (severity == DOMError.SEVERITY_WARNING) {
      System.out.println("[dom3-warning]: " + error.getMessage());
    }
    return true;
  }
  /**
   * @see org.w3c.dom.ls.LSParserFilter#acceptNode(Node)
   */
  public short acceptNode(Node enode) {
    return NodeFilter.FILTER_ACCEPT;
  }
  /**
   * @see org.w3c.dom.ls.LSParserFilter#getWhatToShow()
   */
  public int getWhatToShow() {
    return NodeFilter.SHOW_ELEMENT;
  }
  /**
   * @see org.w3c.dom.ls.LSParserFilter#startElement(Element)
   */
  public short startElement(Element elt) {
    return LSParserFilter.FILTER_ACCEPT;
  }
}





Write DOM out

  
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common
 * Development and Distribution License("CDDL") (collectively, the
 * "License"). You may not use this file except in compliance with the
 * License. You can obtain a copy of the License at
 * http://www.netbeans.org/cddl-gplv2.html
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 * specific language governing permissions and limitations under the
 * License.  When distributing the software, include this License Header
 * Notice in each file and include the License file at
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the
 * License Header, with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Contributor(s):
 *
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 * Microsystems, Inc. All Rights Reserved.
 *
 * If you wish your version of this file to be governed by only the CDDL
 * or only the GPL Version 2, indicate your decision by adding
 * "[Contributor] elects to include this software in this distribution
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 * single choice of license, a recipient has the option to distribute
 * your version of this file under either the CDDL, the GPL Version 2 or
 * to extend the choice of license to its licensees as provided above.
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 * Version 2 license, then the option applies only if the new code is
 * made subject to such option by the copyright holder.
 */
import java.io.IOException;
import java.io.OutputStream;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
/**
 * Utility class collecting library methods related to XML processing. Stolen
 * from nbbuild/antsrc and openide/.../xml.
 * 
 * @author Petr Kuzel, Jesse Glick
 */
public final class XMLUtil {
  public static void write(Document doc, OutputStream out) throws IOException {
    // XXX note that this may fail to write out namespaces correctly if the
    // document
    // is created with namespaces and no explicit prefixes; however no code in
    // this package is likely to be doing so
    try {
      Transformer t = TransformerFactory.newInstance().newTransformer();
      DocumentType dt = doc.getDoctype();
      if (dt != null) {
        String pub = dt.getPublicId();
        if (pub != null) {
          t.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, pub);
        }
        t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, dt.getSystemId());
      }
      t.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); // NOI18N
      t.setOutputProperty(OutputKeys.INDENT, "yes"); // NOI18N
      t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); // NOI18N
      Source source = new DOMSource(doc);
      Result result = new StreamResult(out);
      t.transform(source, result);
    } catch (Exception e) {
      throw (IOException) new IOException(e.toString()).initCause(e);
    } catch (TransformerFactoryConfigurationError e) {
      throw (IOException) new IOException(e.toString()).initCause(e);
    }
  }
}





write Xml DOM Node

  
import java.io.OutputStream;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Node;
/**
 * 
 * 
 * @author Costin Manolache
 */
public class Main {
  public static void writeXml(Node n, OutputStream os) throws TransformerException {
    TransformerFactory tf = TransformerFactory.newInstance();
    // identity
    Transformer t = tf.newTransformer();
    t.setOutputProperty(OutputKeys.INDENT, "yes");
    t.transform(new DOMSource(n), new StreamResult(os));
  }
}





Write Xml (Node n, OutputStream os)

 
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
import java.io.OutputStream;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Node;
/**
 * Few simple utils to read DOM. This is originally from the Jakarta Commons
 * Modeler.
 * 
 * @author Costin Manolache
 */
public class Utils {
  public static void writeXml(Node n, OutputStream os) throws TransformerException {
    TransformerFactory tf = TransformerFactory.newInstance();
    // identity
    Transformer t = tf.newTransformer();
    t.setOutputProperty(OutputKeys.INDENT, "yes");
    t.transform(new DOMSource(n), new StreamResult(os));
}
}





Writing a DOM Document to an XML File

     
import java.io.File;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
public class Main {
  public static void main(String[] argv) throws Exception {
    Document doc = null;
    String filename = "name.xml";
    Source source = new DOMSource(doc);
    File file = new File(filename);
    Result result = new StreamResult(file);
    Transformer xformer = TransformerFactory.newInstance().newTransformer();
    xformer.transform(source, result);
  }
}





XML Document Writer

    
/*
 * Copyright (c) 2000 David Flanagan.  All rights reserved.
 * This code is from the book Java Examples in a Nutshell, 2nd Edition.
 * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
 * You may study, use, and modify it for any non-commercial purpose.
 * You may distribute it non-commercially as long as you retain this notice.
 * For a commercial use license, or to purchase the book (recommended),
 * visit http://www.davidflanagan.ru/javaexamples2.
 */
import java.io.PrintWriter;
import org.w3c.dom.CDATASection;
import org.w3c.dom.rument;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
/**
 * Output a DOM Level 1 Document object to a java.io.PrintWriter as a simple XML
 * document. This class does not handle every type of DOM node, and it doesn"t
 * deal with all the details of XML like DTDs, character encodings and preserved
 * and ignored whitespace. However, it does output basic well-formed XML that
 * can be parsed by a non-validating parser.
 */
public class XMLDocumentWriter {
  PrintWriter out; // the stream to send output to
  /** Initialize the output stream */
  public XMLDocumentWriter(PrintWriter out) {
    this.out = out;
  }
  /** Close the output stream. */
  public void close() {
    out.close();
  }
  /** Output a DOM Node (such as a Document) to the output stream */
  public void write(Node node) {
    write(node, "");
  }
  /**
   * Output the specified DOM Node object, printing it using the specified
   * indentation string
   */
  public void write(Node node, String indent) {
    // The output depends on the type of the node
    switch (node.getNodeType()) {
    case Node.DOCUMENT_NODE: { // If its a Document node
      Document doc = (Document) node;
      out.println(indent + "<?xml version="1.0"?>"); // Output header
      Node child = doc.getFirstChild(); // Get the first node
      while (child != null) { // Loop "till no more nodes
        write(child, indent); // Output node
        child = child.getNextSibling(); // Get next node
      }
      break;
    }
    case Node.DOCUMENT_TYPE_NODE: { // It is a <!DOCTYPE> tag
      DocumentType doctype = (DocumentType) node;
      // Note that the DOM Level 1 does not give us information about
      // the the public or system ids of the doctype, so we can"t output
      // a complete <!DOCTYPE> tag here. We can do better with Level 2.
      out.println("<!DOCTYPE " + doctype.getName() + ">");
      break;
    }
    case Node.ELEMENT_NODE: { // Most nodes are Elements
      Element elt = (Element) node;
      out.print(indent + "<" + elt.getTagName()); // Begin start tag
      NamedNodeMap attrs = elt.getAttributes(); // Get attributes
      for (int i = 0; i < attrs.getLength(); i++) { // Loop through them
        Node a = attrs.item(i);
        out.print(" " + a.getNodeName() + "="" + // Print attr. name
            fixup(a.getNodeValue()) + """); // Print attr. value
      }
      out.println(">"); // Finish start tag
      String newindent = indent + "    "; // Increase indent
      Node child = elt.getFirstChild(); // Get child
      while (child != null) { // Loop
        write(child, newindent); // Output child
        child = child.getNextSibling(); // Get next child
      }
      out.println(indent + "</" + // Output end tag
          elt.getTagName() + ">");
      break;
    }
    case Node.TEXT_NODE: { // Plain text node
      Text textNode = (Text) node;
      String text = textNode.getData().trim(); // Strip off space
      if ((text != null) && text.length() > 0) // If non-empty
        out.println(indent + fixup(text)); // print text
      break;
    }
    case Node.PROCESSING_INSTRUCTION_NODE: { // Handle PI nodes
      ProcessingInstruction pi = (ProcessingInstruction) node;
      out.println(indent + "<?" + pi.getTarget() + " " + pi.getData()
          + "?>");
      break;
    }
    case Node.ENTITY_REFERENCE_NODE: { // Handle entities
      out.println(indent + "&" + node.getNodeName() + ";");
      break;
    }
    case Node.CDATA_SECTION_NODE: { // Output CDATA sections
      CDATASection cdata = (CDATASection) node;
      // Careful! Don"t put a CDATA section in the program itself!
      out.println(indent + "<" + "![CDATA[" + cdata.getData() + "]]"
          + ">");
      break;
    }
    case Node.ruMENT_NODE: { // Comments
      Comment c = (Comment) node;
      out.println(indent + "<!--" + c.getData() + "-->");
      break;
    }
    default: // Hopefully, this won"t happen too much!
      System.err.println("Ignoring node: " + node.getClass().getName());
      break;
    }
  }
  // This method replaces reserved characters with entities.
  String fixup(String s) {
    StringBuffer sb = new StringBuffer();
    int len = s.length();
    for (int i = 0; i < len; i++) {
      char c = s.charAt(i);
      switch (c) {
      default:
        sb.append(c);
        break;
      case "<":
        sb.append("&lt;");
        break;
      case ">":
        sb.append("&gt;");
        break;
      case "&":
        sb.append("&amp;");
        break;
      case """:
        sb.append("&quot;");
        break;
      case "\"":
        sb.append("&apos;");
        break;
      }
    }
    return sb.toString();
  }
}





XML Writer

   
/**
 * Copyright (c) 2004-2006 Regents of the University of California.
 * See "license-prefuse.txt" for licensing terms.
 */
import java.io.PrintWriter;
import java.util.ArrayList;
/**
 * Utility class for writing XML files. This class provides convenience
 * methods for creating XML documents, such as starting and ending
 * tags, and adding content and comments. This class handles correct
 * XML formatting and will properly escape text to ensure that the
 * text remains valid XML.
 * 
 * <p>To use this class, create a new instance with the desired
 * PrintWriter to write the XML to. Call the {@link #begin()} or
 * {@link #begin(String, int)} method when ready to start outputting
 * XML. Then use the provided methods to generate the XML file.
 * Finally, call either the {@link #finish()} or {@link #finish(String)}
 * methods to signal the completion of the file.</p>
 * 
 * @author 
 */
public class XMLWriter {
    
    private PrintWriter m_out;
    private int m_bias = 0;
    private int m_tab;
    private ArrayList m_tagStack = new ArrayList();
    
    /**
     * Create a new XMLWriter.
     * @param out the print writer to write the XML to
     */
    public XMLWriter(PrintWriter out) {
        this(out, 2);
    }
    /**
     * Create a new XMLWriter.
     * @param out the print writer to write the XML to
     * @param tabLength the number of spaces to use for each
     *  level of indentation in the XML file
     */
    public XMLWriter(PrintWriter out, int tabLength) {
        m_out = out;
        m_tab = 2;
    }
    
    /**
     * Print <em>unescaped</em> text into the XML file. To print
     * escaped text, use the {@link #content(String)} method instead.
     * @param s the text to print. This String will not be escaped.
     */
    public void print(String s) {
        m_out.print(s);
    }
    /**
     * Print <em>unescaped</em> text into the XML file, followed by
     * a newline. To print escaped text, use the {@link #content(String)}
     * method instead.
     * @param s the text to print. This String will not be escaped.
     */
    public void println(String s) {
        m_out.print(s);
        m_out.print("\n");
    }
    
    /**
     * Print a newline into the XML file.
     */
    public void println() {
        m_out.print("\n");
    }
    
    /**
     * Begin the XML document. This must be called before any other
     * formatting methods. This method prints an XML header into
     * the top of the output stream.
     */
    public void begin() {
        m_out.print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        println();
    }
    
    /**
     * Begin the XML document. This must be called before any other
     * formatting methods. This method prints an XML header into
     * the top of the output stream, plus additional header text
     * provided by the client
     * @param header header text to insert into the document
     * @param bias the spacing bias to use for all subsequent indenting
     */
    public void begin(String header, int bias) {
        begin();
        m_out.print(header);
        m_bias = bias;
    }
    
    /**
     * Print a comment in the XML document. The comment will be printed
     * according to the current spacing and followed by a newline.
     * @param comment the comment text
     */
    public void comment(String comment) {
        spacing();
        m_out.print("<!-- ");
        m_out.print(comment);
        m_out.print(" -->");
        println();
    }
    
    /**
     * Internal method for printing a tag with attributes.
     * @param tag the tag name
     * @param names the names of the attributes
     * @param values the values of the attributes
     * @param nattr the number of attributes
     * @param close true to close the tag, false to leave it
     * open and adjust the spacing
     */
    protected void tag(String tag, String[] names, String[] values,
            int nattr, boolean close)
    {
        spacing();
        m_out.print("<");
        m_out.print(tag);
        for ( int i=0; i<nattr; ++i ) {
            m_out.print(" ");
            m_out.print(names[i]);
            m_out.print("=");
            m_out.print("\"");
            escapeString(values[i]);
            m_out.print("\"");
        }
        if ( close ) m_out.print("/");
        m_out.print(">");
        println();
        
        if ( !close ) {
            m_tagStack.add(tag);
        }
    }
    
    /**
     * Print a closed tag with attributes. The tag will be followed by a
     * newline.
     * @param tag the tag name
     * @param names the names of the attributes
     * @param values the values of the attributes
     * @param nattr the number of attributes
     */
    public void tag(String tag, String[] names, String[] values, int nattr)
    {
        tag(tag, names, values, nattr, true);
    }
    
    /**
     * Print a start tag with attributes. The tag will be followed by a
     * newline, and the indentation level will be increased.
     * @param tag the tag name
     * @param names the names of the attributes
     * @param values the values of the attributes
     * @param nattr the number of attributes
     */
    public void start(String tag, String[] names, String[] values, int nattr)
    {
        tag(tag, names, values, nattr, false);
    }
    
    /**
     * Internal method for printing a tag with a single attribute.
     * @param tag the tag name
     * @param name the name of the attribute
     * @param value the value of the attribute
     * @param close true to close the tag, false to leave it
     * open and adjust the spacing
     */
    protected void tag(String tag, String name, String value, boolean close) {
        spacing();
        m_out.print("<");
        m_out.print(tag);
        m_out.print(" ");
        m_out.print(name);
        m_out.print("=");
        m_out.print("\"");
        escapeString(value);
        m_out.print("\"");
        if ( close ) m_out.print("/");
        m_out.print(">");
        println();
        
        if ( !close ) {
            m_tagStack.add(tag);
        }
    }
    
    /**
     * Print a closed tag with one attribute. The tag will be followed by a
     * newline.
     * @param tag the tag name
     * @param name the name of the attribute
     * @param value the value of the attribute
     */
    public void tag(String tag, String name, String value)
    {
        tag(tag, name, value, true);
    }
    
    /**
     * Print a start tag with one attribute. The tag will be followed by a
     * newline, and the indentation level will be increased.
     * @param tag the tag name
     * @param name the name of the attribute
     * @param value the value of the attribute
     */
    public void start(String tag, String name, String value)
    {
        tag(tag, name, value, false);
    }
    
    /**
     * Internal method for printing a tag with attributes.
     * @param tag the tag name
     * @param names the names of the attributes
     * @param values the values of the attributes
     * @param nattr the number of attributes
     * @param close true to close the tag, false to leave it
     * open and adjust the spacing
     */
    protected void tag(String tag, ArrayList names, ArrayList values,
            int nattr, boolean close)
    {
        spacing();
        m_out.print("<");
        m_out.print(tag);
        for ( int i=0; i<nattr; ++i ) {
            m_out.print(" ");
            m_out.print((String)names.get(i));
            m_out.print("=");
            m_out.print("\"");
            escapeString((String)values.get(i));
            m_out.print("\"");
        }
        if ( close ) m_out.print("/");
        m_out.print(">");
        println();
        
        if ( !close ) {
            m_tagStack.add(tag);
        }
    }
    
    /**
     * Print a closed tag with attributes. The tag will be followed by a
     * newline.
     * @param tag the tag name
     * @param names the names of the attributes
     * @param values the values of the attributes
     * @param nattr the number of attributes
     */
    public void tag(String tag, ArrayList names, ArrayList values, int nattr)
    {
        tag(tag, names, values, nattr, true);
    }
    
    /**
     * Print a start tag with attributes. The tag will be followed by a
     * newline, and the indentation level will be increased.
     * @param tag the tag name
     * @param names the names of the attributes
     * @param values the values of the attributes
     * @param nattr the number of attributes
     */
    public void start(String tag, ArrayList names, ArrayList values, int nattr)
    {
        tag(tag, names, values, nattr, false);
    }
    
    /**
     * Print a start tag without attributes. The tag will be followed by a
     * newline, and the indentation level will be increased.
     * @param tag the tag name
     */
    public void start(String tag) {
        tag(tag, (String[])null, null, 0, false);
    }
    /**
     * Close the most recently opened tag. The tag will be followed by a
     * newline, and the indentation level will be decreased.
     */
    public void end() {
        String tag = (String)m_tagStack.remove(m_tagStack.size()-1);
        spacing();
        m_out.print("<");
        m_out.print("/");
        m_out.print(tag);
        m_out.print(">");
        println();
    }
    
    /**
     * Print a new content tag with a single attribute, consisting of an
     * open tag, content text, and a closing tag, all on one line.
     * @param tag the tag name
     * @param name the name of the attribute
     * @param value the value of the attribute, this text will be escaped
     * @param content the text content, this text will be escaped
     */
    public void contentTag(String tag, String name, String value, 
                           String content)
    {
        spacing();
        m_out.print("<"); m_out.print(tag); m_out.print(" ");
        m_out.print(name); m_out.print("=");
        m_out.print("\""); escapeString(value); m_out.print("\"");
        m_out.print(">");    
        escapeString(content);
        m_out.print("<"); m_out.print("/"); m_out.print(tag); m_out.print(">");
        println();
    }
    
    /**
     * Print a new content tag with no attributes, consisting of an
     * open tag, content text, and a closing tag, all on one line.
     * @param tag the tag name
     * @param content the text content, this text will be escaped
     */
    public void contentTag(String tag, String content) {
        spacing();
        m_out.print("<"); m_out.print(tag); m_out.print(">");
        escapeString(content);
        m_out.print("<"); m_out.print("/"); m_out.print(tag); m_out.print(">");
        println();
    }
    
    /**
     * Print content text.
     * @param content the content text, this text will be escaped
     */
    public void content(String content) {
        escapeString(content);
    }
    
    /**
     * Finish the XML document.
     */
    public void finish() {
        m_bias = 0;
        m_out.flush();
    }
    
    /**
     * Finish the XML document, printing the given footer text at the
     * end of the document.
     * @param footer the footer text, this will not be escaped
     */
    public void finish(String footer) {
        m_bias = 0;
        m_out.print(footer);
        m_out.flush();
    }
    
    /**
     * Print the current spacing (determined by the indentation level)
     * into the document. This method is used by many of the other
     * formatting methods, and so should only need to be called in
     * the case of custom text printing outside the mechanisms
     * provided by this class.
     */
    public void spacing() {
        int len = m_bias + m_tagStack.size() * m_tab;
        for ( int i=0; i<len; ++i )
            m_out.print(" ");
    }
    
    // ------------------------------------------------------------------------
    // Escape Text
    
    // unicode ranges and valid/invalid characters
    private static final char   LOWER_RANGE = 0x20;
    private static final char   UPPER_RANGE = 0x7f;
    private static final char[] VALID_CHARS = { 0x9, 0xA, 0xD };
    
    private static final char[] INVALID = { "<", ">", """, "\"", "&" };
    private static final String[] VALID = 
        { "&lt;", "&gt;", "&quot;", "&apos;", "&amp;" };
    
    /**
     * Escape a string such that it is safe to use in an XML document.
     * @param str the string to escape
     */
    protected void escapeString(String str) {
        if ( str == null ) {
            m_out.print("null");
            return;
        }
        
        int len = str.length();
        for (int i = 0; i < len; ++i) {
            char c = str.charAt(i);
            
            if ( (c < LOWER_RANGE     && c != VALID_CHARS[0] && 
                  c != VALID_CHARS[1] && c != VALID_CHARS[2]) 
                 || (c > UPPER_RANGE) )
            {
                // character out of range, escape with character value
                m_out.print("&#");
                m_out.print(Integer.toString(c));
                m_out.print(";");
            } else {
                boolean valid = true;
                // check for invalid characters (e.g., "<", "&", etc)
                for (int j=INVALID.length-1; j >= 0; --j )
                {
                    if ( INVALID[j] == c) {
                        valid = false;
                        m_out.print(VALID[j]);
                        break;
                    }
                }
                // if character is valid, don"t escape
                if (valid) {
                    m_out.print(c);
                }
            }
        }
    }
    
} // end of class XMLWriter





XMLWriter helper class

 
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.IOException;
import java.io.Writer;
/**
 * XMLWriter helper class.
 *
 * @author 
 */
public class XMLWriter {

    // -------------------------------------------------------------- Constants

    /**
     * Opening tag.
     */
    public static final int OPENING = 0;

    /**
     * Closing tag.
     */
    public static final int CLOSING = 1;

    /**
     * Element with no content.
     */
    public static final int NO_CONTENT = 2;

    // ----------------------------------------------------- Instance Variables

    /**
     * Buffer.
     */
    protected StringBuffer buffer = new StringBuffer();

    /**
     * Writer.
     */
    protected Writer writer = null;

    // ----------------------------------------------------------- Constructors

    /**
     * Constructor.
     */
    public XMLWriter() {
    }

    /**
     * Constructor.
     */
    public XMLWriter(Writer writer) {
        this.writer = writer;
    }

    // --------------------------------------------------------- Public Methods

    /**
     * Retrieve generated XML.
     *
     * @return String containing the generated XML
     */
    public String toString() {
        return buffer.toString();
    }

    /**
     * Write property to the XML.
     *
     * @param namespace Namespace
     * @param namespaceInfo Namespace info
     * @param name Property name
     * @param value Property value
     */
    public void writeProperty(String namespace, String namespaceInfo,
                              String name, String value) {
        writeElement(namespace, namespaceInfo, name, OPENING);
        buffer.append(value);
        writeElement(namespace, namespaceInfo, name, CLOSING);
    }

    /**
     * Write property to the XML.
     *
     * @param namespace Namespace
     * @param name Property name
     * @param value Property value
     */
    public void writeProperty(String namespace, String name, String value) {
        writeElement(namespace, name, OPENING);
        buffer.append(value);
        writeElement(namespace, name, CLOSING);
    }

    /**
     * Write property to the XML.
     *
     * @param namespace Namespace
     * @param name Property name
     */
    public void writeProperty(String namespace, String name) {
        writeElement(namespace, name, NO_CONTENT);
    }

    /**
     * Write an element.
     *
     * @param name Element name
     * @param namespace Namespace abbreviation
     * @param type Element type
     */
    public void writeElement(String namespace, String name, int type) {
        writeElement(namespace, null, name, type);
    }

    /**
     * Write an element.
     *
     * @param namespace Namespace abbreviation
     * @param namespaceInfo Namespace info
     * @param name Element name
     * @param type Element type
     */
    public void writeElement(String namespace, String namespaceInfo,
                             String name, int type) {
        if ((namespace != null) && (namespace.length() > 0)) {
            switch (type) {
            case OPENING:
                if (namespaceInfo != null) {
                    buffer.append("<" + namespace + ":" + name + " xmlns:"
                                  + namespace + "=\""
                                  + namespaceInfo + "\">");
                } else {
                    buffer.append("<" + namespace + ":" + name + ">");
                }
                break;
            case CLOSING:
                buffer.append("</" + namespace + ":" + name + ">\n");
                break;
            case NO_CONTENT:
            default:
                if (namespaceInfo != null) {
                    buffer.append("<" + namespace + ":" + name + " xmlns:"
                                  + namespace + "=\""
                                  + namespaceInfo + "\"/>");
                } else {
                    buffer.append("<" + namespace + ":" + name + "/>");
                }
                break;
            }
        } else {
            switch (type) {
            case OPENING:
                buffer.append("<" + name + ">");
                break;
            case CLOSING:
                buffer.append("</" + name + ">\n");
                break;
            case NO_CONTENT:
            default:
                buffer.append("<" + name + "/>");
                break;
            }
        }
    }

    /**
     * Write text.
     *
     * @param text Text to append
     */
    public void writeText(String text) {
        buffer.append(text);
    }

    /**
     * Write data.
     *
     * @param data Data to append
     */
    public void writeData(String data) {
        buffer.append(data);
    }

    /**
     * Write XML Header.
     */
    public void writeXMLHeader() {
        buffer.append("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
    }

    /**
     * Send data and reinitializes buffer.
     */
    public void sendData()
        throws IOException {
        if (writer != null) {
            writer.write(buffer.toString());
            buffer = new StringBuffer();
        }
    }

}





XMLWriter.java - serialize an XML document

   
// XMLWriter.java - serialize an XML document.
// Written by David Megginson, david@megginson.ru
// and placed by him into the public domain.
// Extensively modified by John Cowan for TagSoup.
// TagSoup is licensed under the Apache License,
// Version 2.0.  You may obtain a copy of this license at
// http://www.apache.org/licenses/LICENSE-2.0 .  You may also have
// additional legal rights not granted by this license.
//
// TagSoup is distributed in the hope that it will be useful, but
// unless required by applicable law or agreed to in writing, TagSoup
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
// OF ANY KIND, either express or implied; not even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

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("foo");
 * </pre>
 *
 * <p>is equivalent to the regular SAX2 ContentHandler method</p>
 *
 * <pre>
 * w.startElement("", "foo", "", 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("greeting");
 * w.characters("Hello, world!");
 * w.endElement("greeting");
 * w.endDocument();
 * </pre>
 *
 * <p>The resulting document will look like this:</p>
 *
 * <pre>
 * &lt;?xml version="1.0" standalone="yes"?>
 *
 * &lt;greeting>Hello, world!&lt;/greeting>
 * </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("greeting", "Hello, world!");
 * 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("item", "1");
 * w.dataElement("item", "2");
 * w.dataElement("item", "3");
 * </pre>
 *
 * <p>you will end up with</p>
 *
 * <pre>
 * &lt;item>1&lt;/item>&lt;item>3&lt;/item>&lt;item>3&lt;/item>
 * </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("http://www.foo.ru/ns/", "foo");
 * w.endDocument();
 * </pre>
 *
 * <p>The resulting document will look like this:</p>
 *
 * <pre>
 * &lt;?xml version="1.0" standalone="yes"?>
 *
 * &lt;_NS1:foo xmlns:_NS1="http://www.foo.ru/ns/"/>
 * </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("http://www.foo.ru/ns/", "foo");
 * w.startDocument();
 * w.emptyElement("http://www.foo.ru/ns/", "foo");
 * w.endDocument();
 * </pre>
 *
 * <p>The resulting document will look like this:</p>
 *
 * <pre>
 * &lt;?xml version="1.0" standalone="yes"?>
 *
 * &lt;foo:foo xmlns:foo="http://www.foo.ru/ns/"/>
 * </pre>
 *
 * <p>The default Namespace simply uses an empty string as the prefix:</p>
 *
 * <pre>
 * w.setPrefix("http://www.foo.ru/ns/", "");
 * w.startDocument();
 * w.emptyElement("http://www.foo.ru/ns/", "foo");
 * w.endDocument();
 * </pre>
 *
 * <p>The resulting document will look like this:</p>
 *
 * <pre>
 * &lt;?xml version="1.0" standalone="yes"?>
 *
 * &lt;foo xmlns="http://www.foo.ru/ns/"/>
 * </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="1.0" standalone="yes"?>
 *
 * &lt;rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
 *  &lt;rdf:Description about="http://www.foo.ru/ids/books/12345">
 *   &lt;dc:title xmlns:dc="http://www.purl.org/dc/">A Dark Night&lt;/dc:title>
 *   &lt;dc:creator xmlns:dc="http://www.purl.org/dc/">Jane Smith&lt;/dc:title>
 *   &lt;dc:date xmlns:dc="http://www.purl.org/dc/">2000-09-09&lt;/dc:title>
 *  &lt;/rdf:Description>
 * &lt;/rdf:RDF>
 * </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("http://www.purl.org/dc/");
 * </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="1.0" standalone="yes"?>
 *
 * &lt;rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 *             xmlns:dc="http://www.purl.org/dc/">
 *  &lt;rdf:Description about="http://www.foo.ru/ids/books/12345">
 *   &lt;dc:title>A Dark Night&lt;/dc:title>
 *   &lt;dc:creator>Jane Smith&lt;/dc:title>
 *   &lt;dc:date>2000-09-09&lt;/dc:title>
 *  &lt;/rdf:Description>
 * &lt;/rdf:RDF>
 * </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.ru
 * @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");
            if (version == null) {
                write(" version=\"1.0\"");
            } else {
                write(" version=\"");
                write(version);
                write("\"");
            }
            if (outputEncoding != null && outputEncoding != "") {
                write(" encoding=\"");
                write(outputEncoding);
                write("\"");
            }
            if (standalone == null) {
                write(" standalone=\"yes\"?>\n");
            } else {
                write(" standalone=\"");
                write(standalone);
                write("\"");
            }
        }
        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();
  if (forceDTD && !hasOutputDTD) startDTD(localName == null ? qName : localName, "", "");
        write("<");
        writeName(uri, localName, qName, true);
        writeAttributes(atts);
        if (elementLevel == 1) {
            forceNSDecls();
        }
        writeNSDecls();
        write(">");
//  System.out.println("%%%% startElement [" + qName + "] htmlMode = " + htmlMode);
  if (htmlMode && (qName.equals("script") || qName.equals("style"))) {
                cdataElement = true;
//    System.out.println("%%%% CDATA element");
                }
        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 (!(htmlMode &&
            (uri.equals("http://www.w3.org/1999/xhtml") ||
    uri.equals("")) &&
            (qName.equals("area") || qName.equals("base") ||
            qName.equals("basefont") || qName.equals("br") ||
            qName.equals("col") || qName.equals("frame") ||
            qName.equals("hr") || qName.equals("img") ||
            qName.equals("input") || qName.equals("isindex") ||
            qName.equals("link") || qName.equals("meta") ||
            qName.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);
            if (htmlMode &&
                booleanAttribute(atts.getLocalName(i), atts.getQName(i), atts.getValue(i))) break;
            write("=\"");
            writeEsc(ch, 0, ch.length, true);
            write(""");
        }
    }

    private String[] booleans = {"checked", "compact", "declare", "defer",
                                 "disabled", "ismap", "multiple",
                                 "nohref", "noresize", "noshade",
                                 "nowrap", "readonly", "selected"};
    // Return true if the attribute is an HTML boolean from the above list.
    private boolean booleanAttribute (String localName, String qName, String value)
    {
        String name = localName;
        if (name == null) {
            int i = qName.indexOf(":");
            if (i != -1) name = qName.substring(i + 1, qName.length());
        }
        if (!name.equals(value)) return false;
        for (int j = 0; j < booleans.length; j++) {
            if (name.equals(booleans[j])) return true;
            }
        return false;
    }
    /**
     * 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:
                if (!unicodeMode && ch[i] > "\u007f") {
                    write("&#");
                    write(Integer.toString(ch[i]));
                    write(";");
                } else {
                    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 {
        if (name == null) return;               // can"t cope
  if (hasOutputDTD) return;   // only one DTD
  hasOutputDTD = true;
        write("<!DOCTYPE ");
        write(name);
        if (systemid == null) systemid = "";
  if (overrideSystem != null) systemid = overrideSystem;
        char sysquote = (systemid.indexOf(""") != -1) ? "\"": """;
  if (overridePublic != null) publicid = overridePublic;
        if (!(publicid == null || "".equals(publicid))) {
                char pubquote = (publicid.indexOf(""") != -1) ? "\"": """;
                write(" PUBLIC ");
                write(pubquote);
                write(publicid);
                write(pubquote);
                write(" ");
                }
        else {
                write(" SYSTEM ");
                }
        write(sysquote);
        write(systemid);
        write(sysquote);
        write(">\n");
        }
    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);
//  System.out.println("%%%% key = [" + key + "] value = [" + value +"]");
        if (key.equals(ENCODING)) {
            outputEncoding = value;
            unicodeMode = value.substring(0, 3).equalsIgnoreCase("utf");
//                System.out.println("%%%% unicodeMode = " + unicodeMode);
  }
  else if (key.equals(METHOD)) {
    htmlMode = value.equals("html");
  }
  else if (key.equals(DOCTYPE_PUBLIC)) {
    overridePublic = value;
    forceDTD = true;
    }
  else if (key.equals(DOCTYPE_SYSTEM)) {
    overrideSystem = value;
    forceDTD = true;
    }
  else if (key.equals(VERSION)) {
    version = value;
    }
  else if (key.equals(STANDALONE)) {
    standalone = value;
    }
//  System.out.println("%%%% htmlMode = " + htmlMode);
    }
    

    ////////////////////////////////////////////////////////////////////
    // 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";  // currently ignored
    public static final String MEDIA_TYPE = "media-type";  // currently ignored
    public static final String METHOD = "method";  // currently html or xml
    public static final String OMIT_XML_DECLARATION = "omit-xml-declaration";
    public static final String STANDALONE = "standalone";  // currently ignored
    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 unicodeMode = false;
    private String outputEncoding = "";
    private boolean htmlMode = false;
    private boolean forceDTD = false;
    private boolean hasOutputDTD = false;
    private String overridePublic = null;
    private String overrideSystem = null;
    private String version = null;
    private String standalone = null;
    private boolean cdataElement = false;
    
}
// end of XMLWriter.java