Found this while trying to embed Jelly into my app. The closing quote is not written for the xmlns attribute in certain cases.

Calvin
/*
 * Copyright 2001 (C) MetaStuff, Ltd. All Rights Reserved.
 *
 * This software is open source.
 * See the bottom of this file for the licence.
 *
 * $Id: XMLWriter.java,v 1.55 2002/11/13 11:40:39 jstrachan Exp $
 */

package org.dom4j.io;

import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import org.dom4j.Attribute;
import org.dom4j.CDATA;
import org.dom4j.CharacterData;
import org.dom4j.Comment;
import org.dom4j.DocumentType;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Entity;
import org.dom4j.Namespace;
import org.dom4j.Node;
import org.dom4j.ProcessingInstruction;
import org.dom4j.Text;

import org.dom4j.tree.NamespaceStack;

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.XMLFilterImpl;

/**<p><code>XMLWriter</code> takes a DOM4J tree and formats it to a
  * stream as XML.
  * It can also take SAX events too so can be used by SAX clients as this object
  * implements the {@link ContentHandler} and {@link LexicalHandler} interfaces.
  * as well. This formatter performs typical document
  * formatting.  The XML declaration and processing instructions are
  * always on their own lines. An {@link OutputFormat} object can be
  * used to define how whitespace is handled when printing and allows various
  * configuration options, such as to allow suppression of the XML declaration,
  * the encoding declaration or whether empty documents are collapsed.</p>
  *
  * <p> There are <code>write(...)</code> methods to print any of the
  * standard DOM4J classes, including <code>Document</code> and
  * <code>Element</code>, to either a <code>Writer</code> or an
  * <code>OutputStream</code>.  Warning: using your own
  * <code>Writer</code> may cause the writer's preferred character
  * encoding to be ignored.  If you use encodings other than UTF8, we
  * recommend using the method that takes an OutputStream instead.
  * </p>
  *
  * @author <a href="mailto:jstrachan@;apache.org">James Strachan</a>
  * @author Joseph Bowbeer
  * @version $Revision: 1.55 $
  */
public class XMLWriter extends XMLFilterImpl implements LexicalHandler {

    protected static final String[] LEXICAL_HANDLER_NAMES = {
        "http://xml.org/sax/properties/lexical-handler";,
        "http://xml.org/sax/handlers/LexicalHandler";
    };

    private static final boolean SUPPORT_PAD_TEXT = false;

    protected static final OutputFormat DEFAULT_FORMAT = new OutputFormat();

    /** Stores the last type of node written so algorithms can refer to the
      * previous node type */
    protected int lastOutputNodeType;

    /** The Writer used to output to */
    protected Writer writer;

    /** The Stack of namespaceStack written so far */
    private NamespaceStack namespaceStack = new NamespaceStack();

    /** The format used by this writer */
    private OutputFormat format;

    /** whether we should escape text */
    private boolean escapeText = true;
    /** The initial number of indentations (so you can print a whole
        document indented, if you like) **/
    private int indentLevel = 0;

    /** buffer used when escaping strings */
    private StringBuffer buffer = new StringBuffer();

    /** Whether a flush should occur after writing a document */
    private boolean autoFlush;

    /** Lexical handler we should delegate to */
    private LexicalHandler lexicalHandler;

    /** Whether comments should appear inside DTD declarations - defaults to false */
    private boolean showCommentsInDTDs;

    /** Is the writer curerntly inside a DTD definition? */
    private boolean inDTD;

    /** The namespaces used for the current element when consuming SAX events */
    private Map namespacesMap;

        /** 
         * what is the maximum allowed character code
         * such as 127 in US-ASCII (7 bit) or 255 in ISO-* (8 bit) 
         * or -1 to not escape any characters (other than the special XML characters 
like < > &) 
         */
        private int maximumAllowedCharacter;
        

    public XMLWriter(Writer writer) {
        this( writer, DEFAULT_FORMAT );
    }

    public XMLWriter(Writer writer, OutputFormat format) {
        this.writer = writer;
        this.format = format;
                namespaceStack.push(Namespace.NO_NAMESPACE);
    }

    public XMLWriter() {
        this.format = DEFAULT_FORMAT;
        this.writer = new BufferedWriter( new OutputStreamWriter( System.out ) );
        this.autoFlush = true;
                namespaceStack.push(Namespace.NO_NAMESPACE);
    }

    public XMLWriter(OutputStream out) throws UnsupportedEncodingException {
        this.format = DEFAULT_FORMAT;
        this.writer = createWriter(out, format.getEncoding());
        this.autoFlush = true;
                namespaceStack.push(Namespace.NO_NAMESPACE);
    }

    public XMLWriter(OutputStream out, OutputFormat format) throws 
UnsupportedEncodingException {
        this.format = format;
        this.writer = createWriter(out, format.getEncoding());
        this.autoFlush = true;
                namespaceStack.push(Namespace.NO_NAMESPACE);
    }

    public XMLWriter(OutputFormat format) throws UnsupportedEncodingException {
        this.format = format;
        this.writer = createWriter( System.out, format.getEncoding() );
        this.autoFlush = true;
                namespaceStack.push(Namespace.NO_NAMESPACE);
    }

    public void setWriter(Writer writer) {
        this.writer = writer;
        this.autoFlush = false;
    }

    public void setOutputStream(OutputStream out) throws UnsupportedEncodingException {
        this.writer = createWriter(out, format.getEncoding());
        this.autoFlush = true;
    }

    /**
     * @return true if text thats output should be escaped.
     * This is enabled by default. It could be disabled if
     * the output format is textual, like in XSLT where we can have
     * xml, html or text output.
     */
    public boolean isEscapeText() {
        return escapeText;
    }
    
    /**
     * Sets whether text output should be escaped or not.
     * This is enabled by default. It could be disabled if
     * the output format is textual, like in XSLT where we can have
     * xml, html or text output.
     */
    public void setEscapeText(boolean escapeText) {
        this.escapeText = escapeText;
    }
    

    /** Set the initial indentation level.  This can be used to output
      * a document (or, more likely, an element) starting at a given
      * indent level, so it's not always flush against the left margin.
      * Default: 0
      *
      * @param indentLevel the number of indents to start with
      */
    public void setIndentLevel(int indentLevel) {
        this.indentLevel = indentLevel;
    }

        /**
         * Returns the maximum allowed character code that should be allowed
         * unescaped which defaults to 127 in US-ASCII (7 bit) or 
         * 255 in ISO-* (8 bit).
         */
        public int getMaximumAllowedCharacter() {
                if (maximumAllowedCharacter == 0) {
                        maximumAllowedCharacter = defaultMaximumAllowedCharacter();
                }
                return maximumAllowedCharacter;
        }

        /**
         * Sets the maximum allowed character code that should be allowed
         * unescaped
         * such as 127 in US-ASCII (7 bit) or 255 in ISO-* (8 bit)
         * or -1 to not escape any characters (other than the special XML characters 
like < > &) 
         * 
         * If this is not explicitly set then it is defaulted from the encoding.
         *  
         * @param maximumAllowedCharacter The maximumAllowedCharacter to set
         */
        public void setMaximumAllowedCharacter(int maximumAllowedCharacter) {
                this.maximumAllowedCharacter = maximumAllowedCharacter;
        }



    /** Flushes the underlying Writer */
    public void flush() throws IOException {
        writer.flush();
    }

    /** Closes the underlying Writer */
    public void close() throws IOException {
        writer.close();
    }

    /** Writes the new line text to the underlying Writer */
    public void println() throws IOException {
        writer.write( format.getLineSeparator() );
    }

    /** Writes the given {@link Attribute}.
      *
      * @param attribute <code>Attribute</code> to output.
      */
    public void write(Attribute attribute) throws IOException {
        writeAttribute(attribute);

        if ( autoFlush ) {
            flush();
        }
    }


    /** <p>This will print the <code>Document</code> to the current Writer.</p>
     *
     * <p> Warning: using your own Writer may cause the writer's
     * preferred character encoding to be ignored.  If you use
     * encodings other than UTF8, we recommend using the method that
     * takes an OutputStream instead.  </p>
     *
     * <p>Note: as with all Writers, you may need to flush() yours
     * after this method returns.</p>
     *
     * @param doc <code>Document</code> to format.
     * @throws <code>IOException</code> - if there's any problem writing.
     **/
    public void write(Document doc) throws IOException {
        writeDeclaration();

        if (doc.getDocType() != null) {
            indent();
            writeDocType(doc.getDocType());
        }

        for ( int i = 0, size = doc.nodeCount(); i < size; i++ ) {
            Node node = doc.node(i);
            writeNode( node );
        }
        writePrintln();

        if ( autoFlush ) {
            flush();
        }
    }

    /** <p>Writes the <code>{@link Element}</code>, including
      * its <code>{@link Attribute}</code>s, and its value, and all
      * its content (child nodes) to the current Writer.</p>
      *
      * @param element <code>Element</code> to output.
      */
    public void write(Element element) throws IOException {
        writeElement(element);

        if ( autoFlush ) {
            flush();
        }
    }


    /** Writes the given {@link CDATA}.
      *
      * @param cdata <code>CDATA</code> to output.
      */
    public void write(CDATA cdata) throws IOException {
        writeCDATA( cdata.getText() );

        if ( autoFlush ) {
            flush();
        }
    }

    /** Writes the given {@link Comment}.
      *
      * @param comment <code>Comment</code> to output.
      */
    public void write(Comment comment) throws IOException {
        writeComment( comment.getText() );

        if ( autoFlush ) {
            flush();
        }
    }

    /** Writes the given {@link DocumentType}.
      *
      * @param docType <code>DocumentType</code> to output.
      */
    public void write(DocumentType docType) throws IOException {
        writeDocType(docType);

        if ( autoFlush ) {
            flush();
        }
    }


    /** Writes the given {@link Entity}.
      *
      * @param entity <code>Entity</code> to output.
      */
    public void write(Entity entity) throws IOException {
        writeEntity( entity );

        if ( autoFlush ) {
            flush();
        }
    }


    /** Writes the given {@link Namespace}.
      *
      * @param namespace <code>Namespace</code> to output.
      */
    public void write(Namespace namespace) throws IOException {
        writeNamespace(namespace);

        if ( autoFlush ) {
            flush();
        }
    }

    /** Writes the given {@link ProcessingInstruction}.
      *
      * @param processingInstruction <code>ProcessingInstruction</code> to output.
      */
    public void write(ProcessingInstruction processingInstruction) throws IOException {
        writeProcessingInstruction(processingInstruction);

        if ( autoFlush ) {
            flush();
        }
    }

    /** <p>Print out a {@link String}, Perfoms
      * the necessary entity escaping and whitespace stripping.</p>
      *
      * @param text is the text to output
      */
    public void write(String text) throws IOException {
        writeString(text);

        if ( autoFlush ) {
            flush();
        }
    }

    /** Writes the given {@link Text}.
      *
      * @param text <code>Text</code> to output.
      */
    public void write(Text text) throws IOException {
        writeString(text.getText());

        if ( autoFlush ) {
            flush();
        }
    }

    /** Writes the given {@link Node}.
      *
      * @param node <code>Node</code> to output.
      */
    public void write(Node node) throws IOException {
        writeNode(node);

        if ( autoFlush ) {
            flush();
        }
    }

    /** Writes the given object which should be a String, a Node or a List
      * of Nodes.
      *
      * @param object is the object to output.
      */
    public void write(Object object) throws IOException {
        if (object instanceof Node) {
            write((Node) object);
        }
        else if (object instanceof String) {
            write((String) object);
        }
        else if (object instanceof List) {
            List list = (List) object;
            for ( int i = 0, size = list.size(); i < size; i++ ) {
                write( list.get(i) );
            }
        }
        else if (object != null) {
            throw new IOException( "Invalid object: " + object );
        }
    }


    /** <p>Writes the opening tag of an {@link Element},
      * including its {@link Attribute}s
      * but without its content.</p>
      *
      * @param element <code>Element</code> to output.
      */
    public void writeOpen(Element element) throws IOException {
        writer.write("<");
        writer.write( element.getQualifiedName() );
        writeAttributes(element);
        writer.write(">");
    }

    /** <p>Writes the closing tag of an {@link Element}</p>
      *
      * @param element <code>Element</code> to output.
      */
    public void writeClose(Element element) throws IOException {
        writeClose( element.getQualifiedName() );
    }


    // XMLFilterImpl methods
    //-------------------------------------------------------------------------
    public void parse(InputSource source) throws IOException, SAXException {
        installLexicalHandler();
        super.parse(source);
    }


    public void setProperty(String name, Object value) throws 
SAXNotRecognizedException, SAXNotSupportedException {
        for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
            if (LEXICAL_HANDLER_NAMES[i].equals(name)) {
                setLexicalHandler((LexicalHandler) value);
                return;
            }
        }
        super.setProperty(name, value);
    }

    public Object getProperty(String name) throws SAXNotRecognizedException, 
SAXNotSupportedException {
        for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
            if (LEXICAL_HANDLER_NAMES[i].equals(name)) {
                return getLexicalHandler();
            }
        }
        return super.getProperty(name);
    }

    public void setLexicalHandler (LexicalHandler handler) {
        if (handler == null) {
            throw new NullPointerException("Null lexical handler");
        }
        else {
            this.lexicalHandler = handler;
        }
    }

    public LexicalHandler getLexicalHandler(){
        return lexicalHandler;
    }


    // ContentHandler interface
    //-------------------------------------------------------------------------
    public void setDocumentLocator(Locator locator) {
        super.setDocumentLocator(locator);
    }

    public void startDocument() throws SAXException {
        try {
            writeDeclaration();
            super.startDocument();
        }
        catch (IOException e) {
            handleException(e);
        }
    }

    public void endDocument() throws SAXException {
        super.endDocument();
    }

    public void startPrefixMapping(String prefix, String uri) throws SAXException {
        if ( namespacesMap == null ) {
            namespacesMap = new HashMap();
        }
        namespacesMap.put(prefix, uri);
        super.startPrefixMapping(prefix, uri);
    }

    public void endPrefixMapping(String prefix) throws SAXException {
        super.endPrefixMapping(prefix);
    }

    public void startElement(String namespaceURI, String localName, String qName, 
Attributes attributes) throws SAXException {
        try {
            writePrintln();
            indent();
            writer.write("<");
            writer.write(qName);
            writeNamespaces();
            writeAttributes( attributes );
            writer.write(">");
            ++indentLevel;
            lastOutputNodeType = Node.ELEMENT_NODE;

            super.startElement( namespaceURI, localName, qName, attributes );
        }
        catch (IOException e) {
            handleException(e);
        }
    }

    public void endElement(String namespaceURI, String localName, String qName) throws 
SAXException {
        try {
            --indentLevel;
            if ( lastOutputNodeType == Node.ELEMENT_NODE ) {
                writePrintln();
                indent();
            }

            // XXXX: need to determine this using a stack and checking for
            // content / children
            boolean hadContent = true;
            if ( hadContent ) {
                writeClose(qName);
            }
            else {
                writeEmptyElementClose(qName);
            }
            lastOutputNodeType = Node.ELEMENT_NODE;

            super.endElement( namespaceURI, localName, qName );
        }
        catch (IOException e) {
            handleException(e);
        }
    }

    public void characters(char[] ch, int start, int length) throws SAXException {
        try {
            write( new String( ch, start, length ) );

            super.characters(ch, start, length);
        }
        catch (IOException e) {
            handleException(e);
        }
    }

    public void ignorableWhitespace(char[] ch, int start, int length) throws 
SAXException {
        super.ignorableWhitespace(ch, start, length);
    }

    public void processingInstruction(String target, String data) throws SAXException {
        try {
            indent();
            writer.write("<?");
            writer.write(target);
            writer.write(" ");
            writer.write(data);
            writer.write("?>");
            writePrintln();
            lastOutputNodeType = Node.PROCESSING_INSTRUCTION_NODE;

            super.processingInstruction(target, data);
        }
        catch (IOException e) {
            handleException(e);
        }
    }



    // DTDHandler interface
    //-------------------------------------------------------------------------
    public void notationDecl(String name, String publicID, String systemID) throws 
SAXException {
        super.notationDecl(name, publicID, systemID);
    }

    public void unparsedEntityDecl(String name, String publicID, String systemID, 
String notationName) throws SAXException {
        super.unparsedEntityDecl(name, publicID, systemID, notationName);
    }


    // LexicalHandler interface
    //-------------------------------------------------------------------------
    public void startDTD(String name, String publicID, String systemID) throws 
SAXException {
        inDTD = true;
        try {
            writeDocType(name, publicID, systemID);
        }
        catch (IOException e) {
            handleException(e);
        }

        if (lexicalHandler != null) {
            lexicalHandler.startDTD(name, publicID, systemID);
        }
    }

    public void endDTD() throws SAXException {
        inDTD = false;
        if (lexicalHandler != null) {
            lexicalHandler.endDTD();
        }
    }

    public void startCDATA() throws SAXException {
        try {
            writer.write( "<![CDATA[" );
        }
        catch (IOException e) {
            handleException(e);
        }

        if (lexicalHandler != null) {
            lexicalHandler.startCDATA();
        }
    }

    public void endCDATA() throws SAXException {
        try {
            writer.write( "]]>" );
        }
        catch (IOException e) {
            handleException(e);
        }

        if (lexicalHandler != null) {
            lexicalHandler.endCDATA();
        }
    }

    public void startEntity(String name) throws SAXException {
        try {
            writeEntityRef(name);
        }
        catch (IOException e) {
            handleException(e);
        }

        if (lexicalHandler != null) {
            lexicalHandler.startEntity(name);
        }
    }

    public void endEntity(String name) throws SAXException {
        if (lexicalHandler != null) {
            lexicalHandler.endEntity(name);
        }
    }

    public void comment(char[] ch, int start, int length) throws SAXException {
        if ( showCommentsInDTDs || ! inDTD ) {
            try {
                writeComment( new String(ch, start, length) );
            }
            catch (IOException e) {
                handleException(e);
            }
        }

        if (lexicalHandler != null) {
            lexicalHandler.comment(ch, start, length);
        }
    }



    // Implementation methods
    //-------------------------------------------------------------------------
    protected void writeElement(Element element) throws IOException {
        int size = element.nodeCount();
        String qualifiedName = element.getQualifiedName();

        writePrintln();
        indent();

        writer.write("<");
        writer.write(qualifiedName);

        int previouslyDeclaredNamespaces = namespaceStack.size();
        Namespace ns = element.getNamespace();
        if (isNamespaceDeclaration( ns ) ) {
            namespaceStack.push(ns);
            writeNamespace(ns);
        }

        // Print out additional namespace declarations
        boolean textOnly = true;
        for ( int i = 0; i < size; i++ ) {
            Node node = element.node(i);
            if ( node instanceof Namespace ) {
                Namespace additional = (Namespace) node;
                if (isNamespaceDeclaration( additional ) ) {
                    namespaceStack.push(additional);
                    writeNamespace(additional);
                }
            }
            else if ( node instanceof Element) {
                textOnly = false;
            }
        }

        writeAttributes(element);

        lastOutputNodeType = Node.ELEMENT_NODE;

        if ( size <= 0 ) {
            writeEmptyElementClose(qualifiedName);
        }
        else {
            writer.write(">");
            if ( textOnly ) {
                // we have at least one text node so lets assume
                // that its non-empty
                writeElementContent(element);
            }
            else {
                // we know it's not null or empty from above
                ++indentLevel;

                writeElementContent(element);

                --indentLevel;

                writePrintln();
                indent();
            }
            writer.write("</");
            writer.write(qualifiedName);
            writer.write(">");
        }

        // remove declared namespaceStack from stack
        while (namespaceStack.size() > previouslyDeclaredNamespaces) {
            namespaceStack.pop();
        }

        lastOutputNodeType = Node.ELEMENT_NODE;
    }

    /** Outputs the content of the given element. If whitespace trimming is
     * enabled then all adjacent text nodes are appended together before
     * the whitespace trimming occurs to avoid problems with multiple
     * text nodes being created due to text content that spans parser buffers
     * in a SAX parser.
     */
    protected void writeElementContent(Element element) throws IOException {
        if (format.isTrimText()) {
            // concatenate adjacent text nodes together
            // so that whitespace trimming works properly
            Text lastTextNode = null;
            StringBuffer buffer = null;
            for ( int i = 0, size = element.nodeCount(); i < size; i++ ) {
                Node node = element.node(i);
                if ( node instanceof Text ) {
                    if ( lastTextNode == null ) {
                        lastTextNode = (Text) node;
                    }
                    else {
                        if (buffer == null) {
                            buffer = new StringBuffer( lastTextNode.getText() );
                        }
                      buffer.append( ((Text) node).getText() );
                    }
                }
                else {
                    if ( lastTextNode != null ) {
                        if ( buffer != null ) {
                            writeString( buffer.toString() );
                            buffer = null;
                        }
                        else {
                            writeString( lastTextNode.getText() );
                        }
                        lastTextNode = null;
                    }
                    writeNode(node);
                }
            }
            if ( lastTextNode != null ) {
                if ( buffer != null ) {
                    writeString( buffer.toString() );
                    buffer = null;
                }
                else {
                    writeString( lastTextNode.getText() );
                }
                lastTextNode = null;
            }
        }
        else {
            for ( int i = 0, size = element.nodeCount(); i < size; i++ ) {
                Node node = element.node(i);
                writeNode(node);
            }
        }
    }
    protected void writeCDATA(String text) throws IOException {
        writer.write( "<![CDATA[" );
        writer.write( text );
        writer.write( "]]>" );

        lastOutputNodeType = Node.CDATA_SECTION_NODE;
    }

    protected void writeDocType(DocumentType docType) throws IOException {
        if (docType != null) {
            docType.write( writer );
            //writeDocType( docType.getElementName(), docType.getPublicID(), 
docType.getSystemID() );
            writePrintln();
        }
    }


    protected void writeNamespace(Namespace namespace) throws IOException {
        if ( namespace != null ) {
            writeNamespace(namespace.getPrefix(), namespace.getURI());
        }
    }

    /**
     * Writes the SAX namepsaces
     */
    protected void writeNamespaces() throws IOException {
        if ( namespacesMap != null ) {
            for ( Iterator iter = namespacesMap.entrySet().iterator(); iter.hasNext(); 
) {
                Map.Entry entry = (Map.Entry) iter.next();
                String prefix = (String) entry.getKey();
                String uri = (String) entry.getValue();
                writeNamespace(prefix, uri);
            }
            namespacesMap = null;
        }
    }

    /**
     * Writes the SAX namepsaces
     */
    protected void writeNamespace(String prefix, String uri) throws IOException {
        if ( prefix != null && prefix.length() > 0 ) {
            writer.write(" xmlns:");
            writer.write(prefix);
            writer.write("=\"");
        }
        else {
            writer.write(" xmlns=\"");
        }
        writer.write(uri);
        writer.write("\"");
    }

    protected void writeProcessingInstruction(ProcessingInstruction 
processingInstruction) throws IOException {
        //indent();
        writer.write( "<?" );
        writer.write( processingInstruction.getName() );
        writer.write( " " );
        writer.write( processingInstruction.getText() );
        writer.write( "?>" );
        writePrintln();

        lastOutputNodeType = Node.PROCESSING_INSTRUCTION_NODE;
    }

    protected void writeString(String text) throws IOException {
        if ( text != null && text.length() > 0 ) {
            if ( escapeText ) {
                text = escapeElementEntities(text);
            }

            if ( SUPPORT_PAD_TEXT ) {
                if (lastOutputNodeType == Node.ELEMENT_NODE) {
                    String padText = getPadText();
                    if ( padText != null ) {
                        writer.write(padText);
                    }
                }
            }

            if (format.isTrimText()) {
                boolean first = true;
                StringTokenizer tokenizer = new StringTokenizer(text);
                while (tokenizer.hasMoreTokens()) {
                    String token = tokenizer.nextToken();
                    if ( first ) {
                        first = false;
                        if ( lastOutputNodeType == Node.TEXT_NODE ) {
                            writer.write(" ");
                        }
                    }
                    else {
                        writer.write(" ");
                    }
                    writer.write(token);
                    lastOutputNodeType = Node.TEXT_NODE;
                }
            }
            else {
                lastOutputNodeType = Node.TEXT_NODE;
                writer.write(text);
            }
        }
    }


    protected void writeNode(Node node) throws IOException {
        int nodeType = node.getNodeType();
        switch (nodeType) {
            case Node.ELEMENT_NODE:
                writeElement((Element) node);
                break;
            case Node.ATTRIBUTE_NODE:
                writeAttribute((Attribute) node);
                break;
            case Node.TEXT_NODE:
                writeString(node.getText());
                //write((Text) node);
                break;
            case Node.CDATA_SECTION_NODE:
                writeCDATA(node.getText());
                break;
            case Node.ENTITY_REFERENCE_NODE:
                writeEntity((Entity) node);
                break;
            case Node.PROCESSING_INSTRUCTION_NODE:
                writeProcessingInstruction((ProcessingInstruction) node);
                break;
            case Node.COMMENT_NODE:
                writeComment(node.getText());
                break;
            case Node.DOCUMENT_NODE:
                write((Document) node);
                break;
            case Node.DOCUMENT_TYPE_NODE:
                writeDocType((DocumentType) node);
                break;
            case Node.NAMESPACE_NODE:
                // Will be output with attributes
                //write((Namespace) node);
                break;
            default:
                throw new IOException( "Invalid node type: " + node );
        }
    }




    protected void installLexicalHandler() {
        XMLReader parent = getParent();
        if (parent == null) {
            throw new NullPointerException("No parent for filter");
        }
        // try to register for lexical events
        for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
            try {
                parent.setProperty(LEXICAL_HANDLER_NAMES[i], this);
                break;
            }
            catch (SAXNotRecognizedException ex) {
                // ignore
            }
            catch (SAXNotSupportedException ex) {
                // ignore
            }
        }
    }

    protected void writeDocType(String name, String publicID, String systemID) throws 
IOException {
        boolean hasPublic = false;

        writer.write("<!DOCTYPE ");
        writer.write(name);
        if ((publicID != null) && (!publicID.equals(""))) {
            writer.write(" PUBLIC \"");
            writer.write(publicID);
            writer.write("\"");
            hasPublic = true;
        }
        if ((systemID != null) && (!systemID.equals(""))) {
            if (!hasPublic) {
                writer.write(" SYSTEM");
            }
            writer.write(" \"");
            writer.write(systemID);
            writer.write("\"");
        }
        writer.write(">");
        writePrintln();
    }

    protected void writeEntity(Entity entity) throws IOException {
        writeEntityRef( entity.getName() );
    }

    protected void writeEntityRef(String name) throws IOException {
        writer.write( "&" );
        writer.write( name );
        writer.write( ";" );

        lastOutputNodeType = Node.ENTITY_REFERENCE_NODE;
    }

    protected void writeComment(String text) throws IOException {
        if (format.isNewlines()) {
            if ( lastOutputNodeType != Node.COMMENT_NODE ) {
                println();
            }
            indent();
        }
        writer.write( "<!--" );
        writer.write( text );
        writer.write( "-->" );

        writePrintln();

        lastOutputNodeType = Node.COMMENT_NODE;
    }

    /** Writes the attributes of the given element
      *
      */
    protected void writeAttributes( Element element ) throws IOException {

        // I do not yet handle the case where the same prefix maps to
        // two different URIs. For attributes on the same element
        // this is illegal; but as yet we don't throw an exception
        // if someone tries to do this
        for ( int i = 0, size = element.attributeCount(); i < size; i++ ) {
            Attribute attribute = element.attribute(i);
            Namespace ns = attribute.getNamespace();
            if (ns != null && ns != Namespace.NO_NAMESPACE && ns != 
Namespace.XML_NAMESPACE) {
                String prefix = ns.getPrefix();
                String uri = namespaceStack.getURI(prefix);
                if (!ns.getURI().equals(uri)) { // output a new namespace declaration
                    writeNamespace(ns);
                    namespaceStack.push(ns);
                }
            }

            writer.write(" ");
            writer.write(attribute.getQualifiedName());
            writer.write("=\"");
            writeEscapeAttributeEntities(attribute.getValue());
            writer.write("\"");
        }
    }

    protected void writeAttribute(Attribute attribute) throws IOException {
        writer.write(" ");
        writer.write(attribute.getQualifiedName());
        writer.write("=");

        writer.write("\"");

        writeEscapeAttributeEntities(attribute.getValue());

        writer.write("\"");
        lastOutputNodeType = Node.ATTRIBUTE_NODE;
    }

    protected void writeAttributes(Attributes attributes) throws IOException {
        for (int i = 0, size = attributes.getLength(); i < size; i++) {
            writeAttribute( attributes, i );
        }
    }

    protected void writeAttribute(Attributes attributes, int index) throws IOException 
{
        writer.write(" ");
        writer.write(attributes.getQName(index));
        writer.write("=\"");
        writeEscapeAttributeEntities(attributes.getValue(index));
        writer.write("\"");
    }



    protected void indent() throws IOException {
        String indent = format.getIndent();
        if ( indent != null && indent.length() > 0 ) {
            for ( int i = 0; i < indentLevel; i++ ) {
                writer.write(indent);
            }
        }
    }

    /**
     * <p>
     * This will print a new line only if the newlines flag was set to true
     * </p>
     *
     * @param out <code>Writer</code> to write to
     */
    protected void writePrintln() throws IOException  {
        if (format.isNewlines()) {
            writer.write( format.getLineSeparator() );
        }
    }

    /**
     * Get an OutputStreamWriter, use preferred encoding.
     */
    protected Writer createWriter(OutputStream outStream, String encoding) throws 
UnsupportedEncodingException {
        return new BufferedWriter(
            new OutputStreamWriter( outStream, encoding )
        );
    }

    /**
     * <p>
     * This will write the declaration to the given Writer.
     *   Assumes XML version 1.0 since we don't directly know.
     * </p>
     */
    protected void writeDeclaration() throws IOException {
        String encoding = format.getEncoding();

        // Only print of declaration is not suppressed
        if (! format.isSuppressDeclaration()) {
            // Assume 1.0 version
            if (encoding.equals("UTF8")) {
                writer.write("<?xml version=\"1.0\"");
                if (!format.isOmitEncoding()) {
                    writer.write(" encoding=\"UTF-8\"");
                }
                writer.write("?>");
            } else {
                writer.write("<?xml version=\"1.0\"");
                if (! format.isOmitEncoding()) {
                    writer.write(" encoding=\"" + encoding + "\"");
                }
                writer.write("?>");
            }
            println();
        }
    }

    protected void writeClose(String qualifiedName) throws IOException {
        writer.write("</");
        writer.write(qualifiedName);
        writer.write(">");
    }

    protected void writeEmptyElementClose(String qualifiedName) throws IOException {
        // Simply close up
        if (! format.isExpandEmptyElements()) {
            writer.write("/>");
        } else {
            writer.write("></");
            writer.write(qualifiedName);
            writer.write(">");
        }
    }

    protected boolean isExpandEmptyElements() {
        return format.isExpandEmptyElements();
    }


    /** This will take the pre-defined entities in XML 1.0 and
      * convert their character representation to the appropriate
      * entity reference, suitable for XML attributes.
      */
    protected String escapeElementEntities(String text) {
        char[] block = null;
        int i, last = 0, size = text.length();
        for ( i = 0; i < size; i++ ) {
            String entity = null;
            char c = text.charAt(i);
            switch( c ) {
                case '<' :
                    entity = "&lt;";
                    break;
                case '>' :
                    entity = "&gt;";
                    break;
                case '&' :
                    entity = "&amp;";
                    break;
                case '\t': case '\n': case '\r':
                    // don't encode standard whitespace characters
                    break;
                default:
                        if (c < 32 || shouldEncodeChar(c)) {
                        entity = "&#" + (int) c + ";";
                        }
                    break;
            }
            if (entity != null) {
                if ( block == null ) {
                    block = text.toCharArray();
                }
                buffer.append(block, last, i - last);
                buffer.append(entity);
                last = i + 1;
            }
        }
        if ( last == 0 ) {
            return text;
        }
        if ( last < size ) {
            if ( block == null ) {
                block = text.toCharArray();
            }
            buffer.append(block, last, i - last);
        }
        String answer = buffer.toString();
        buffer.setLength(0);
        return answer;
    }


    protected void writeEscapeAttributeEntities(String text) throws IOException {
        if ( text != null ) {
            String escapedText = escapeAttributeEntities( text );
            writer.write( escapedText );
        }
    }
    /** This will take the pre-defined entities in XML 1.0 and
      * convert their character representation to the appropriate
      * entity reference, suitable for XML attributes.
      */
    protected String escapeAttributeEntities(String text) {
        char[] block = null;
        int i, last = 0, size = text.length();
        for ( i = 0; i < size; i++ ) {
            String entity = null;
            char c = text.charAt(i);
            switch( c ) {
                case '<' :
                    entity = "&lt;";
                    break;
                case '>' :
                    entity = "&gt;";
                    break;
                case '\'' :
                    entity = "&apos;";
                    break;
                case '\"' :
                    entity = "&quot;";
                    break;
                case '&' :
                    entity = "&amp;";
                    break;
                case '\t': case '\n': case '\r':
                    // don't encode standard whitespace characters
                    break;
                default:
                        if (c < 32 || shouldEncodeChar(c)) {
                        entity = "&#" + (int) c + ";";
                        }
                    break;
            }
            if (entity != null) {
                if ( block == null ) {
                    block = text.toCharArray();
                }
                buffer.append(block, last, i - last);
                buffer.append(entity);
                last = i + 1;
            }
        }
        if ( last == 0 ) {
            return text;
        }
        if ( last < size ) {
            if ( block == null ) {
                block = text.toCharArray();
            }
            buffer.append(block, last, i - last);
        }
        String answer = buffer.toString();
        buffer.setLength(0);
        return answer;
    }

        /**
         * Should the given character be escaped. This depends on the
         * encoding of the document.
         * 
         * @return boolean
         */
        protected boolean shouldEncodeChar(char c) {
                int max = getMaximumAllowedCharacter();
                return max > 0 && c > max;
        }
        
        /**
         * Returns the maximum allowed character code that should be allowed
         * unescaped which defaults to 127 in US-ASCII (7 bit) or 
         * 255 in ISO-* (8 bit).
         */
        protected int defaultMaximumAllowedCharacter() {
                String encoding = format.getEncoding();
                if (encoding != null) {
                        if (encoding.equals("US-ASCII")) {
                                return 127;
                        }                       
                }
                // no encoding for things like ISO-*, UTF-8 or UTF-16
                return -1;
        }

    protected boolean isNamespaceDeclaration( Namespace ns ) {
        if (ns != null && ns != Namespace.XML_NAMESPACE) {
            String uri = ns.getURI();
            if ( uri != null ) {
                if ( ! namespaceStack.contains( ns ) ) {
                    return true;

                }
            }
        }
        return false;

    }

    protected void handleException(IOException e) throws SAXException {
        throw new SAXException(e);
    }

    protected String getPadText() {
        return null;
    }

    //Laramie Crocker 4/8/2002 10:38AM
    /** Lets subclasses get at the current format object, so they can call 
setTrimText, setNewLines, etc.
      * Put in to support the HTMLWriter, in the way
      *  that it pushes the current newline/trim state onto a stack and overrides
      *  the state within preformatted tags.
      */
    protected OutputFormat getOutputFormat() {
        return format;
    }
}




/*
 * Redistribution and use of this software and associated documentation
 * ("Software"), with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright
 *    statements and notices.  Redistributions must also contain a
 *    copy of this document.
 *
 * 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 name "DOM4J" must not be used to endorse or promote
 *    products derived from this Software without prior written
 *    permission of MetaStuff, Ltd.  For written permission,
 *    please contact [EMAIL PROTECTED]
 *
 * 4. Products derived from this Software may not be called "DOM4J"
 *    nor may "DOM4J" appear in their names without prior written
 *    permission of MetaStuff, Ltd. DOM4J is a registered
 *    trademark of MetaStuff, Ltd.
 *
 * 5. Due credit should be given to the DOM4J Project
 *    (http://dom4j.org/).
 *
 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS
 * ``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
 * METASTUFF, LTD. 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.
 *
 * Copyright 2001 (C) MetaStuff, Ltd. All Rights Reserved.
 *
 * $Id: XMLWriter.java,v 1.55 2002/11/13 11:40:39 jstrachan Exp $
 */
Index: XMLWriter.java
===================================================================
RCS file: /cvsroot/dom4j/dom4j/src/java/org/dom4j/io/XMLWriter.java,v
retrieving revision 1.55
diff -u -r1.55 XMLWriter.java
--- XMLWriter.java      13 Nov 2002 11:40:39 -0000      1.55
+++ XMLWriter.java      14 Nov 2002 22:22:55 -0000
@@ -872,17 +872,7 @@
 
     protected void writeNamespace(Namespace namespace) throws IOException {
         if ( namespace != null ) {
-            String prefix = namespace.getPrefix();
-            if ( prefix != null && prefix.length() > 0 ) {
-                writer.write(" xmlns:");
-                writer.write(prefix);
-                writer.write("=\"");
-            }
-            else {
-                writer.write(" xmlns=\"");
-            }
-            writer.write(namespace.getURI());
-            writer.write("\"");
+            writeNamespace(namespace.getPrefix(), namespace.getURI());
         }
     }
 
@@ -895,18 +885,26 @@
                 Map.Entry entry = (Map.Entry) iter.next();
                 String prefix = (String) entry.getKey();
                 String uri = (String) entry.getValue();
-                if ( prefix != null && prefix.length() > 0 ) {
-                    writer.write(" xmlns:");
-                    writer.write(prefix);
-                    writer.write("=\"");
-                }
-                else {
-                    writer.write(" xmlns=\"");
-                }
-                writer.write(uri);
+                writeNamespace(prefix, uri);
             }
             namespacesMap = null;
         }
+    }
+
+    /**
+     * Writes the SAX namepsaces
+     */
+    protected void writeNamespace(String prefix, String uri) throws IOException {
+        if ( prefix != null && prefix.length() > 0 ) {
+            writer.write(" xmlns:");
+            writer.write(prefix);
+            writer.write("=\"");
+        }
+        else {
+            writer.write(" xmlns=\"");
+        }
+        writer.write(uri);
+        writer.write("\"");
     }
 
     protected void writeProcessingInstruction(ProcessingInstruction 
processingInstruction) throws IOException {

Reply via email to