org.apache.cocoon.xml.dom.DOMUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.cocoon.xml.dom.DOMUtil.java

Source

/*
 * Copyright 1999-2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.cocoon.xml.dom;

import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.xml.IncludeXMLConsumer;
import org.apache.cocoon.xml.XMLUtils;

import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.excalibur.source.SourceParameters;
import org.apache.excalibur.xml.sax.SAXParser;
import org.apache.excalibur.xml.sax.XMLizable;
import org.apache.excalibur.xml.xpath.NodeListImpl;
import org.apache.excalibur.xml.xpath.XPathProcessor;
import org.apache.excalibur.xml.xpath.XPathUtil;
import org.apache.xpath.XPathAPI;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.TransformerException;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

/**
 *  This class is a utility class for miscellaneous DOM functions, like
 *  getting and setting values of nodes.
 *
 * @author <a href="mailto:cziegeler@s-und-n.de">Carsten Ziegeler</a>
 * @version $Id: DOMUtil.java 366725 2006-01-07 13:53:30Z antonio $
*/
public final class DOMUtil {

    /**
     * Get the owner of the DOM document belonging to the node.
     * This works even if the node is the document itself.
     *
     * @param node The node.
     * @return     The corresponding document.
     */
    public static Document getOwnerDocument(Node node) {
        if (node.getNodeType() == Node.DOCUMENT_NODE) {
            return (Document) node;
        } else {
            return node.getOwnerDocument();
        }
    }

    /**
     * Get the value of the node specified by the XPath.
     * This works similar to xsl:value-of. If the node does not exist <CODE>null</CODE>
     * is returned.
     *
     * @param root The node to start the search.
     * @param path XPath search expression.
     * @return     The value of the node or <CODE>null</CODE>
     */
    public static String getValueOfNode(XPathProcessor processor, Node root, String path)
            throws ProcessingException {
        if (path == null) {
            throw new ProcessingException("Not a valid XPath: " + path);
        }
        if (root != null) {
            path = StringUtils.strip(path, "/");
            Node node = XPathUtil.searchSingleNode(processor, root, path);
            if (node != null) {
                return getValueOfNode(node);
            }
        }
        return null;
    }

    /**
     * Get the value of the node specified by the XPath.
     * This works similar to xsl:value-of. If the node is not found
     * the <CODE>defaultValue</CODE> is returned.
     *
     * @param root The node to start the search.
     * @param path XPath search expression.
     * @param defaultValue The default value if the node does not exist.
     * @return     The value of the node or <CODE>defaultValue</CODE>
     */
    public static String getValueOfNode(XPathProcessor processor, Node root, String path, String defaultValue)
            throws ProcessingException {
        String value = getValueOfNode(processor, root, path);
        if (value == null)
            value = defaultValue;

        return value;
    }

    /**
     * Get the boolean value of the node specified by the XPath.
     * This works similar to xsl:value-of. If the node exists and has a value
     * this value is converted to a boolean, e.g. "true" or "false" as value
     * will result into the corresponding boolean values.
     *
     * @param root The node to start the search.
     * @param path XPath search expression.
     * @return     The boolean value of the node.
     * @throws ProcessingException If the node is not found.
     */
    public static boolean getValueOfNodeAsBoolean(XPathProcessor processor, Node root, String path)
            throws ProcessingException {
        String value = getValueOfNode(processor, root, path);
        if (value == null) {
            throw new ProcessingException("No such node: " + path);
        }
        return Boolean.valueOf(value).booleanValue();
    }

    /**
     * Get the boolean value of the node specified by the XPath.
     * This works similar to xsl:value-of. If the node exists and has a value
     * this value is converted to a boolean, e.g. "true" or "false" as value
     * will result into the corresponding boolean values.
     * If the node does not exist, the <CODE>defaultValue</CODE> is returned.
     *
     * @param root The node to start the search.
     * @param path XPath search expression.
     * @param defaultValue Default boolean value.
     * @return     The value of the node or <CODE>defaultValue</CODE>
     */
    public static boolean getValueOfNodeAsBoolean(XPathProcessor processor, Node root, String path,
            boolean defaultValue) throws ProcessingException {
        String value = getValueOfNode(processor, root, path);
        if (value != null) {
            return BooleanUtils.toBoolean(value);
        }
        return defaultValue;
    }

    /**
     * Get the value of the DOM node.
     * The value of a node is the content of the first text node.
     * If the node has no text nodes, <code>null</code> is returned.
     */
    public static String getValueOfNode(Node node) {
        if (node != null) {
            if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
                return node.getNodeValue();
            } else {
                node.normalize();
                NodeList childs = node.getChildNodes();
                int i = 0;
                int length = childs.getLength();
                while (i < length) {
                    if (childs.item(i).getNodeType() == Node.TEXT_NODE) {
                        return childs.item(i).getNodeValue().trim();
                    } else {
                        i++;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Get the value of the node.
     * The value of the node is the content of the first text node.
     * If the node has no text nodes the <CODE>defaultValue</CODE> is
     * returned.
     */
    public static String getValueOfNode(Node node, String defaultValue) {
        return StringUtils.defaultString(getValueOfNode(node), defaultValue);
    }

    /**
     * Set the value of the DOM node.
     * All current children of the node are removed and a new text node
     * with the value is appended.
     */
    public static void setValueOfNode(Node node, String value) {
        if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
            node.setNodeValue(value);
        } else {
            while (node.hasChildNodes() == true) {
                node.removeChild(node.getFirstChild());
            }
            node.appendChild(node.getOwnerDocument().createTextNode(value));
        }
    }

    /** XML definition for a document */
    private static final String XML_DEFINITION = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>";
    private static final String XML_ROOT_DEFINITION = XML_DEFINITION + "<root>";

    /**
     * Get a document fragment from a <code>Reader</code>.
     * The reader must provide valid XML, but it is allowed that the XML
     * has more than one root node. This xml is parsed by the
     * specified parser instance and a DOM DocumentFragment is created.
     */
    public static DocumentFragment getDocumentFragment(SAXParser parser, Reader stream) throws ProcessingException {
        DocumentFragment frag = null;

        Writer writer;
        Reader reader;
        boolean removeRoot = true;

        try {
            // create a writer,
            // write the root element, then the input from the
            // reader
            writer = new StringWriter();

            writer.write(XML_ROOT_DEFINITION);
            char[] cbuf = new char[16384];
            int len;
            do {
                len = stream.read(cbuf, 0, 16384);
                if (len != -1) {
                    writer.write(cbuf, 0, len);
                }
            } while (len != -1);
            writer.write("</root>");

            // now test if xml input start with <?xml
            String xml = writer.toString();
            String searchString = XML_ROOT_DEFINITION + "<?xml ";
            if (xml.startsWith(searchString) == true) {
                // now remove the surrounding root element
                xml = xml.substring(XML_ROOT_DEFINITION.length(), xml.length() - 7);
                removeRoot = false;
            }

            reader = new StringReader(xml);

            InputSource input = new InputSource(reader);

            DOMBuilder builder = new DOMBuilder();
            builder.startDocument();
            builder.startElement("", "root", "root", XMLUtils.EMPTY_ATTRIBUTES);

            IncludeXMLConsumer filter = new IncludeXMLConsumer(builder, builder);
            parser.parse(input, filter);

            builder.endElement("", "root", "root");
            builder.endDocument();

            // Create Document Fragment, remove <root>
            final Document doc = builder.getDocument();
            frag = doc.createDocumentFragment();
            final Node root = doc.getDocumentElement().getFirstChild();
            root.normalize();
            if (removeRoot == false) {
                root.getParentNode().removeChild(root);
                frag.appendChild(root);
            } else {
                Node child;
                while (root.hasChildNodes() == true) {
                    child = root.getFirstChild();
                    root.removeChild(child);
                    frag.appendChild(child);
                }
            }
        } catch (SAXException sax) {
            throw new ProcessingException("SAXException: " + sax, sax);
        } catch (IOException ioe) {
            throw new ProcessingException("IOException: " + ioe, ioe);
        }
        return frag;
    }

    /**
     * Create a parameter object from xml.
     * The xml is flat and consists of elements which all have exactly one text node:
     * <parone>value_one<parone>
     * <partwo>value_two<partwo>
     * A parameter can occur more than once with different values.
     * If <CODE>source</CODE> is not specified a new paramter object is created
     * otherwise the parameters are added to source.
     */
    public static SourceParameters createParameters(Node fragment, SourceParameters source) {
        SourceParameters par = (source == null ? new SourceParameters() : source);
        if (fragment != null) {
            NodeList childs = fragment.getChildNodes();
            if (childs != null) {
                Node current;
                for (int i = 0; i < childs.getLength(); i++) {
                    current = childs.item(i);

                    // only element nodes
                    if (current.getNodeType() == Node.ELEMENT_NODE) {
                        current.normalize();
                        NodeList valueChilds = current.getChildNodes();
                        String key;
                        StringBuffer valueBuffer;
                        String value;

                        key = current.getNodeName();
                        valueBuffer = new StringBuffer();
                        for (int m = 0; m < valueChilds.getLength(); m++) {
                            current = valueChilds.item(m); // attention: current is reused here!
                            if (current.getNodeType() == Node.TEXT_NODE) { // only text nodes
                                if (valueBuffer.length() > 0)
                                    valueBuffer.append(' ');
                                valueBuffer.append(current.getNodeValue());
                            }
                        }
                        value = valueBuffer.toString().trim();
                        if (key != null && value.length() > 0) {
                            par.setParameter(key, value);
                        }
                    }
                }
            }
        }
        return par;
    }

    /**
     * Create a string from a DOM document fragment.
     * Only the top level text nodes are chained together to build the text.
     */
    public static String createText(DocumentFragment fragment) {
        StringBuffer value = new StringBuffer();
        if (fragment != null) {
            NodeList childs = fragment.getChildNodes();
            if (childs != null) {
                Node current;

                for (int i = 0; i < childs.getLength(); i++) {
                    current = childs.item(i);

                    // only text nodes
                    if (current.getNodeType() == Node.TEXT_NODE) {
                        if (value.length() > 0)
                            value.append(' ');
                        value.append(current.getNodeValue());
                    }
                }
            }
        }
        return value.toString().trim();
    }

    /**
     * Compare all attributes of two elements.
     * This method returns true only if both nodes have the same number of
     * attributes and the same attributes with equal values.
     * Namespace definition nodes are ignored
     */
    public static boolean compareAttributes(Element first, Element second) {
        NamedNodeMap attr1 = first.getAttributes();
        NamedNodeMap attr2 = second.getAttributes();
        String value;

        if (attr1 == null && attr2 == null)
            return true;
        int attr1Len = (attr1 == null ? 0 : attr1.getLength());
        int attr2Len = (attr2 == null ? 0 : attr2.getLength());
        if (attr1Len > 0) {
            int l = attr1.getLength();
            for (int i = 0; i < l; i++) {
                if (attr1.item(i).getNodeName().startsWith("xmlns:") == true)
                    attr1Len--;
            }
        }
        if (attr2Len > 0) {
            int l = attr2.getLength();
            for (int i = 0; i < l; i++) {
                if (attr2.item(i).getNodeName().startsWith("xmlns:") == true)
                    attr2Len--;
            }
        }
        if (attr1Len != attr2Len)
            return false;
        int i, l;
        int m, l2;
        i = 0;
        l = attr1.getLength();
        l2 = attr2.getLength();
        boolean ok = true;
        // each attribute of first must be in second with the same value
        while (i < l && ok == true) {
            value = attr1.item(i).getNodeName();
            if (value.startsWith("xmlns:") == false) {
                ok = false;
                m = 0;
                while (m < l2 && ok == false) {
                    if (attr2.item(m).getNodeName().equals(value) == true) {
                        // same name, same value?
                        ok = attr1.item(i).getNodeValue().equals(attr2.item(m).getNodeValue());
                    }
                    m++;
                }
            }

            i++;
        }
        return ok;
    }

    /**
     * Implementation for <code>String</code> :
     * outputs characters representing the value.
     *
     * @param parent The node getting the value
     * @param text   the value
     */
    public static void valueOf(Node parent, String text) throws ProcessingException {
        if (text != null) {
            parent.appendChild(parent.getOwnerDocument().createTextNode(text));
        }
    }

    /**
     * Implementation for <code>XMLizable</code> :
     * outputs the value by calling <code>v.toSax(contentHandler)</code>.
     *
     * @param parent The node getting the value
     * @param v the XML fragment
     */
    public static void valueOf(Node parent, XMLizable v) throws ProcessingException {
        if (v != null) {
            DOMBuilder builder = new DOMBuilder(parent);
            try {
                v.toSAX(builder);
            } catch (SAXException e) {
                throw new ProcessingException(e);
            }
        }
    }

    /**
     * Implementation for <code>org.w3c.dom.Node</code> :
     * converts the Node to a SAX event stream.
     *
     * @param parent The node getting the value
     * @param v the value
     */
    public static void valueOf(Node parent, Node v) throws ProcessingException {
        if (v != null) {
            parent.appendChild(parent.getOwnerDocument().importNode(v, true));
        }
    }

    /**
     * Implementation for <code>java.util.Collection</code> :
     * outputs the value by calling {@link #valueOf(Node, Object)} on each element of the
     * collection.
     *
     * @param parent The node getting the value
     * @param v the XML fragment
     */
    public static void valueOf(Node parent, Collection v) throws ProcessingException {
        if (v != null) {
            Iterator iterator = v.iterator();
            while (iterator.hasNext()) {
                valueOf(parent, iterator.next());
            }
        }
    }

    /**
     * Implementation for <code>java.util.Map</code> :
     * For each entry an element is created with the childs key and value
     * Outputs the value and the key by calling {@link #valueOf(Node, Object)}
     * on each value and key of the Map.
     *
     * @param parent The node getting the value
     * @param v      the Map
     */
    public static void valueOf(Node parent, Map v) throws ProcessingException {
        if (v != null) {
            Node mapNode = parent.getOwnerDocument().createElementNS(null, "java.util.map");
            parent.appendChild(mapNode);
            for (Iterator iter = v.entrySet().iterator(); iter.hasNext();) {
                Map.Entry me = (Map.Entry) iter.next();

                Node entryNode = mapNode.getOwnerDocument().createElementNS(null, "entry");
                mapNode.appendChild(entryNode);

                Node keyNode = entryNode.getOwnerDocument().createElementNS(null, "key");
                entryNode.appendChild(keyNode);
                valueOf(keyNode, me.getKey());

                Node valueNode = entryNode.getOwnerDocument().createElementNS(null, "value");
                entryNode.appendChild(valueNode);
                valueOf(valueNode, me.getValue());
            }
        }
    }

    /**
     * Implementation for <code>Object</code> depending on its class :
     * <ul>
     * <li>if it's an array, call {@link #valueOf(Node, Object)} on all its elements,</li>
     * <li>if it's class has a specific {@link #valueOf(Node, Object)} implementation, use it,</li>
     * <li>else, output it's string representation.</li>
     * </ul>
     *
     * @param parent The node getting the value
     * @param v the value
     */
    public static void valueOf(Node parent, Object v) throws ProcessingException {
        if (v == null) {
            return;
        }

        // Array: recurse over each element
        if (v.getClass().isArray()) {
            Object[] elements = (Object[]) v;

            for (int i = 0; i < elements.length; i++) {
                valueOf(parent, elements[i]);
            }
            return;
        }

        // Check handled object types in case they were not typed in the XSP

        // XMLizable
        if (v instanceof XMLizable) {
            valueOf(parent, (XMLizable) v);
            return;
        }

        // Node
        if (v instanceof Node) {
            valueOf(parent, (Node) v);
            return;
        }

        // Collection
        if (v instanceof Collection) {
            valueOf(parent, (Collection) v);
            return;
        }

        // Map
        if (v instanceof Map) {
            valueOf(parent, (Map) v);
            return;
        }

        // Give up: hope it's a string or has a meaningful string representation
        valueOf(parent, String.valueOf(v));
    }

    /**
     * Use an XPath string to select a single node. XPath namespace
     * prefixes are resolved from the context node, which may not
     * be what you want (see the next method).
     *
     * @param contextNode The node to start searching from.
     * @param str A valid XPath string.
     * @param processor The XPath processor to use
     * @return The first node found that matches the XPath, or null.
     *
     * @throws TransformerException
     */
    public static Node getSingleNode(Node contextNode, String str, XPathProcessor processor)
            throws TransformerException {
        String[] pathComponents = buildPathArray(str);
        if (pathComponents == null) {
            return processor.selectSingleNode(contextNode, str);
        } else {
            return getFirstNodeFromPath(contextNode, pathComponents, false);
        }
    }

    /**
     * Use an XPath string to select a single node. XPath namespace
     * prefixes are resolved from the context node, which may not
     * be what you want (see the next method).
     *
     * @param contextNode The node to start searching from.
     * @param str A valid XPath string.
     * @return The first node found that matches the XPath, or null.
     *
     * @throws TransformerException
     * @deprecated
     */
    public static Node getSingleNode(Node contextNode, String str) throws TransformerException {
        String[] pathComponents = buildPathArray(str);
        if (pathComponents == null) {
            return XPathAPI.selectSingleNode(contextNode, str);
        } else {
            return getFirstNodeFromPath(contextNode, pathComponents, false);
        }
    }

    /**
     * Return the <CODE>Node</CODE> from the DOM Node <CODE>rootNode</CODE>
     * using the XPath expression <CODE>path</CODE>.
     * If the node does not exist, it is created and then returned.
     * This is a very simple method for creating new nodes. If the
     * XPath contains selectors ([,,,]) or "*" it is of course not
     * possible to create the new node. So if you use such XPaths
     * the node must exist beforehand.
     * An simple exception is if the expression contains attribute
     * test to values (e.g. [@id = 'du' and @number = 'you'],
     * the attributes with the given values are added. The attributes
     * must be separated with 'and'.
     * Another problem are namespaces: XPath requires sometimes selectors for
     * namespaces, e.g. : /*[namespace-uri()="uri" and local-name()="name"]
     * Creating such a node with a namespace is not possible right now as we use
     * a very simple XPath parser which is not able to parse all kinds of selectors
     * correctly.
     *
     * @param rootNode The node to start the search.
     * @param path     XPath expression for searching the node.
     * @return         The node specified by the path.
     * @throws ProcessingException If no path is specified or the XPath engine fails.
     * @deprecated
     */
    public static Node selectSingleNode(Node rootNode, String path) throws ProcessingException {
        // Now we have to parse the string
        // First test:  path? rootNode?
        if (path == null) {
            throw new ProcessingException("XPath is required.");
        }
        if (rootNode == null)
            return rootNode;

        if (path.length() == 0 || path.equals("/") == true)
            return rootNode;

        // now the first "quick" test is if the node exists using the
        // full XPathAPI
        try {
            Node testNode = getSingleNode(rootNode, path);
            if (testNode != null)
                return testNode;
        } catch (javax.xml.transform.TransformerException local) {
            throw new ProcessingException(
                    "Transforming exception during selectSingleNode with path: '" + path + "'. Exception: " + local,
                    local);
        }
        // Remove leading "/" on both ends
        path = StringUtils.strip(path, "/");

        // now step through the nodes!
        Node parent = rootNode;
        int pos;
        int posSelector;
        do {
            pos = path.indexOf("/"); // get next separator
            posSelector = path.indexOf("[");
            if (posSelector != -1 && posSelector < pos) {
                posSelector = path.indexOf("]");
                pos = path.indexOf("/", posSelector);
            }

            String nodeName;
            boolean isAttribute = false;
            if (pos != -1) { // found separator
                nodeName = path.substring(0, pos); // string until "/"
                path = path.substring(pos + 1); // rest of string after "/"
            } else {
                nodeName = path;
            }

            // test for attribute spec
            if (nodeName.startsWith("@") == true) {
                isAttribute = true;
            }

            Node singleNode;
            try {
                singleNode = getSingleNode(parent, nodeName);
            } catch (javax.xml.transform.TransformerException localException) {
                throw new ProcessingException("XPathUtil.selectSingleNode: " + localException.getMessage(),
                        localException);
            }

            // create node if necessary
            if (singleNode == null) {
                Node newNode;
                // delete XPath selectors
                int posSelect = nodeName.indexOf("[");
                String XPathExp = null;
                if (posSelect != -1) {
                    XPathExp = nodeName.substring(posSelect + 1, nodeName.length() - 1);
                    nodeName = nodeName.substring(0, posSelect);
                }
                if (isAttribute == true) {
                    try {
                        newNode = getOwnerDocument(rootNode).createAttributeNS(null, nodeName.substring(1));
                        ((Element) parent).setAttributeNodeNS((org.w3c.dom.Attr) newNode);
                        parent = newNode;
                    } catch (DOMException local) {
                        throw new ProcessingException("Unable to create new DOM node: '" + nodeName + "'.", local);
                    }
                } else {
                    try {
                        newNode = getOwnerDocument(rootNode).createElementNS(null, nodeName);
                    } catch (DOMException local) {
                        throw new ProcessingException("Unable to create new DOM node: '" + nodeName + "'.", local);
                    }
                    if (XPathExp != null) {
                        java.util.List attrValuePairs = new java.util.ArrayList(4);
                        boolean noError = true;

                        String attr;
                        String value;
                        // scan for attributes
                        java.util.StringTokenizer tokenizer = new java.util.StringTokenizer(XPathExp, "= ");
                        while (tokenizer.hasMoreTokens() == true) {
                            attr = tokenizer.nextToken();
                            if (attr.startsWith("@") == true) {
                                if (tokenizer.hasMoreTokens() == true) {
                                    value = tokenizer.nextToken();
                                    if (value.startsWith("'") && value.endsWith("'"))
                                        value = value.substring(1, value.length() - 1);
                                    if (value.startsWith("\"") && value.endsWith("\""))
                                        value = value.substring(1, value.length() - 1);
                                    attrValuePairs.add(attr.substring(1));
                                    attrValuePairs.add(value);
                                } else {
                                    noError = false;
                                }
                            } else if (attr.trim().equals("and") == false) {
                                noError = false;
                            }
                        }
                        if (noError == true) {
                            for (int l = 0; l < attrValuePairs.size(); l = l + 2) {
                                ((Element) newNode).setAttributeNS(null, (String) attrValuePairs.get(l),
                                        (String) attrValuePairs.get(l + 1));
                            }
                        }
                    }
                    parent.appendChild(newNode);
                    parent = newNode;
                }
            } else {
                parent = singleNode;
            }
        } while (pos != -1);
        return parent;
    }

    /**
     * Return the <CODE>Node</CODE> from the DOM Node <CODE>rootNode</CODE>
     * using the XPath expression <CODE>path</CODE>.
     * If the node does not exist, it is created and then returned.
     * This is a very simple method for creating new nodes. If the
     * XPath contains selectors ([,,,]) or "*" it is of course not
     * possible to create the new node. So if you use such XPaths
     * the node must exist beforehand.
     * An simple exception is if the expression contains attribute
     * test to values (e.g. [@id = 'du' and @number = 'you'],
     * the attributes with the given values are added. The attributes
     * must be separated with 'and'.
     * Another problem are namespaces: XPath requires sometimes selectors for
     * namespaces, e.g. : /*[namespace-uri()="uri" and local-name()="name"]
     * Creating such a node with a namespace is not possible right now as we use
     * a very simple XPath parser which is not able to parse all kinds of selectors
     * correctly.
     *
     * @param rootNode  The node to start the search.
     * @param path      XPath expression for searching the node.
     * @param processor The XPath processor to use
     * @return          The node specified by the path.
     * @throws ProcessingException If no path is specified or the XPath engine fails.
     */
    public static Node selectSingleNode(Node rootNode, String path, XPathProcessor processor)
            throws ProcessingException {
        // Now we have to parse the string
        // First test:  path? rootNode?
        if (path == null) {
            throw new ProcessingException("XPath is required.");
        }
        if (rootNode == null)
            return rootNode;

        if (path.length() == 0 || path.equals("/") == true)
            return rootNode;

        // now the first "quick" test is if the node exists using the
        // full XPathAPI
        try {
            Node testNode = getSingleNode(rootNode, path, processor);
            if (testNode != null)
                return testNode;
        } catch (javax.xml.transform.TransformerException local) {
            throw new ProcessingException(
                    "Transforming exception during selectSingleNode with path: '" + path + "'. Exception: " + local,
                    local);
        }

        // remove leading "/" oon both ends
        path = StringUtils.strip(path, "/");

        // now step through the nodes!
        Node parent = rootNode;
        int pos;
        int posSelector;
        do {
            pos = path.indexOf("/"); // get next separator
            posSelector = path.indexOf("[");
            if (posSelector != -1 && posSelector < pos) {
                posSelector = path.indexOf("]");
                pos = path.indexOf("/", posSelector);
            }

            String nodeName;
            boolean isAttribute = false;
            if (pos != -1) { // found separator
                nodeName = path.substring(0, pos); // string until "/"
                path = path.substring(pos + 1); // rest of string after "/"
            } else {
                nodeName = path;
            }

            // test for attribute spec
            if (nodeName.startsWith("@") == true) {
                isAttribute = true;
            }

            Node singleNode;
            try {
                singleNode = getSingleNode(parent, nodeName, processor);
            } catch (javax.xml.transform.TransformerException localException) {
                throw new ProcessingException("XPathUtil.selectSingleNode: " + localException.getMessage(),
                        localException);
            }

            // create node if necessary
            if (singleNode == null) {
                Node newNode;
                // delete XPath selectors
                int posSelect = nodeName.indexOf("[");
                String XPathExp = null;
                if (posSelect != -1) {
                    XPathExp = nodeName.substring(posSelect + 1, nodeName.length() - 1);
                    nodeName = nodeName.substring(0, posSelect);
                }
                if (isAttribute == true) {
                    try {
                        newNode = getOwnerDocument(rootNode).createAttributeNS(null, nodeName.substring(1));
                        ((Element) parent).setAttributeNodeNS((org.w3c.dom.Attr) newNode);
                        parent = newNode;
                    } catch (DOMException local) {
                        throw new ProcessingException("Unable to create new DOM node: '" + nodeName + "'.", local);
                    }
                } else {
                    try {
                        newNode = getOwnerDocument(rootNode).createElementNS(null, nodeName);
                    } catch (DOMException local) {
                        throw new ProcessingException("Unable to create new DOM node: '" + nodeName + "'.", local);
                    }
                    if (XPathExp != null) {
                        java.util.List attrValuePairs = new java.util.ArrayList(4);
                        boolean noError = true;

                        String attr;
                        String value;
                        // scan for attributes
                        java.util.StringTokenizer tokenizer = new java.util.StringTokenizer(XPathExp, "= ");
                        while (tokenizer.hasMoreTokens() == true) {
                            attr = tokenizer.nextToken();
                            if (attr.startsWith("@") == true) {
                                if (tokenizer.hasMoreTokens() == true) {
                                    value = tokenizer.nextToken();
                                    if (value.startsWith("'") && value.endsWith("'"))
                                        value = value.substring(1, value.length() - 1);
                                    if (value.startsWith("\"") && value.endsWith("\""))
                                        value = value.substring(1, value.length() - 1);
                                    attrValuePairs.add(attr.substring(1));
                                    attrValuePairs.add(value);
                                } else {
                                    noError = false;
                                }
                            } else if (attr.trim().equals("and") == false) {
                                noError = false;
                            }
                        }
                        if (noError == true) {
                            for (int l = 0; l < attrValuePairs.size(); l = l + 2) {
                                ((Element) newNode).setAttributeNS(null, (String) attrValuePairs.get(l),
                                        (String) attrValuePairs.get(l + 1));
                            }
                        }
                    }
                    parent.appendChild(newNode);
                    parent = newNode;
                }
            } else {
                parent = singleNode;
            }
        } while (pos != -1);
        return parent;
    }

    /**
     * Get the value of the node specified by the XPath.
     * This works similar to xsl:value-of. If the node does not exist <CODE>null</CODE>
     * is returned.
     *
     * @param root The node to start the search.
     * @param path XPath search expression.
     * @return     The value of the node or <CODE>null</CODE>
     * @deprecated
     */
    public static String getValueOf(Node root, String path) throws ProcessingException {
        if (path == null) {
            throw new ProcessingException("Not a valid XPath: " + path);
        }
        if (root == null)
            return null;
        path = StringUtils.strip(path, "/");

        try {
            Node node = getSingleNode(root, path);
            if (node != null) {
                return getValueOfNode(node);
            }
        } catch (javax.xml.transform.TransformerException localException) {
            throw new ProcessingException("XPathUtil.selectSingleNode: " + localException.getMessage(),
                    localException);
        }
        return null;
    }

    /**
     * Get the value of the node specified by the XPath.
     * This works similar to xsl:value-of. If the node does not exist <CODE>null</CODE>
     * is returned.
     *
     * @param root The node to start the search.
     * @param path XPath search expression.
     * @param processor The XPath processor to use
     * @return     The value of the node or <CODE>null</CODE>
     */
    public static String getValueOf(Node root, String path, XPathProcessor processor) throws ProcessingException {
        if (path == null) {
            throw new ProcessingException("Not a valid XPath: " + path);
        }
        if (root == null)
            return null;
        path = StringUtils.strip(path, "/");

        try {
            Node node = getSingleNode(root, path, processor);
            if (node != null) {
                return getValueOfNode(node);
            }
        } catch (javax.xml.transform.TransformerException localException) {
            throw new ProcessingException("XPathUtil.selectSingleNode: " + localException.getMessage(),
                    localException);
        }
        return null;
    }

    /**
     * Get the value of the node specified by the XPath.
     * This works similar to xsl:value-of. If the node is not found
     * the <CODE>defaultValue</CODE> is returned.
     *
     * @param root The node to start the search.
     * @param path XPath search expression.
     * @param defaultValue The default value if the node does not exist.
     * @return     The value of the node or <CODE>defaultValue</CODE>
     * @deprecated
     */
    public static String getValueOf(Node root, String path, String defaultValue) throws ProcessingException {
        String value = getValueOf(root, path);
        if (value == null) {
            value = defaultValue;
        }
        return value;
    }

    /**
     * Get the value of the node specified by the XPath.
     * This works similar to xsl:value-of. If the node is not found
     * the <CODE>defaultValue</CODE> is returned.
     *
     * @param root The node to start the search.
     * @param path XPath search expression.
     * @param defaultValue The default value if the node does not exist.
     * @param processor The XPath Processor
     * @return     The value of the node or <CODE>defaultValue</CODE>
     */
    public static String getValueOf(Node root, String path, String defaultValue, XPathProcessor processor)
            throws ProcessingException {
        String value = getValueOf(root, path, processor);
        if (value == null) {
            value = defaultValue;
        }
        return value;
    }

    /**
     * Get the boolean value of the node specified by the XPath.
     * This works similar to xsl:value-of. If the node exists and has a value
     * this value is converted to a boolean, e.g. "true" or "false" as value
     * will result into the corresponding boolean values.
     *
     * @param root The node to start the search.
     * @param path XPath search expression.
     * @return     The boolean value of the node.
     * @throws ProcessingException If the node is not found.
     * @deprecated
     */
    public static boolean getValueAsBooleanOf(Node root, String path) throws ProcessingException {
        String value = getValueOf(root, path);
        if (value != null) {
            return Boolean.valueOf(value).booleanValue();
        } else {
            throw new ProcessingException("No such node: " + path);
        }
    }

    /**
     * Get the boolean value of the node specified by the XPath.
     * This works similar to xsl:value-of. If the node exists and has a value
     * this value is converted to a boolean, e.g. "true" or "false" as value
     * will result into the corresponding boolean values.
     *
     * @param root The node to start the search.
     * @param path XPath search expression.
     * @param processor The XPath Processor
     * @return     The boolean value of the node.
     * @throws ProcessingException If the node is not found.
     */
    public static boolean getValueAsBooleanOf(Node root, String path, XPathProcessor processor)
            throws ProcessingException {
        String value = getValueOf(root, path, processor);
        if (value == null) {
            throw new ProcessingException("No such node: " + path);
        }
        return Boolean.valueOf(value).booleanValue();
    }

    /**
     * Get the boolean value of the node specified by the XPath.
     * This works similar to xsl:value-of. If the node exists and has a value
     * this value is converted to a boolean, e.g. "true" or "false" as value
     * will result into the corresponding boolean values.
     * If the node does not exist, the <CODE>defaultValue</CODE> is returned.
     *
     * @param root The node to start the search.
     * @param path XPath search expression.
     * @param defaultValue Default boolean value.
     * @return     The value of the node or <CODE>defaultValue</CODE>
     * @deprecated
     */
    public static boolean getValueAsBooleanOf(Node root, String path, boolean defaultValue)
            throws ProcessingException {
        String value = getValueOf(root, path);
        if (value != null) {
            return Boolean.valueOf(value).booleanValue();
        }
        return defaultValue;
    }

    /**
     * Get the boolean value of the node specified by the XPath.
     * This works similar to xsl:value-of. If the node exists and has a value
     * this value is converted to a boolean, e.g. "true" or "false" as value
     * will result into the corresponding boolean values.
     * If the node does not exist, the <CODE>defaultValue</CODE> is returned.
     *
     * @param root The node to start the search.
     * @param path XPath search expression.
     * @param defaultValue Default boolean value.
     * @param processor The XPath Processor
     * @return     The value of the node or <CODE>defaultValue</CODE>
     */
    public static boolean getValueAsBooleanOf(Node root, String path, boolean defaultValue,
            XPathProcessor processor) throws ProcessingException {
        String value = getValueOf(root, path, processor);
        if (value != null) {
            return Boolean.valueOf(value).booleanValue();
        }
        return defaultValue;
    }

    /**
     * Create a new empty DOM document.
     */
    public static Document createDocument() throws ProcessingException {
        try {
            DocumentBuilderFactory documentFactory = DocumentBuilderFactory.newInstance();
            documentFactory.setNamespaceAware(true);
            documentFactory.setValidating(false);
            DocumentBuilder docBuilder = documentFactory.newDocumentBuilder();
            return docBuilder.newDocument();
        } catch (ParserConfigurationException pce) {
            throw new ProcessingException("Creating document failed.", pce);
        }
    }

    /**
     *  Use an XPath string to select a nodelist.
     *  XPath namespace prefixes are resolved from the contextNode.
     *
     *  @param contextNode The node to start searching from.
     *  @param str A valid XPath string.
     *  @return A NodeIterator, should never be null.
     *
     * @throws TransformerException
     * @deprecated
     */
    public static NodeList selectNodeList(Node contextNode, String str) throws TransformerException {
        String[] pathComponents = buildPathArray(str);
        if (pathComponents != null) {
            return getNodeListFromPath(contextNode, pathComponents);
        }
        return XPathAPI.selectNodeList(contextNode, str);
    }

    /**
     *  Use an XPath string to select a nodelist.
     *  XPath namespace prefixes are resolved from the contextNode.
     *
     *  @param contextNode The node to start searching from.
     *  @param str A valid XPath string.
     *  @param processor The XPath Processor
     *  @return A NodeIterator, should never be null.
     *
     * @throws TransformerException
     */
    public static NodeList selectNodeList(Node contextNode, String str, XPathProcessor processor)
            throws TransformerException {
        String[] pathComponents = buildPathArray(str);
        if (pathComponents != null) {
            return getNodeListFromPath(contextNode, pathComponents);
        }
        return processor.selectNodeList(contextNode, str);
    }

    /**
     * Build the input for the get...FromPath methods. If the XPath
     * expression cannot be handled by the methods, <code>null</code>
     * is returned.
     */
    public static String[] buildPathArray(String xpath) {
        String[] result = null;
        if (xpath != null && xpath.charAt(0) != '/') {
            // test
            int components = 1;
            int i, l;
            l = xpath.length();
            boolean found = false;
            i = 0;
            while (i < l && found == false) {
                switch (xpath.charAt(i)) {
                case '[':
                    found = true;
                    break;
                case '(':
                    found = true;
                    break;
                case '*':
                    found = true;
                    break;
                case '@':
                    found = true;
                    break;
                case ':':
                    found = true;
                    break;
                case '/':
                    components++;
                default:
                    i++;
                }
            }
            if (found == false) {
                result = new String[components];
                if (components == 1) {
                    result[components - 1] = xpath;
                } else {
                    i = 0;
                    int start = 0;
                    components = 0;
                    while (i < l) {
                        if (xpath.charAt(i) == '/') {
                            result[components] = xpath.substring(start, i);
                            start = i + 1;
                            components++;
                        }
                        i++;
                    }
                    result[components] = xpath.substring(start);
                }
            }
        }
        return result;
    }

    /**
     * Use a path to select the first occurence of a node. The namespace
     * of a node is ignored!
     * @param contextNode The node starting the search.
     * @param path        The path to search the node. The
     *                    contextNode is searched for a child named path[0],
     *                    this node is searched for a child named path[1]...
     * @param create      If a child with the corresponding name is not found
     *                    and create is set, this node will be created.
    */
    public static Node getFirstNodeFromPath(Node contextNode, final String[] path, final boolean create) {
        if (contextNode == null || path == null || path.length == 0)
            return contextNode;
        // first test if the node exists
        Node item = getFirstNodeFromPath(contextNode, path, 0);
        if (item == null && create == true) {
            int i = 0;
            NodeList childs;
            boolean found;
            int m, l;
            while (contextNode != null && i < path.length) {
                childs = contextNode.getChildNodes();
                found = false;
                if (childs != null) {
                    m = 0;
                    l = childs.getLength();
                    while (found == false && m < l) {
                        item = childs.item(m);
                        if (item.getNodeType() == Node.ELEMENT_NODE
                                && item.getLocalName().equals(path[i]) == true) {
                            found = true;
                            contextNode = item;
                        }
                        m++;
                    }
                }
                if (found == false) {
                    Element e = contextNode.getOwnerDocument().createElementNS(null, path[i]);
                    contextNode.appendChild(e);
                    contextNode = e;
                }
                i++;
            }
            item = contextNode;
        }
        return item;
    }

    /**
     * Private helper method for getFirstNodeFromPath()
     */
    private static Node getFirstNodeFromPath(final Node contextNode, final String[] path, final int startIndex) {
        int i = 0;
        NodeList childs;
        boolean found;
        int l;
        Node item = null;

        childs = contextNode.getChildNodes();
        found = false;
        if (childs != null) {
            i = 0;
            l = childs.getLength();
            while (found == false && i < l) {
                item = childs.item(i);
                if (item.getNodeType() == Node.ELEMENT_NODE && path[startIndex]
                        .equals(item.getLocalName() != null ? item.getLocalName() : item.getNodeName()) == true) {
                    if (startIndex == path.length - 1) {
                        found = true;
                    } else {
                        item = getFirstNodeFromPath(item, path, startIndex + 1);
                        if (item != null)
                            found = true;
                    }
                }
                if (found == false) {
                    i++;
                }
            }
            if (found == false) {
                item = null;
            }
        }
        return item;
    }

    /**
     * Use a path to select all occurences of a node. The namespace
     * of a node is ignored!
     * @param contextNode The node starting the search.
     * @param path        The path to search the node. The
     *                    contextNode is searched for a child named path[0],
     *                    this node is searched for a child named path[1]...
     */
    public static NodeList getNodeListFromPath(Node contextNode, String[] path) {
        if (contextNode == null)
            return new NodeListImpl();
        if (path == null || path.length == 0) {
            return new NodeListImpl(new Node[] { contextNode });
        }
        NodeListImpl result = new NodeListImpl();
        try {
            getNodesFromPath(result, contextNode, path, 0);
        } catch (NullPointerException npe) {
            // this NPE is thrown because the parser is not configured
            // to use DOM Level 2
            throw new NullPointerException("XMLUtil.getNodeListFromPath() did catch a NullPointerException."
                    + "This might be due to a missconfigured XML parser which does not use DOM Level 2."
                    + "Make sure that you use the XML parser shipped with Cocoon.");
        }
        return result;
    }

    /**
     * Helper method for getNodeListFromPath()
     */
    private static void getNodesFromPath(final NodeListImpl result, final Node contextNode, final String[] path,
            final int startIndex) {
        final NodeList childs = contextNode.getChildNodes();
        int m, l;
        Node item;
        if (startIndex == (path.length - 1)) {
            if (childs != null) {
                m = 0;
                l = childs.getLength();
                while (m < l) {
                    item = childs.item(m);
                    if (item.getNodeType() == Node.ELEMENT_NODE) {
                        // Work around: org.apache.xerces.dom.ElementImpl doesn't handle getLocalName() correct
                        if (path[startIndex].equals(
                                item.getLocalName() != null ? item.getLocalName() : item.getNodeName()) == true) {
                            result.addNode(item);
                        }
                    }
                    m++;
                }
            }
        } else {
            if (childs != null) {
                m = 0;
                l = childs.getLength();
                while (m < l) {
                    item = childs.item(m);
                    if (item.getNodeType() == Node.ELEMENT_NODE) {
                        // Work around: org.apache.xerces.dom.ElementImpl doesn't handle getLocalName() correct
                        if (path[startIndex].equals(
                                item.getLocalName() != null ? item.getLocalName() : item.getNodeName()) == true) {
                            getNodesFromPath(result, item, path, startIndex + 1);
                        }
                    }
                    m++;
                }
            }
        }
    }

    /**
     * Converts a org.w3c.dom.Node to a String. Uses {@link javax.xml.transform.Transformer}
     * to convert from a Node to a String.
     *
     * @param node a <code>org.w3c.dom.Node</code> value
     * @return String representation of the document
     * @deprecated Use {@link XMLUtils#serializeNodeToXML(Node)} instead.
     */
    public static String node2String(Node node) {
        try {
            return XMLUtils.serializeNodeToXML(node);
        } catch (ProcessingException e) {
            // Empty
        }
        return "";
    }

    /**
     * Create a string representation of a org.w3c.dom.Node and any
     * (most) subtypes.
     * @param node a <code>org.w3c.dom.Node</code> value
     * @param pretty a <code>boolean</code> value whether to format the XML
     * @return a <code>String</code> value
     * @deprecated Please use {@link XMLUtils#serializeNode(Node, Properties)} instead.
     */
    public static String node2String(Node node, boolean pretty) {
        try {
            if (pretty) {
                Properties props = new Properties();
                props.setProperty(OutputKeys.INDENT, "yes");
                return XMLUtils.serializeNode(node, props);
            } else {
                return XMLUtils.serializeNodeToXML(node);
            }
        } catch (ProcessingException e) {
        }
        return "";
    }

    /**
     * Create a string representation of a org.w3c.dom.Node and any
     * (most) subtypes.
     * @param node a <code>org.w3c.dom.Node</code> value
     * @return a <code>StringBuffer</code> value
     * @deprecated Please use {@link XMLUtils#serializeNodeToXML(Node)} instead.
     */
    public static StringBuffer node2StringBuffer(Node node) {
        return new StringBuffer(node2String(node));
    }

    /**
     * Create a string representation of a org.w3c.dom.Node and any
     * (most) subtypes.
     * @param node a <code>org.w3c.dom.Node</code> value
     * @param pretty a <code>boolean</code> value whether to format the XML
     * @param indent a <code>String</code> value containing spaces as
     * initial indent, if null defaults to empty string.
     * @return a <code>StringBuffer</code> value
     * @deprecated Please use {@link XMLUtils#serializeNode(Node, Properties)} instead.
     */
    public static StringBuffer node2StringBuffer(Node node, boolean pretty, String indent) {
        return new StringBuffer(node2String(node, pretty));
    }
}