// Make sure that an absolute XPath is applied on the actual root
// node
if (xpathStr.startsWith(XPATH_DELIM)) {
if (!xpathStr.substring(1, xpathStr.indexOf(XPATH_DELIM, 1)).
equals(context.getName())) {
throw new XPathException("Cannot apply an absolute XPath " +
"(" + xpathStr + ") to a context " +
"that doesn't match names with " +
"the defined root node");
} else {
// Trim off the root node name and leading delimiter
xpathStr =
xpathStr.substring(xpathStr.indexOf(XPATH_DELIM, 1) + 1);
}
}
// We now know that we are dealing with 'simple' XPaths. However,
// we don't cater for functions besides the text() function, so if we
// find a '(' we need to determine whether the function is text or not.
ODOMObservable odomObservable = null;
StringTokenizer tokenizer = new StringTokenizer(xpathStr, XPATH_DELIM);
// Create an XPath relative to the parent (tracked during path
// traversal). For example, with a context of 'a' and an XPath of
// 'b/c/d' the parent and relative XPath will change as follows for
// each token found:
//
// Parent XPath Token
// ------ ----- -----
// a b b
// a/b c c
// a/b/c d d
Element parent = context;
XPath xpath;
String xPathToken;
while (tokenizer.hasMoreTokens()) {
xPathToken = tokenizer.nextToken();
// @todo pass the namespaces on in a nicer way (e.g. have a protected constructor that takes the map and copies it)
xpath = new XPath(xPathToken, this.getNamespacesString());
// Check to see if the node or nodes already exist in the document.
List nodes = xpath.selectNodes(parent);
ODOMObservable node = null;
if ((nodes == null) || (nodes.size() == 0)) {
// Node wasn't found, so create one.
ODOMObservable result = null;
int predicateStart = xPathToken.indexOf(PREDICATE_START);
// Handle a predicate on the current path step if needed
if (predicateStart != -1) {
String predicate =
xPathToken.substring(predicateStart + 1,
xPathToken.
indexOf(PREDICATE_END));
if (!isPredicateValid(predicate)) {
throw new IllegalStateException(
"Unsupported predicate format: " + predicate);
}
String elementName = xPathToken.substring(0,
predicateStart);
while (node == null) {
// Create the correct number of nodes according to the
// number in the predicate field. This involves
// creating the node and checking to see if the
// original value can be found, if not create another
// and so on...
result = createNode((ODOMElement) parent,
factory,
elementName);
node = (ODOMObservable) xpath.selectSingleNode(parent);
}
} else {
// No predicate required on this step
result = createNode((ODOMElement) parent,
factory,
xPathToken);
}
// Since a new node has been created, we need to track this
// latest node creation (for the return value) and track
// the parent for the next path step (if the new node is an
// element and there are further steps)
if (result != null) {
odomObservable = result;
if (result instanceof Element) {
parent = (Element) result;
} else if (tokenizer.hasMoreElements()) {
throw new XPathException(
"XPath creation for \"" + xpath +
"\" terminated early because the new node \"" +
new ODOMXPath(result).getExternalForm() + "\" is " +
"not an element but should have sub-nodes " +
"created");
}
} else {
throw new XPathException("Unexpected null result while " +
"creating path step");
}
} else if (nodes.size() == 1) {
// Node was found so store it as the new parent but only if it
// is an Element).
node = (ODOMObservable) nodes.get(0);
if (node instanceof Element) {
parent = (Element) node;
} else if (tokenizer.hasMoreTokens()) {
throw new XPathException(
"XPath creation for \"" + xpath +
"\" terminated early because the existing node \"" +
new ODOMXPath(node).getExternalForm() + "\" is not " +
"an element");
}