/*
* © Copyright IBM Corp. 2012
*
* 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.
*/
/*
* Created on June 14, 2005
*
*/
package com.ibm.commons.xml.xpath.xml;
import java.util.Iterator;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.ibm.commons.util.StringUtil;
import com.ibm.commons.xml.DOMUtil;
import com.ibm.commons.xml.NamespaceContext;
import com.ibm.commons.xml.XMLException;
import com.ibm.commons.xml.XPathContext;
import com.ibm.commons.xml.XResult;
import com.ibm.commons.xml.XResultUtils;
import com.ibm.commons.xml.xpath.AbstractSimpleExpression;
import com.ibm.commons.xml.xpath.NodeListImpl;
import com.ibm.commons.xml.xpath.XPathException;
import com.ibm.commons.xml.xpath.part.AttributePart;
import com.ibm.commons.xml.xpath.part.IndexedElementPart;
import com.ibm.commons.xml.xpath.part.Part;
/**
* @author Mark Wallace
* @author Eugene Konstantinov
* @author Philippe Riand
*/
public class XmlSimpleExpression extends AbstractSimpleExpression {
public XmlSimpleExpression(String expression, boolean isFromRoot,
Part[] parts) {
super(expression, isFromRoot, parts);
}
/* (non-Javadoc)
* @see com.ibm.commons.xml.xpath.XPathExpression#supportsXPathContext()
*/
public boolean supportsXPathContext() {
return true;
}
/* (non-Javadoc)
* @see com.ibm.commons.xml.xpath.XPathExpression#pushXPathContext(java.lang.Object)
*/
public void pushXPathContext(Object node) throws XPathException {
try {
Document document = getDocument(node);
DOMUtil.pushXPathContext(document, getExpression());
}
catch (XMLException xe) {
throw new XPathException(xe);
}
}
/* (non-Javadoc)
* @see com.ibm.commons.xml.xpath.XPathExpression#popXPathContext(java.lang.Object)
*/
public void popXPathContext(Object node) throws XPathException {
try {
Document document = getDocument(node);
XPathContext context = DOMUtil.getXPathContext(document);
if (context != null && context.getExpression().equals(getExpression())) {
DOMUtil.popXPathContext(document);
}
}
catch (XMLException xe) {
throw new XPathException(xe);
}
}
/* (non-Javadoc)
* @see com.ibm.commons.xml.xpath.AbstractSimpleExpression#eval(java.lang.Object, com.ibm.commons.xml.NamespaceContext)
*/
protected XResult doEval(Object node, NamespaceContext namespaceContext) throws XPathException {
if (!isFromRoot() && node instanceof Document) {
Document document = (Document)node;
XPathContext pathContext = DOMUtil.getXPathContext(document);
if (pathContext != null) {
node = pathContext.getContextNodes();
// create the context nodes if they don't exist
if (node == null) {
try {
pathContext.createNodes();
}
catch (XMLException xe) {
throw new XPathException(xe);
}
node = pathContext.getContextNodes();
}
}
}
if (node == null) {
throw new XPathException(new NullPointerException("Cannot evaluate an XPath on a null object")); // $NLS-XmlSimpleExpression.CannotevaluateanXPathonanullobjec-1$
}
return super.doEval(node, namespaceContext);
}
/* (non-Javadoc)
* @see com.ibm.commons.xml.xpath.AbstractSimpleExpression#setValue(java.lang.Object, java.lang.Object, com.ibm.commons.xml.NamespaceContext, boolean)
*/
protected void doSetValue(Object node, Object value, NamespaceContext namespaceContext, boolean autoCreate) throws XPathException {
if (node instanceof Document) {
Document document = (Document)node;
XPathContext pathContext = DOMUtil.getXPathContext(document);
if (pathContext != null) {
node = pathContext.getContextNodes();
// create the nodes if auto creating
if (node == null && autoCreate) {
try {
pathContext.createNodes();
node = ((NodeList)pathContext.getContextNodes()).item(0);
}
catch (XMLException xe) {
throw new XPathException(xe);
}
}
}
}
super.doSetValue(node, value, namespaceContext, autoCreate);
}
/* (non-Javadoc)
* @see com.ibm.xfaces.xpath.XPathExpression#isReadOnly(java.lang.Object)
*/
public boolean isReadOnly(Object node) {
return false;
}
public NamespaceContext resolveNamespaceContext(Object node, NamespaceContext ns) {
if(node instanceof Node) {
return Utils.resolveNamespaceContext((Node)node,ns);
}
if(node instanceof NodeList) {
NodeList l = (NodeList)node;
if(l.getLength()>0) {
return Utils.resolveNamespaceContext(l.item(0),ns);
}
}
return null;
}
public Object getRootNode(Object node) {
if(node instanceof Node) {
Node n = (Node)node;
return DOMUtil.getOwnerDocument(n);
}
if(node instanceof NodeList) {
NodeList nl = (NodeList)node;
if(nl.getLength()>0) {
return DOMUtil.getOwnerDocument(nl.item(0));
}
}
return node;
}
/**
* Creates a new node
*
* @param node
* the parent node for the node beign created
* @param part
* the part of the XPath expression that describes the created
* node
*
* @return a created node or <code>null</code> if an event of failure
* @see AbstractSimpleExpression#createPart(Object, Part)
*/
protected Object createPart(Object node, Part part, NamespaceContext namespaceContext) throws XPathException {
if (node instanceof NodeListImpl) {
NodeListImpl nodeList = (NodeListImpl)node;
if (nodeList.getLength() == 1) {
node = nodeList.get(0);
}
}
if (node == null || !(node instanceof Node)) {
throw new XPathException(null,"Error creating part:" + part.toString() // $NLS-XmlSimpleExpression.ErrorcreatePART-1$
+ " Node must be of the type " + Node.class // $NLS-XmlSimpleExpression.Nodemustbeofthetype-1$
+ " but it was :" // $NLS-XmlSimpleExpression.BUTitwas-1$
+ ((node == null) ? null : node.getClass()));
}
Node nodeObj = (Node) node;
Document document = DOMUtil.getOwnerDocument(nodeObj);
Node newNode = null;
if (part instanceof AttributePart) {
if (StringUtil.isEmpty(part.getPrefix())) {
newNode = document.createAttribute(part.getName());
nodeObj.getAttributes().setNamedItem(newNode);
} else {
String nsURI = getNamespaceURI(namespaceContext, part.getPrefix());
newNode = document.createAttributeNS(nsURI, part.getName());
newNode.setPrefix(part.getPrefix());
nodeObj.getAttributes().setNamedItem(newNode);
setNamespaceAttr(newNode);
}
} else {
if (StringUtil.isEmpty(part.getPrefix())) {
newNode = document.createElement(part.getName());
nodeObj.appendChild(newNode);
} else {
String nsURI = getNamespaceURI(namespaceContext, part.getPrefix());
newNode = document.createElementNS(nsURI, part.getName());
newNode.setPrefix(part.getPrefix());
nodeObj.appendChild(newNode);
setNamespaceAttr(newNode);
}
}
return newNode;
}
/**
* Removes node from its parent
*
* @param node
* the node to be removed
* @param part
* not used
* @see AbstractSimpleExpression#deletePart(Object, Part)
*/
protected void deletePart(Object node, Part part) {
if (node instanceof NodeListImpl) {
NodeListImpl nodeList = (NodeListImpl)node;
if (nodeList.getLength() == 1) {
node = nodeList.get(0);
}
}
if (node instanceof Node) {
Node nodeObj = (Node) node;
Node parentNode = nodeObj.getParentNode();
if (parentNode != null) {
parentNode.removeChild(nodeObj);
}
}
}
/**
* Evaluates the part of XPath expression
*
* @param node
* evaluation starts from. It can be either a Node or a NodeListImpl
* @param part
* not used
* @see AbstractSimpleExpression#evaluatePart(Object, Part)
*/
protected Object evaluatePart(Object node, Part part, NamespaceContext namespaceContext) throws XPathException {
if(node==null) {
return null;
}
// In case of a list, then applies the part on each element
if( node instanceof NodeListImpl ) {
NodeListImpl result = new NodeListImpl(16);
for (Iterator iter = ((NodeListImpl) node).iterator(); iter.hasNext();) {
Object res = evaluatePart(iter.next(), part, namespaceContext);
if (res instanceof NodeListImpl) {
result.addAll((NodeListImpl) res);
} else if (res != null) {
result.add(res);
}
}
return (result.isEmpty()) ? null : result;
}
Node nodeObj = (Node) node;
if (part.getName().equals(".")) {
return nodeObj;
}
if (part.getName().equals("..")) {
return nodeObj.getParentNode();
}
if (part instanceof AttributePart) {
if (node instanceof Element) {
if (StringUtil.isEmpty(part.getPrefix())) {
return ((Element)node).getAttributeNode(part.getName());
} else {
String nsURI = getNamespaceURI(namespaceContext,part.getPrefix());
return ((Element)node).getAttributeNodeNS(nsURI, part.getName());
}
}
return null;
}
// process child nodes
if (node instanceof Document) {
Document doc = (Document) node;
Element elem = doc.getDocumentElement();
if (elem != null && equalsName(namespaceContext, part, elem)) {
return elem;
}
return null;
}
int index = 0;
if (part instanceof IndexedElementPart) {
index = ((IndexedElementPart) part).getIndex();
if (index < 1) {
String message = "Invalid predicate {0}, XPath predicates are 1-based."; // $NLS-XmlSimpleExpression.Invalidpredicate0XPathpredicatesa-1$
message = StringUtil.format(message, new Object[] { Integer.valueOf(index) });
throw new IllegalArgumentException(message);
}
}
NodeList content = nodeObj.getChildNodes();
Node resultObject = null;
NodeListImpl resultList = null;
int found = 0;
int len = content.getLength();
for (int i = 0; i < len; i++) {
Node childNode = content.item(i);
if (equalsName(namespaceContext, part, childNode)) {
found++;
if(index==0 || index==found) {
if(resultObject==null) {
resultObject = childNode;
} else {
if(resultList==null) {
resultList = new NodeListImpl();
resultList.add(resultObject);
}
resultList.add(childNode);
}
}
}
}
if (resultList!=null) {
return resultList;
}
return resultObject;
}
/**
* Sets up a node's value. If node is the element type it will atempt to set
* the TEXT node value instead. Otherwise, it sets up the value of the node
* itself
*
* @param node
* to be set with a new value
* @param part
* not used
* @param value
* String representing a new value
*
* @see AbstractSimpleExpression#setPart(Object, Part, Object)
*/
protected void setNodeValue(Object node, Object value) throws XPathException {
if (node == null) {
throw new XPathException(null, "Cannot set a value on an null XPath result" ); // $NLS-XmlSimpleExpression.CannotsetavalueonannullXPathresul-1$
}
if (node instanceof NodeListImpl) {
NodeListImpl nodeList = (NodeListImpl)node;
if (nodeList.getLength() == 1) {
node = nodeList.get(0);
}
else {
throw new XPathException(null, "Cannot set a value on a list of elements" ); // $NLS-XmlSimpleExpression.Cannotsetavalueonalistofelements-1$
}
}
Node nodeObj = (Node) node;
String strValue = (value == null) ? "" : value.toString(); //$NON-NLS-1$
DOMUtil.setTextValue(nodeObj, strValue);
}
/**
* Returns <code>true</code> if node is of the correct type for
* evaluation. The valid types are:
* <p>
* <ul>
* <li>org.w3c.dom.Node</li>
* <li>NodeListImpl</li>
* </ul>
*
*
* @param node
* the node to be check for validity
* @return true if node of the correct type or false otherwise
* @see com.ibm.xfaces.xpath.XPathExpression#isValid(java.lang.Object)
*/
public boolean isValid(Object node) {
if ( (node instanceof Node) || (node instanceof NodeListImpl)) {
return true;
}
return false;
}
/**
* Returns wrapper over the node that knows how to extract value.
* <p>
* This method should return a <code>com.ibm.xfaces.xpath.XNodeList</code>if node is instance of <code>org.w3c.dom.Node</code>
* or else it returns the node back presuming the node is already wrapped up e.g. <code>org.apache.xpath.objects.XObject</code>
* @param node
* node which is being wrapped up
* @return wrapper object
* @see com.ibm.xfaces.xpath.XPathExpression#valueOf(Object)
* @see com.ibm.xfaces.xpath.xml.NodeListImpl
*/
public XResult wrapUp(Object node) throws XPathException {
if(node == null ) {
return XResultUtils.emptyResult;
}
if (node instanceof Node){
return new XResultUtils.XMLNode((Node)node);
}
if( node instanceof NodeList ) {
NodeList nl = (NodeList)node;
int len = nl.getLength();
if(len==0) {
return XResultUtils.emptyResult;
} else if(len==1) {
return new XResultUtils.XMLNode(nl.item(0));
} else {
return new XResultUtils.XMLNodeList((NodeList)node);
}
}
throw new XPathException(null,"Internal error while executing simple path. Result:",node.toString()); // $NLS-XmlSimpleExpression.Internalerrorwhileexecutingsimple-1$
}
//
// Utility methods
//
protected void setNamespaceAttr(Node node) {
if (node == null) {
return;
}
try {
Document document = node.getOwnerDocument();
Element docElement = document.getDocumentElement();
if (docElement == null) {
return;
}
// add namespace ffrom new node
String prefix = node.getPrefix();
String namespaceURI = node.getNamespaceURI();
String qualifiedName = "xmlns:" + prefix; // $NON-NLS-1$
if (!docElement.hasAttribute(qualifiedName)) {
Attr attr = document.createAttributeNS(
"http://www.w3.org/2000/xmlns/", //$NON-NLS-1$
qualifiedName); //$NON-NLS-1$
attr.setNodeValue(namespaceURI);
docElement.setAttributeNode(attr);
}
}
catch (Exception e) {
e.printStackTrace();
}
}
protected Document getDocument(Object node) throws XMLException {
if (node instanceof Document) {
return (Document)node;
}
return ((Node)node).getOwnerDocument();
}
protected boolean equalsName(NamespaceContext namespaceContext, int idx, Node elem) {
if (idx >= getPartCount())
return false;
else
return equalsName(namespaceContext,getPart(idx), elem);
}
protected static String getNamespaceURI(NamespaceContext namespaceContext, String nsPrefix) {
if (namespaceContext != null) {
return namespaceContext.getNamespaceURI(nsPrefix);
}
return null;
}
protected static boolean equalsName(NamespaceContext namespaceContext, Part part, Node elem) {
if (elem == null) {
return false;
}
String localName = elem.getLocalName();
if (localName == null) {
return StringUtil.equals(part.getName(), elem.getNodeName());
}
if (StringUtil.equals(part.getName(), localName)) {
String elemNsURI = elem.getNamespaceURI();
if (namespaceContext != null
&& !StringUtil.isEmpty(part.getPrefix())) {
String partNsURI = getNamespaceURI(namespaceContext,part.getPrefix());
return StringUtil.equals(partNsURI, elemNsURI);
} else {
return true;
}
}
return false;
}
}