Added: cocoon/whiteboard/maven2/cocoon-flat-layout/cocoon-core/src/main/java/org/apache/cocoon/xml/dom/DOMUtil.java URL: http://svn.apache.org/viewcvs/cocoon/whiteboard/maven2/cocoon-flat-layout/cocoon-core/src/main/java/org/apache/cocoon/xml/dom/DOMUtil.java?rev=330548&view=auto ============================================================================== --- cocoon/whiteboard/maven2/cocoon-flat-layout/cocoon-core/src/main/java/org/apache/cocoon/xml/dom/DOMUtil.java (added) +++ cocoon/whiteboard/maven2/cocoon-flat-layout/cocoon-core/src/main/java/org/apache/cocoon/xml/dom/DOMUtil.java Thu Nov 3 05:41:06 2005 @@ -0,0 +1,1140 @@ +/* + * 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.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.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; + +/** + * This class is a utility class for miscellaneous DOM functions, like + * getting and setting values of nodes. + * + * @author Carsten Ziegeler + * @version $Id: DOMUtil.java 164876 2005-04-26 20:49:28Z vgritsenko $ +*/ +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 null + * is returned. + * + * @param root The node to start the search. + * @param path XPath search expression. + * @return The value of the node or null + */ + 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 defaultValue 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 defaultValue + */ + 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 defaultValue 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 defaultValue + */ + 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, null 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 defaultValue 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 = ""; + private static final String XML_ROOT_DEFINITION = XML_DEFINITION + ""; + + /** + * Get a document fragment from a Reader. + * 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(""); + + // now test if xml input start with + 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: + * value_one + * value_two + * A parameter can occur more than once with different values. + * If source 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 != 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 String : + * 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 XMLizable : + * outputs the value by calling v.toSax(contentHandler). + * + * @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 org.w3c.dom.Node : + * 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 java.util.Collection : + * 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 java.util.Map : + * 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) { + Iterator iterator = v.keySet().iterator(); + Node mapNode = parent.getOwnerDocument().createElementNS(null, "java.util.map"); + parent.appendChild(mapNode); + for (Iterator iter = v.entrySet().iterator(); iter.hasNext();) { + final 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 Object depending on its class : + *
    + *
  • if it's an array, call {@link #valueOf(Node, Object)} on all its elements,
  • + *
  • if it's class has a specific {@link #valueOf(Node, Object)} implementation, use it,
  • + *
  • else, output it's string representation.
  • + *
+ * + * @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); + } + } + + /** + * Return the Node from the DOM Node rootNode + * using the XPath expression path. + * 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 null + * 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 null + */ + 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 defaultValue 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 defaultValue + */ + 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. + * @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 defaultValue 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 defaultValue + */ + 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. + * @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, null + * 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++; + } + } + } + } + +} Propchange: cocoon/whiteboard/maven2/cocoon-flat-layout/cocoon-core/src/main/java/org/apache/cocoon/xml/dom/DOMUtil.java ------------------------------------------------------------------------------ svn:eol-style = native