blob: 69ca2aca8f940a179cdbfdefc58ce3e8e2d3995e [file] [log] [blame]
/*
* 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.
*
* @version $Id$
*/
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 != 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();) {
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 <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);
}
}
/**
* 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.
* @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.
* @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.
* @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.
* @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.
* @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++;
}
}
}
}
}