package org.openrosa.client.xpath; import java.io.Serializable; import java.util.Enumeration; import java.util.Vector; import com.google.gwt.xml.client.Element; import com.google.gwt.xml.client.Node; /** * @author Cosmin * */ //TODO: descendant axis doesn't work public class XPathLocationStep implements Serializable { /** * Generated serialization ID. */ private static final long serialVersionUID = 2652817312061137794L; String axis = null; String nodeTest = null; String nodePrefix = null; String functionName = null; String predicate = null; private void parseLocationStep(String locationStep) { // todo: should check if the whole xpath expression // is parameter to a function // todo: optimizations -> next.toCharArray !!! String next = locationStep; int pattIndex = 0; axis = ""; if (next.equals("//")) { nodeTest = "//"; return; } else if (next.equals("/")) { nodeTest = "/"; return; } // test if we have a relative path // for example: ../../../@zipcode // in this case the nodeTest will be null if (next.startsWith("..")) { axis = "parent"; nodeTest = ".."; } else if (next.startsWith(".")) { // don't know what axis to set here // child:: is probably incorrect nodeTest = "."; } else // test if we have an axis if (next.indexOf("::") == -1) if (next.startsWith("@")) { axis = "attribute"; next = new String(next.toCharArray(), 1, next.length() - 1); } else axis = "child"; else { pattIndex = next.indexOf("::"); if (pattIndex != -1) { axis = new String(next.toCharArray(), 0, pattIndex); next = new String(next.toCharArray(), pattIndex + 2, next .length() - pattIndex - 2); } } pattIndex = next.indexOf("["); if (pattIndex != -1) { nodeTest = new String(next.toCharArray(), 0, pattIndex); next = new String(next.toCharArray(), pattIndex + 1, next.length() - pattIndex - 1); pattIndex = next.lastIndexOf(']'); // pattIndex shouldn't be -1 in this case // maybe we should throw an exception?? // for now assume that the expression is // formed correctly predicate = new String(next.toCharArray(), 0, pattIndex); } else nodeTest = next; // test for prefix if ((pattIndex = nodeTest.indexOf(":")) != -1) { nodePrefix = new String(nodeTest.toCharArray(), 0, pattIndex); nodeTest = new String(nodeTest.toCharArray(), pattIndex + 1, next .length() - pattIndex - 1); } // System.out.println("this partial location: "+locationStep+" is parsed // into"); // System.out.println("functionName="+functionName+" axis="+axis+" // nodeTest="+nodeTest+" predicate="+predicate); } public XPathLocationStep(String locationStep) { parseLocationStep(locationStep); }// constructor /** * the contextNodeSet is made of nodes that are instances of Element A fix * is needed here: to the result vector I only add Element-s or String. This * is not correct. I should only add Node-s */ public Vector<?> getResult(Vector<Node> contextNodeSet, Vector<Object> resultNodeSet) { Vector<Object> outputNodeSet = resultNodeSet; int nodeCount = contextNodeSet.size(); int i = 0; if (axis.equals("child") || axis.equals("descendant")) { for (i = 0; i < nodeCount; i++) { Node node = (Node) contextNodeSet.elementAt(i); int childCount = 0; if(node != null && node.getChildNodes() != null) childCount = node.getChildNodes().getLength(); for (int j = 0; j < childCount; j++) { if (node.getChildNodes().item(j).getNodeType() == Node.ELEMENT_NODE) { Element childNode = (Element) node.getChildNodes().item(j); String childName = childNode.getNodeName(); //Small addition to cater for nodes with prefixes int pos = childName.indexOf(':'); if(pos >= 0) childName = childName.substring(pos+1); String prefix = null; if (nodePrefix != null) { Element element = (Element) childNode; prefix = element.getNamespaceURI(); } if (nodeTest.equals("*") || nodeTest.equalsIgnoreCase(childName) //TODO This was just changed from equals to make xpath expressions case insensitive || nodeTest.equals("node()")) { if (((nodePrefix != null) && (nodePrefix .equals(prefix))) || (nodePrefix == null)) outputNodeSet.addElement(childNode); } else if (nodeTest.equals("text()")) outputNodeSet.addElement(childNode.getChildNodes().item(0).getNodeValue()); if (axis.equals("descendant")) { Vector<?> descendants = null; descendants = getMatchingDescendants(childNode); for (int k = 0; k < descendants.size(); k++) outputNodeSet.addElement(descendants.elementAt(k)); } } else if (node.getChildNodes().item(j).getNodeType() == Node.TEXT_NODE) { if (nodeTest.equals("text()")) outputNodeSet.addElement(node.getChildNodes().item(j).getNodeValue()); } } } } if (axis.equals("parent")) { for (i = 0; i < nodeCount; i++) { Node cn = (Node) contextNodeSet.elementAt(i); if (cn instanceof Element) outputNodeSet.addElement(((Element) cn).getParentNode()); } } if (axis.equals("attribute")) { for (i = 0; i < nodeCount; i++) { Node n = (Node) contextNodeSet.elementAt(i); if (n instanceof Element) { String val = ((Element) n).getAttribute(nodeTest); if (val != null) outputNodeSet.addElement(val); } } } // other axes go here // no axis whatsoever (or maybe unknown to me :) if (axis.equals("")) { if (nodeTest.equals("/")) { Object startNode = null; // find first element in the contextNodeSet for (Enumeration<Node> nodes = contextNodeSet.elements(); nodes.hasMoreElements();) { startNode = nodes.nextElement(); if (startNode instanceof Element) break; } if (startNode instanceof Element) { //Element tmp = null; Node tmp = null; //while ( (((Element)startNode).getParentNode() instanceof Element) && //((tmp = (Element)((Element) startNode).getParentNode()) != null)) //while((tmp = (Element)((Element) startNode).getParentNode()) != null) // startNode = tmp; tmp = ((Node) startNode).getParentNode(); while(tmp != null){ startNode = tmp; tmp = ((Node) startNode).getParentNode(); } outputNodeSet.addElement(startNode); } else { // System.out.println("couldn't find root"); // couldn't find any elements in context return contextNodeSet; } } else if (nodeTest.equals(".")) { // simply copy the input vector for (Enumeration<?> enumeration = contextNodeSet.elements(); enumeration .hasMoreElements();) outputNodeSet.addElement(enumeration.nextElement()); } } if (predicate != null) { Predicate predicateEvaluator = new Predicate(outputNodeSet, predicate); outputNodeSet = predicateEvaluator.getResult(); } return outputNodeSet; } private Vector<?> getMatchingDescendants(Node node) { Vector<Node> matchingDescendants = new Vector<Node>(); int childCount = node.getChildNodes().getLength(); for (int j = 0; j < childCount; j++) { // this is were we test if the // node test part of our xpath expression // matches this node if (node.getChildNodes().item(j).getNodeType() == Node.ELEMENT_NODE) { Node childNode = (Node) node.getChildNodes().item(j); String name = ((Element) childNode).getNodeName(); if (nodeTest.equals("*") || nodeTest.equalsIgnoreCase(name)) //TODO This was just changed from equals to make xpath expression case insensitive matchingDescendants.addElement(node); Node[] moreDescendants = null; Vector<?> tmp = getMatchingDescendants(childNode); moreDescendants = new Node[tmp.size()]; tmp.copyInto(moreDescendants); tmp = null; for (int i = 0; i < moreDescendants.length; i++) matchingDescendants.addElement(moreDescendants[i]); } } return matchingDescendants; } }