How to convert XML Node object to String in Java?

June 27, 2019 No comments QA Java Node String Transformer DOM XML

Nowadays when everyone use JSON instead of a heavy XML for web communication this problem might be a little bit obsolete but, believe me, sooner or later you will be struggling with this one just like I was.

The main solution you will find in the internet is to use Transformer class that comes with Java in javax.xml.transform package.

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;

private static String convertNodeToString(Node node) {
     try {
        StringWriter writer = new StringWriter();

        Transformer trans = TransformerFactory.newInstance().newTransformer();
        trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        trans.setOutputProperty(OutputKeys.INDENT, "yes");
        trans.transform(new DOMSource(node), new StreamResult(writer));

        return writer.toString();
    } catch (TransformerException te) {
        te.printStackTrace();
    }

    return "";
}

This solution should work in most cases but it is not perfect. When you are processing whole XML document from root you will probably don't have any issue, however when you try to parse a single Node extracted from XML document you might get errors like:

javax.xml.transform.TransformerException: java.lang.RuntimeException: Namespace for prefix 'xxxxxx' has not been declared.
    at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:737)
    at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:343)

This is because Transformer requires XML document to be well-formed and all namespace prefixes, used in Node, must be declared.

To get rid of such errors I've created a function that can read Node element and generate XML string without checking namespaces. You can use it and adapt to your needs. Keep in mind it is very simple, parse only three type of nodes:

- Node.DOCUMENT_NODE,
- Node.ELEMENT_NODE,
- Node.TEXT_NODE.
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public static void getXMLString(Node node, boolean withoutNamespaces, StringBuffer buff, boolean endTag) {
    buff.append("<")
        .append(namespace(node.getNodeName(), withoutNamespaces));

    if (node.hasAttributes()) {
        buff.append(" ");

        NamedNodeMap attr = node.getAttributes();
        int attrLenth = attr.getLength();
        for (int i = 0; i < attrLenth; i++) {
            Node attrItem = attr.item(i);
            String name = namespace(attrItem.getNodeName(), withoutNamespaces);
            String value = attrItem.getNodeValue();

            buff.append(name)
                .append("=")
                .append("\"")
                .append(value)
                .append("\"");

            if (i < attrLenth - 1) {
                buff.append(" ");
            }
        }
    }

    if (node.hasChildNodes()) {
        buff.append(">");

        NodeList children = node.getChildNodes();
        int childrenCount = children.getLength();

        if (childrenCount == 1) {
            Node item = children.item(0);
            int itemType = item.getNodeType();
            if (itemType == Node.TEXT_NODE) {
                if (item.getNodeValue() == null) {
                    buff.append("/>");
                } else {
                    buff.append(item.getNodeValue());
                    buff.append("</")
                        .append(namespace(node.getNodeName(), withoutNamespaces))
                        .append(">");
                }

                endTag = false;
            }
        }

        for (int i = 0; i < childrenCount; i++) {
            Node item = children.item(i);
            int itemType = item.getNodeType();
            if (itemType == Node.DOCUMENT_NODE || itemType == Node.ELEMENT_NODE) {
                getXMLString(item, withoutNamespaces, buff, endTag);
            }
        }
    } else {
        if (node.getNodeValue() == null) {
            buff.append("/>");
        } else {
            buff.append(node.getNodeValue());
            buff.append("</")
                .append(namespace(node.getNodeName(), withoutNamespaces))
                .append(">");
        }

        endTag = false;
    }

    if (endTag) {
        buff.append("</")
            .append(namespace(node.getNodeName(), withoutNamespaces))
            .append(">");
    }
}

private static String namespace(String str, boolean withoutNamespace) {
    if (withoutNamespace && str.contains(":")) {
        return str.substring(str.indexOf(":") + 1);
    }

    return str;
}

Method parameters:

- Node node - (org.w3c.dom.Node),
- boolean withoutNamespaces - if true will remove all namespace prefixes,
- StringBuffer - output XML document,
- boolean endTag - should be set to true, this parameter is used in a recursive call.

This is how you can use it:

StringBuffer buff = new StringBuffer();
getXMLString(element, true, buff, true);
System.out.println(buff.toString());
{{ message }}

{{ 'Comments are closed.' | trans }}