DOMWriter.java Source code

Java tutorial

Introduction

Here is the source code for DOMWriter.java

Source

/*
 * 
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999 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 acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Tomcat", 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 names without prior written
 *    permission of the Apache Group.
 *
 * 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.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Traverse a DOM tree in order to print a document that is parsed.
 *
 * @author Andy Clark, IBM
 * @author Thomas.Diesler@jboss.org
 */
@SuppressWarnings("unchecked")
public class DOMWriter {
    // Print writer
    private PrintWriter out;
    // True, if canonical output
    private boolean canonical;
    // True, if pretty printing should be used
    private boolean prettyprint;
    // True, if the XML declaration should be written
    private boolean writeXMLDeclaration;
    // True, if whitespace should be ignored
    private boolean ignoreWhitespace;
    // Explicit character set encoding
    private String charsetName;
    // indent for the pretty printer
    private int prettyIndent;
    // True, if the XML declaration has been written
    private boolean wroteXMLDeclaration;
    // The node that started the write
    private Node rootNode;
    // True if we want namespace completion
    private boolean completeNamespaces = true;
    // The current default namespace
    private String currentDefaultNamespace;

    public DOMWriter(Writer w) {
        this.out = new PrintWriter(w);
    }

    public DOMWriter(Writer w, String charsetName) {
        this.out = new PrintWriter(w);
        this.charsetName = charsetName;
        this.writeXMLDeclaration = true;
    }

    public DOMWriter(OutputStream stream) {
        try {
            this.out = new PrintWriter(new OutputStreamWriter(stream, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            // ignore, UTF-8 should be available
        }
    }

    public DOMWriter(OutputStream stream, String charsetName) {
        try {
            this.out = new PrintWriter(new OutputStreamWriter(stream, charsetName));
            this.charsetName = charsetName;
            this.writeXMLDeclaration = true;
        } catch (UnsupportedEncodingException e) {
            throw new IllegalArgumentException("Unsupported encoding: " + charsetName);
        }
    }

    /**
     * Print a node with explicit prettyprinting.
     * The defaults for all other DOMWriter properties apply.
     *
     */
    public static String printNode(Node node, boolean prettyprint) {
        StringWriter strw = new StringWriter();
        new DOMWriter(strw).setPrettyprint(prettyprint).print(node);
        return strw.toString();
    }

    public boolean isCanonical() {
        return canonical;
    }

    /**
     * Set wheter entities should appear in their canonical form.
     * The default is false.
     */
    public DOMWriter setCanonical(boolean canonical) {
        this.canonical = canonical;
        return this;
    }

    public boolean isIgnoreWhitespace() {
        return ignoreWhitespace;
    }

    /**
     * Set whether whitespace should be ignored.
     * The default is false.
     */
    public DOMWriter setIgnoreWhitespace(boolean ignoreWhitespace) {
        this.ignoreWhitespace = ignoreWhitespace;
        return this;
    }

    /**
     * Set wheter subelements should have their namespaces completed.
     * Setting this to false may lead to invalid XML fragments.
     * The default is true.
     */
    public DOMWriter setCompleteNamespaces(boolean complete) {
        this.completeNamespaces = complete;
        return this;
    }

    public boolean isPrettyprint() {
        return prettyprint;
    }

    /**
     * Set wheter element should be indented.
     * The default is false.
     */
    public DOMWriter setPrettyprint(boolean prettyprint) {
        this.prettyprint = prettyprint;
        return this;
    }

    public boolean isWriteXMLDeclaration() {
        return writeXMLDeclaration;
    }

    /**
     * Set wheter the XML declaration should be written.
     * The default is false.
     */
    public DOMWriter setWriteXMLDeclaration(boolean flag) {
        this.writeXMLDeclaration = flag;
        return this;
    }

    public void print(Node node) {
        if (prettyprint && ignoreWhitespace)
            throw new IllegalStateException("Cannot pretty print and ignore whitespace");

        rootNode = node;
        printInternal(node, false);
    }

    private void printInternal(Node node, boolean indentEndMarker) {
        // is there anything to do?
        if (node == null) {
            return;
        }

        // JBAS-2117 - Don't skip the DOCUMENT_NODE
        // if (node instanceof Document) node = ((Document)node).getDocumentElement();

        if (wroteXMLDeclaration == false && writeXMLDeclaration == true && canonical == false) {
            out.print("<?xml version='1.0'");
            if (charsetName != null)
                out.print(" encoding='" + charsetName + "'");

            out.print("?>");
            if (prettyprint)
                out.println();

            wroteXMLDeclaration = true;
        }

        int type = node.getNodeType();
        boolean hasChildNodes = node.getChildNodes().getLength() > 0;

        String nodeName = node.getNodeName();
        switch (type) {
        // print document
        case Node.DOCUMENT_NODE: {
            NodeList children = node.getChildNodes();
            for (int iChild = 0; iChild < children.getLength(); iChild++) {
                printInternal(children.item(iChild), false);
            }
            out.flush();
            break;
        }

        // print element with attributes
        case Node.ELEMENT_NODE: {
            Element element = (Element) node;
            if (prettyprint) {
                for (int i = 0; i < prettyIndent; i++) {
                    out.print(' ');
                }
                prettyIndent++;
            }

            out.print('<');
            out.print(nodeName);

            Map nsMap = new HashMap();
            String elPrefix = node.getPrefix();
            String elNamespaceURI = node.getNamespaceURI();
            if (elPrefix != null) {
                String nsURI = getNamespaceURI(elPrefix, element, rootNode);
                nsMap.put(elPrefix, nsURI);
            }

            Attr attrs[] = sortAttributes(node.getAttributes());
            for (int i = 0; i < attrs.length; i++) {
                Attr attr = attrs[i];
                String atPrefix = attr.getPrefix();
                String atName = attr.getNodeName();
                String atValue = normalize(attr.getNodeValue(), canonical);

                if (atName.equals("xmlns"))
                    currentDefaultNamespace = atValue;

                if (atPrefix != null && !atPrefix.equals("xmlns") && !atPrefix.equals("xml")) {
                    String nsURI = getNamespaceURI(atPrefix, element, rootNode);
                    nsMap.put(atPrefix, nsURI);
                    // xsi:type='ns1:SubType', xsi:type='xsd:string'
                    if (atName.equals(atPrefix + ":type") && atValue.indexOf(":") > 0) {
                        // xsi defined on the envelope
                        if (nsURI == null)
                            nsURI = getNamespaceURI(atPrefix, element, null);

                        if ("http://www.w3.org/2001/XMLSchema-instance".equals(nsURI)) {
                            String typePrefix = atValue.substring(0, atValue.indexOf(":"));
                            String typeURI = getNamespaceURI(typePrefix, element, rootNode);
                            nsMap.put(typePrefix, typeURI);
                        }
                    }
                }

                out.print(" " + atName + "='" + atValue + "'");
            }

            // Add namespace declaration for prefixes
            // that are defined further up the tree
            if (completeNamespaces) {
                Iterator itPrefix = nsMap.keySet().iterator();
                while (itPrefix.hasNext()) {
                    String prefix = (String) itPrefix.next();
                    String nsURI = (String) nsMap.get(prefix);
                    if (nsURI == null) {
                        nsURI = getNamespaceURI(prefix, element, null);
                        out.print(" xmlns:" + prefix + "='" + nsURI + "'");
                    }
                }
            }

            // The SAX ContentHandler will by default not add the namespace declaration
            // <Hello xmlns='http://somens'>World</Hello>
            if (elPrefix == null && elNamespaceURI != null) {
                String defaultNamespace = element.getAttribute("xmlns");
                if (defaultNamespace.length() == 0 && !elNamespaceURI.equals(currentDefaultNamespace)) {
                    out.print(" xmlns='" + elNamespaceURI + "'");
                    currentDefaultNamespace = elNamespaceURI;
                }
            }

            if (hasChildNodes) {
                out.print('>');
            }

            // Find out if the end marker is indented
            indentEndMarker = isEndMarkerIndented(node);

            if (indentEndMarker) {
                out.print('\n');
            }

            NodeList childNodes = node.getChildNodes();
            int len = childNodes.getLength();
            for (int i = 0; i < len; i++) {
                Node childNode = childNodes.item(i);
                printInternal(childNode, false);
            }
            break;
        }

        // handle entity reference nodes
        case Node.ENTITY_REFERENCE_NODE: {
            if (canonical) {
                NodeList children = node.getChildNodes();
                if (children != null) {
                    int len = children.getLength();
                    for (int i = 0; i < len; i++) {
                        printInternal(children.item(i), false);
                    }
                }
            } else {
                out.print('&');
                out.print(nodeName);
                out.print(';');
            }
            break;
        }

        // print cdata sections
        case Node.CDATA_SECTION_NODE: {
            if (canonical) {
                out.print(normalize(node.getNodeValue(), canonical));
            } else {
                out.print("<![CDATA[");
                out.print(node.getNodeValue());
                out.print("]]&gt;");
            }
            break;
        }

        // print text
        case Node.TEXT_NODE: {
            String text = normalize(node.getNodeValue(), canonical);
            if (text.trim().length() > 0) {
                out.print(text);
            } else if (prettyprint == false && ignoreWhitespace == false) {
                out.print(text);
            }
            break;
        }

        // print processing instruction
        case Node.PROCESSING_INSTRUCTION_NODE: {
            out.print("<?");
            out.print(nodeName);
            String data = node.getNodeValue();
            if (data != null && data.length() > 0) {
                out.print(' ');
                out.print(data);
            }
            out.print("?>");
            break;
        }

        // print comment
        case Node.COMMENT_NODE: {
            for (int i = 0; i < prettyIndent; i++) {
                out.print(' ');
            }

            out.print("<!--");
            String data = node.getNodeValue();
            if (data != null) {
                out.print(data);
            }
            out.print("-->");

            if (prettyprint) {
                out.print('\n');
            }

            break;
        }
        }

        if (type == Node.ELEMENT_NODE) {
            if (prettyprint)
                prettyIndent--;

            if (hasChildNodes == false) {
                out.print("/>");
            } else {
                if (indentEndMarker) {
                    for (int i = 0; i < prettyIndent; i++) {
                        out.print(' ');
                    }
                }

                out.print("</");
                out.print(nodeName);
                out.print('>');
            }

            if (prettyIndent > 0) {
                out.print('\n');
            }
        }
        out.flush();
    }

    private String getNamespaceURI(String prefix, Element element, Node stopNode) {
        Node parent = element.getParentNode();
        String nsURI = element.getAttribute("xmlns:" + prefix);
        if (nsURI.length() == 0 && element != stopNode && parent instanceof Element)
            return getNamespaceURI(prefix, (Element) parent, stopNode);

        return (nsURI.length() > 0 ? nsURI : null);
    }

    private boolean isEndMarkerIndented(Node node) {
        if (prettyprint) {
            NodeList childNodes = node.getChildNodes();
            int len = childNodes.getLength();
            for (int i = 0; i < len; i++) {
                Node children = childNodes.item(i);
                if (children.getNodeType() == Node.ELEMENT_NODE) {
                    return true;
                }
            }
        }
        return false;
    }

    /** Returns a sorted list of attributes. */
    private 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.compareTo(name) < 0) {
                    name = curName;
                    index = j;
                }
            }
            if (index != i) {
                Attr temp = array[i];
                array[i] = array[index];
                array[index] = temp;
            }
        }
        return (array);
    }

    /** Normalizes the given string. */
    public static String normalize(String s, boolean canonical) {
        StringBuffer str = new StringBuffer();

        int len = (s != null) ? s.length() : 0;
        for (int i = 0; i < len; i++) {
            char ch = s.charAt(i);
            switch (ch) {
            case '<': {
                str.append("&lt;");
                break;
            }
            case '>': {
                str.append("&gt;");
                break;
            }
            case '&': {
                str.append("&amp;");
                break;
            }
            case '"': {
                str.append("&quot;");
                break;
            }
            case '\'': {
                str.append("&apos;");
                break;
            }
            case '\r':
            case '\n': {
                if (canonical) {
                    str.append("&#");
                    str.append(Integer.toString(ch));
                    str.append(';');
                    break;
                }
                // else, default append char
            }
            default: {
                str.append(ch);
            }
            }
        }
        return (str.toString());
    }
}