org.kalypsodeegree.xml.XMLTools.java Source code

Java tutorial

Introduction

Here is the source code for org.kalypsodeegree.xml.XMLTools.java

Source

/** This file is part of kalypso/deegree.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * history:
 *
 * Files in this package are originally taken from deegree and modified here
 * to fit in kalypso. As goals of kalypso differ from that one in deegree
 * interface-compatibility to deegree is wanted but not retained always.
 *
 * If you intend to use this software in other ways than in kalypso
 * (e.g. OGC-web services), you should consider the latest version of deegree,
 * see http://www.deegree.org .
 *
 * all modifications are licensed as deegree,
 * original copyright:
 *
 * Copyright (C) 2001 by:
 * EXSE, Department of Geography, University of Bonn
 * http://www.giub.uni-bonn.de/exse/
 * lat/lon GmbH
 * http://www.lat-lon.de
 */

package org.kalypsodeegree.xml;

// JDK 1.3
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;

import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * XML Tools based on JAXP 1.1 for parsing documents and retrieving node values/node attributes.
 * <p>
 * 
 * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider </a>
 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a>
 * @version $Revision$
 */
public final class XMLTools {
    private static DocumentBuilderFactory DOCUMENT_FACTORY;
    static {
        try {
            DOCUMENT_FACTORY = DocumentBuilderFactory.newInstance();
            DOCUMENT_FACTORY.setNamespaceAware(true);
            DOCUMENT_FACTORY.setValidating(false);
            DOCUMENT_FACTORY.setFeature("http://apache.org/xml/features/allow-java-encodings", true);
        } catch (final Throwable e) {
            e.printStackTrace();
        }
    }

    /**
     * Checks if a given CDATA-value has to be escaped if it is used as a text value in an XML element. If the submitted
     * string contains a character that have to be escaped or if the string is made of more than 1500 characters it is
     * encapsulated into a CDATA-section. Returns a version that is safe to be used.
     * <p>
     * The method is just proofed for a UTF-8 character encoding.
     * <p>
     * 
     * @param cdata
     *          value to be used
     * @return the very same value (but escaped if necessary)
     */
    public static StringBuffer validateCDATA(final String cdata) {
        StringBuffer sb = null;
        if (cdata != null && (cdata.length() > 1000 || cdata.indexOf('<') >= 0 || cdata.indexOf('>') >= 0
                || cdata.indexOf('&') >= 0 || cdata.indexOf('"') >= 0 || cdata.indexOf("'") >= 0)) {
            sb = new StringBuffer(cdata.length() + 15);
            sb.append("<![CDATA[").append(cdata).append("]]>");
        } else {
            if (cdata != null) {
                sb = new StringBuffer(cdata);
            }
        }
        return sb;
    }

    /**
     * Returns the specified child element of the given elemen. If there are more than one with the same name, the first
     * one is returned.
     * <p>
     * 
     * @param name
     *          name of the child element
     * @param namespace
     *          namespace of the child element
     * @param node
     *          current element
     * @return the element or null, if it is missing
     * @throws XMLParsingException
     *           specified child element is missing and required is true
     */
    public static Element getRequiredChildByName(final String name, final String namespace, final Node node)
            throws XMLParsingException {
        final NodeList nl = node.getChildNodes();
        Element element = null;
        Element result = null;

        if (nl != null && nl.getLength() > 0) {
            for (int i = 0; i < nl.getLength(); i++) {
                if (nl.item(i) instanceof Element) {
                    element = (Element) nl.item(i);
                    final String s = element.getNamespaceURI();
                    if (s == null && namespace == null || namespace != null && namespace.equals(s)) {
                        if (element.getLocalName().equals(name)) {
                            result = element;
                            break;
                        }
                    }
                }
            }
        }

        if (result == null) {
            throw new XMLParsingException(
                    "Required child-element '" + name + "' of element '" + node.getNodeName() + "' is missing!");
        }

        return result;
    }

    /**
     * Returns the specified child element of the given elemen. If there are more than one with the same name, the first
     * one is returned.
     * <p>
     * 
     * @param name
     *          name of the child element
     * @param namespace
     *          namespace of the child element
     * @param node
     *          current element
     * @return the element or null, if it is missing
     */
    public static Element getChildByName(final String name, final String namespace, final Node node) {

        final NodeList nl = node.getChildNodes();

        if (nl != null && nl.getLength() > 0) {
            for (int i = 0; i < nl.getLength(); i++) {
                if (nl.item(i) instanceof Element) {
                    final Element element = (Element) nl.item(i);

                    final String s = element.getNamespaceURI();
                    if (s == null && namespace == null || namespace != null && namespace.equals(s)) {
                        if (element.getLocalName().equals(name))
                            return element;
                    }
                }
            }
        }

        return null;
    }

    /**
     * Returns the specified child elements of the given element.
     * <p>
     * 
     * @param name
     *          name of the child elements
     * @param namespace
     *          namespace of the child elements
     * @param node
     *          current element
     * @return the list of matching child elements
     */
    public static ElementList getChildElementsByName(final String name, final String namespace, final Node node) {

        final NodeList nl = node.getChildNodes();
        Element element = null;
        final ElementList elementList = new ElementList();

        if (nl != null && nl.getLength() > 0) {
            for (int i = 0; i < nl.getLength(); i++) {
                if (nl.item(i) instanceof Element) {
                    element = (Element) nl.item(i);

                    final String s = element.getNamespaceURI();

                    if (s == null && namespace == null || namespace != null && namespace.equals(s)) {
                        if (element.getLocalName().equals(name)) {
                            elementList.addElement(element);
                        }
                    }
                }
            }
        }
        return elementList;
    }

    /**
     * Returns the text contained in the specified element. The returned value is trimmed by calling the trim() method of
     * java.lang.String
     * <p>
     * 
     * @param node
     *          current element
     * @return the textual contents of the element or null, if it is missing
     */
    public static String getStringValue(final Node node) {
        if (node.getNodeType() == Node.TEXT_NODE)
            return node.getNodeValue();
        final NodeList children = node.getChildNodes();
        final StringBuffer sb = new StringBuffer(children.getLength() * 500);

        for (int i = 0; i < children.getLength(); i++) {
            if (children.item(i).getNodeType() == Node.TEXT_NODE
                    || children.item(i).getNodeType() == Node.CDATA_SECTION_NODE) {
                sb.append(children.item(i).getNodeValue());
            }
        }

        return sb.toString().trim();
    }

    /**
     * Returns the text contained in the specified child element of the given element.
     * <p>
     * 
     * @param name
     *          name of the child element
     * @param namespace
     *          namespace of the child element
     * @param node
     *          current element
     * @param defaultValue
     *          default value if element is missing
     * @return the textual contents of the element or the given default value, if missing
     */
    public static String getStringValue(final String name, final String namespace, final Node node,
            final String defaultValue) {
        final Element element = getChildByName(name, namespace, node);
        if (element == null)
            return defaultValue;

        final String value = getValue(element);
        // REMARK: empty string is not the same as no tag!
        if (value == null /* || value.equals( "" ) */)
            return defaultValue;

        return value;
    }

    /**
     * @param name
     * @param namespace
     * @param node
     */
    public static QName getQNameValue(final Element element) {
        if (element == null)
            return null;

        final String value = getValue(element);
        if (StringUtils.isBlank(value))
            return null;

        // hack to build a qName from string presentation (QName.toString())
        // this is needed as the XML-SchemaType is still xs:string and not xs:QName
        // according to Markus U. Mller (OGC SLD-Editor) this will change in the next version in SLD Standard
        final int pos = value.indexOf('}');
        if (value.startsWith("{") && pos > 0)
            return QName.valueOf(value);

        QName prefixedName = parsePrefixedQName(value);
        String prefix = prefixedName.getPrefix();
        final String namespaceURI = element.lookupNamespaceURI(prefix);
        return new QName(namespaceURI, prefixedName.getLocalPart(), prefix);
    }

    /**
     * Parses the submitted {@link String} as a {@link QName}.<br/>
     * The string must be of form <code>prefix:localPart</code>.
     * The reuturned qname alsways has namespace {@link XMLConstants#NULL_NS_URI}.
     */
    public static QName parsePrefixedQName(String prefixed) {
        String[] tmp = StringUtils.split(prefixed, ":", 2); //$NON-NLS-1$
        if (tmp.length == 2)
            return new QName(XMLConstants.NULL_NS_URI, tmp[1], tmp[0]);
        else
            return new QName(prefixed);
    }

    /**
     * Returns the text contained in the specified child element of the given element.
     * <p>
     * 
     * @param name
     *          name of the child element
     * @param namespace
     *          namespace of the child element
     * @param node
     *          current element
     * @return the textual contents of the element or null, if it is missing
     */
    public static String getRequiredStringValue(final String name, final String namespace, final Node node)
            throws XMLParsingException {
        final Element element = getRequiredChildByName(name, namespace, node);
        return getValue(element);
    }

    /**
     * Returns the numerical value of the text contained in the specified child element of the given element as a double
     * (if it denotes a double).
     * <p>
     * 
     * @param name
     *          name of the child element
     * @param namespace
     *          namespace of the child element
     * @param node
     *          current element
     * @param defaultValue
     *          value to be used if the specified element is missing or it's value is not numerical
     * @return the textual contents of the element as a double-value
     */
    public static double getDoubleValue(final String name, final String namespace, final Node node,
            final double defaultValue) {
        double value = defaultValue;
        final String textValue = getStringValue(name, namespace, node, null);

        if (textValue != null) {
            try {
                value = Double.parseDouble(textValue);
            } catch (final NumberFormatException e) {
                e.printStackTrace();
            }
        }

        return value;
    }

    /**
     * Returns the numerical value of the text contained in the specified child element of the given element as a double
     * (if it denotes a double).
     * <p>
     * 
     * @param name
     *          name of the child element
     * @param namespace
     *          namespace of the child element
     * @param node
     *          current element
     * @return the textual contents of the element as a double-value
     * @throws XMLParsingException
     *           specified child element is missing or the contained text does not denote a double value
     */
    public static double getRequiredDoubleValue(final String name, final String namespace, final Node node)
            throws XMLParsingException {
        double value;
        final String textValue = getRequiredStringValue(name, namespace, node);

        try {
            value = Double.parseDouble(textValue);
        } catch (final NumberFormatException e) {
            throw new XMLParsingException(
                    "Value ('" + textValue + "') of element '" + name + "' does not denote a valid double value.");
        }
        return value;
    }

    /**
     * Returns the value of the specified node attribute or null if it is missing.
     * <p>
     * 
     * @param name
     *          (local) name of attribute
     * @param node
     *          current element
     * @return the textual contents of the attribute or null
     */
    public static String getAttrValue(final String name, final Node node) {
        String value = null;
        final NamedNodeMap atts = node.getAttributes();

        if (atts != null) {
            final Attr attribute = (Attr) atts.getNamedItem(name);

            if (attribute != null) {
                value = attribute.getValue();
            }
        }
        return value;
    }

    /**
     * Returns the value of the specified node attribute.
     * <p>
     * 
     * @param name
     *          (local) name of attribute
     * @param node
     *          current element
     * @return the textual contents of the attribute
     * @throws XMLParsingException
     *           if specified attribute is missing
     */
    public static String getRequiredAttrValue(final String name, final Node node) throws XMLParsingException {
        String value = null;
        final NamedNodeMap atts = node.getAttributes();

        if (atts != null) {
            final Attr attribute = (Attr) atts.getNamedItem(name);

            if (attribute != null) {
                value = attribute.getValue();
            }
        }
        if (value == null) {
            throw new XMLParsingException(
                    "Required attribute '" + name + "' of element '" + node.getNodeName() + "' is missing.");
        }
        return value;
    }

    /**
     * Returns the value of the specified node attribute. // * FIXME: Due to apparent bugs in getNamedItemNS (name,
     * namespace), // * when used to find attribute nodes, the current implementation // * uses a workaround.
     * <p>
     * 
     * @param name
     *          (local) name of attribute
     * @param namespace
     *          namespace of attribute
     * @param node
     *          current element
     * @return the textual contents of the attribute
     * @throws XMLParsingException
     *           if specified attribute is missing
     */
    public static String getRequiredAttrValue(final String name, final String namespace, final Node node)
            throws XMLParsingException {
        String value = null;

        final NamedNodeMap atts = node.getAttributes();

        if (atts != null) {
            final Attr attribute = (Attr) atts.getNamedItemNS(namespace, name);

            if (attribute != null) {
                value = attribute.getValue();
            }
        }

        // for (int i = 0; i < atts.getLength (); i++) {
        // Node n = atts.item (i);
        // System.out.println ("Nodename: " + toLocalName (n.getNodeName ()));
        // System.out.println ("NamespaceURI: " + n.getNamespaceURI ());
        // if (n.getNamespaceURI ().equals (namespace) && toLocalName
        // (n.getNodeName
        // ()).equals (name)) {
        // System.out.println ("Me is here!");
        // value = ((Attr) n).getValue ();
        // break;
        // }
        // }

        if (value == null) {
            throw new XMLParsingException("Required attribute '" + namespace + ":" + name + "' of element '"
                    + node.getNodeName() + "' is missing.");
        }
        return value;
    }

    /**
     * creates a new and empty dom document
     */
    public static Document create() {
        try {
            final DocumentBuilder builder = DOCUMENT_FACTORY.newDocumentBuilder();
            return builder.newDocument();
        } catch (final ParserConfigurationException ex) {
            System.out.println(ex);
            return null;
        }
    }

    /**
     * Returns the attribute value of the given node.
     */
    public static String getAttrValue(final Node node, final String attrName) {
        // get attr name and dtype
        final NamedNodeMap atts = node.getAttributes();

        if (atts == null) {
            return null;
        }

        final Attr a = (Attr) atts.getNamedItem(attrName);

        if (a != null) {
            return a.getValue();
        }

        return null;
    }

    /**
     * Returns the attribute value of the given node.
     */
    public static String getAttrValue(final Node node, final String namespace, final String attrName) {
        // get attr name and dtype
        final NamedNodeMap atts = node.getAttributes();

        if (atts == null) {
            return null;
        }

        final Attr a = (Attr) atts.getNamedItemNS(namespace, attrName);

        if (a != null) {
            return a.getValue();
        }

        return null;
    }

    // FIXME: file encoding?!
    public static Document parse(final IFile file) throws IOException, SAXException, CoreException {
        InputStream stream = null;

        try {
            stream = new BufferedInputStream(file.getContents());
            final Document doc = parse(stream);
            stream.close();
            return doc;
        } finally {
            IOUtils.closeQuietly(stream);
        }
    }

    /**
     * Parses a XML document and returns a DOM object.
     * 
     * @param fileName
     *          the filename of the XML file to be parsed
     * @return a DOM object
     * @throws IOException
     * @throws SAXException
     */
    public static Document parse(final String fileName) throws IOException, SAXException {
        final Reader reader = new InputStreamReader(new FileInputStream(fileName));
        return parse(reader);
    }

    /**
     * @param resource
     * @return dom from url
     * @throws IOException
     * @throws SAXException
     */
    public static Document parse(final URL resource) throws IOException, SAXException {
        InputStream stream = null;

        try {
            stream = resource.openStream();
            final Document doc = parse(stream);
            stream.close();
            return doc;
        } finally {
            IOUtils.closeQuietly(stream);
        }
    }

    public static Document parse(final File file) throws SAXException, IOException {
        InputStream stream = null;

        try {
            stream = new BufferedInputStream(new FileInputStream(file));
            final Document doc = parse(stream);
            stream.close();
            return doc;
        } finally {
            IOUtils.closeQuietly(stream);
        }
    }

    /**
     * Parses a XML document and returns a DOM object.
     * 
     * @param reader
     *          accessing the resource to parse
     * @return a DOM object
     * @throws IOException
     * @throws SAXException
     * @deprecated Probably code which uses this method is not safe for two reasons: the reader only gets closed if no
     *             exception is thrown, second probably the charset is not correctly set. Use {@link #parse(InputStream)} instead, charset encoding is set inside the xml-file.
     */
    @Deprecated
    public static Document parse(final Reader reader) throws IOException, SAXException {
        javax.xml.parsers.DocumentBuilder parser = null;

        try {
            parser = DOCUMENT_FACTORY.newDocumentBuilder();
        } catch (final ParserConfigurationException ex) {
            ex.printStackTrace();
            throw new IOException("Unable to initialize DocumentBuilder: " + ex.getMessage());
        }

        final Document doc = parser.parse(new InputSource(reader));
        reader.close();

        return doc;
    }

    /**
     * Parses a XML document and returns a DOM object.
     * <p>
     * The stream is NOT closed by this method.
     * 
     * @param reader
     *          accessing the resource to parse
     * @return a DOM object
     * @throws IOException
     * @throws SAXException
     */
    public static Document parse(final InputStream is) throws IOException, SAXException {
        try {
            final DocumentBuilder parser = DOCUMENT_FACTORY.newDocumentBuilder();
            return parser.parse(new InputSource(is));
        } catch (final ParserConfigurationException ex) {
            ex.printStackTrace();

            throw new IOException("Unable to initialize DocumentBuilder: " + ex.getMessage(), ex);
        }
    }

    /**
     * copies one node to another node (of a different dom document).
     */
    public static Node copyNode(final Node source, final Node dest) {
        // Debug.debugMethodBegin( "XMLTools", "copyNode" );
        if (source.getNodeType() == Node.TEXT_NODE) {
            final Text tn = dest.getOwnerDocument().createTextNode(source.getNodeValue());
            return tn;
        }

        final NamedNodeMap attr = source.getAttributes();

        if (attr != null) {
            for (int i = 0; i < attr.getLength(); i++) {
                ((Element) dest).setAttribute(attr.item(i).getNodeName(), attr.item(i).getNodeValue());
            }
        }

        final NodeList list = source.getChildNodes();

        for (int i = 0; i < list.getLength(); i++) {
            if (!(list.item(i) instanceof Text)) {
                final Element en = dest.getOwnerDocument().createElementNS(list.item(i).getNamespaceURI(),
                        list.item(i).getNodeName());

                if (list.item(i).getNodeValue() != null) {
                    en.setNodeValue(list.item(i).getNodeValue());
                }

                final Node n = copyNode(list.item(i), en);
                dest.appendChild(n);
            } else if (list.item(i) instanceof CDATASection) {
                final CDATASection cd = dest.getOwnerDocument().createCDATASection(list.item(i).getNodeValue());
                dest.appendChild(cd);
            } else {
                final Text tn = dest.getOwnerDocument().createTextNode(list.item(i).getNodeValue());
                dest.appendChild(tn);
            }
        }

        // Debug.debugMethodEnd();
        return dest;
    }

    /**
     * inserts a node into a dom element (of a different dom document)
     */
    public static Node insertNodeInto(final Node source, final Node dest) {

        Document dDoc = null;
        final Document sDoc = source.getOwnerDocument();

        if (dest instanceof Document) {
            dDoc = (Document) dest;
        } else {
            dDoc = dest.getOwnerDocument();
        }

        if (dDoc.equals(sDoc)) {
            dest.appendChild(source);
        } else {
            final Element element = dDoc.createElementNS(source.getNamespaceURI(), source.getNodeName());
            dest.appendChild(element);

            copyNode(source, element);
        }

        return dest;
    }

    /**
     * returns the first child element of the submitted node
     */
    public static Element getFirstElement(final Node node) {

        final NodeList nl = node.getChildNodes();
        Element element = null;

        if (nl != null && nl.getLength() > 0) {
            for (int i = 0; i < nl.getLength(); i++) {
                if (nl.item(i) instanceof Element) {
                    element = (Element) nl.item(i);

                    break;
                }
            }
        }

        return element;
    }

    /**
     * removes all direct child nodes of the submitted node with the also submitted name
     */
    public static Node removeNamedChildNodes(final Node node, final String nodeName) {

        final NodeList nl = node.getChildNodes();

        if (nl != null && nl.getLength() > 0) {
            for (int i = 0; i < nl.getLength(); i++) {
                if (nl.item(i).getNodeName().equals(nodeName)) {
                    node.removeChild(nl.item(i));
                }
            }
        }

        return node;
    }

    /**
     * returns the first child element of the submitted node
     */
    public static Element getNamedChild(final Node node, final String name) {
        // Debug.debugMethodBegin( "XMLTools", "getNamedChild" );
        final NodeList nl = node.getChildNodes();
        Element element = null;
        Element return_ = null;

        if (nl != null && nl.getLength() > 0) {
            for (int i = 0; i < nl.getLength(); i++) {
                if (nl.item(i) instanceof Element) {
                    element = (Element) nl.item(i);

                    if (element.getNodeName().equals(name)) {
                        return_ = element;

                        break;
                    }
                }
            }
        }

        // Debug.debugMethodEnd();
        return return_;
    }

    /**
     * Returns the first child element of the submitted node that matches the given namespace and name.
     */
    public static Element getNamedChild(final Node node, final String namespace, final String name) {
        // Debug.debugMethodBegin( "XMLTools", "getNamedChild" );
        final NodeList nl = node.getChildNodes();
        Element element = null;
        Element return_ = null;

        if (nl != null && nl.getLength() > 0) {
            for (int i = 0; i < nl.getLength(); i++) {
                if (nl.item(i) instanceof Element) {
                    element = (Element) nl.item(i);

                    final String s = element.getNamespaceURI();

                    if (namespace == null && s == null || namespace.equals(s)) {
                        if (element.getLocalName().equals(name)) {
                            return_ = element;

                            break;
                        }
                    }
                }
            }
        }

        // Debug.debugMethodEnd();
        return return_;
    }

    /**
     * Returns the concatenated Strings of all direct children that are TEXT_NODEs.
     */
    public static String getValue(final Node node) {
        final NodeList children = node.getChildNodes();
        final StringBuffer sb = new StringBuffer(children.getLength() * 500);

        for (int i = 0; i < children.getLength(); i++) {
            if (children.item(i).getNodeType() == Node.TEXT_NODE
                    || children.item(i).getNodeType() == Node.CDATA_SECTION_NODE) {
                sb.append(children.item(i).getNodeValue());
            }
        }

        return sb.toString().trim();
    }

    /**
     * Returns all child ELEMENTs.
     */
    public static ElementList getChildElements(final Node node) {
        final NodeList children = node.getChildNodes();
        final ElementList list = new ElementList();

        for (int i = 0; i < children.getLength(); i++) {
            if (children.item(i).getNodeType() == Node.ELEMENT_NODE)
                list.addElement((Element) children.item(i));
        }

        return list;
    }

    /**
     * Appends a node and it's children to the given StringBuffer. Indentation is added on recursion.
     */
    public static void appendNode(final Node node, final String indent, final StringBuffer sb) {
        switch (node.getNodeType()) {
        case Node.DOCUMENT_NODE: {
            sb.append("<?xml version=\"1.0\"?>\n");

            final Document doc = (Document) node;
            appendNode(doc.getDocumentElement(), "", sb);

            break;
        }

        case Node.ELEMENT_NODE: {
            final String name = node.getNodeName();
            sb.append(indent + "<" + name);

            final NamedNodeMap attributes = node.getAttributes();

            for (int i = 0; i < attributes.getLength(); i++) {
                final Node current = attributes.item(i);
                sb.append(" " + current.getNodeName() + "=\"" + current.getNodeValue() + "\"");
            }

            sb.append(">");

            // Kinder durchgehen
            final NodeList children = node.getChildNodes();

            if (children != null) {
                for (int i = 0; i < children.getLength(); i++) {
                    appendNode(children.item(i), indent, sb);
                }
            }

            sb.append(indent + "</" + name + ">");

            break;
        }

        case Node.TEXT_NODE:
        case Node.CDATA_SECTION_NODE: {
            final String trimmed = node.getNodeValue().trim();

            if (!trimmed.equals("")) {
                sb.append(indent + trimmed);
            }

            break;
        }

        case Node.PROCESSING_INSTRUCTION_NODE:
            break;

        case Node.ENTITY_REFERENCE_NODE:
            break;

        case Node.DOCUMENT_TYPE_NODE:
            break;
        }
    }

    /**
     * extracts the local name from a node name
     */
    public static String toLocalName(String nodeName) {
        final int pos = nodeName.lastIndexOf(':');

        if (pos > -1) {
            nodeName = nodeName.substring(pos + 1, nodeName.length());
        }

        return nodeName;
    }
}