/**
* (The MIT License)
*
* Copyright (c) 2008 - 2014:
*
* * {Aaron Patterson}[http://tenderlovemaking.com]
* * {Mike Dalessio}[http://mike.daless.io]
* * {Charles Nutter}[http://blog.headius.com]
* * {Sergio Arbeo}[http://www.serabe.com]
* * {Patrick Mahoney}[http://polycrystal.org]
* * {Yoko Harada}[http://yokolet.blogspot.com]
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* 'Software'), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package nokogiri;
import static nokogiri.internals.NokogiriHelpers.getNokogiriClass;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Set;
import javax.xml.transform.TransformerException;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import nokogiri.internals.NokogiriNamespaceContext;
import nokogiri.internals.NokogiriXPathFunctionResolver;
import nokogiri.internals.NokogiriXPathVariableResolver;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyException;
import org.jruby.RubyObject;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.sun.org.apache.xml.internal.dtm.DTM;
import com.sun.org.apache.xml.internal.utils.PrefixResolver;
import com.sun.org.apache.xpath.internal.XPathContext;
import com.sun.org.apache.xpath.internal.jaxp.JAXPExtensionsProvider;
import com.sun.org.apache.xpath.internal.jaxp.JAXPPrefixResolver;
import com.sun.org.apache.xpath.internal.jaxp.JAXPVariableStack;
import com.sun.org.apache.xpath.internal.objects.XObject;
/**
* Class for Nokogiri::XML::XpathContext
*
* @author sergio
* @author Yoko Harada <yokolet@gmail.com>
* @author John Shahid <jvshahid@gmail.com>
*/
@JRubyClass(name="Nokogiri::XML::XPathContext")
public class XmlXpathContext extends RubyObject {
public final static String XPATH_CONTEXT = "CACHCED_XPATH_CONTEXT";
private XmlNode context;
private final NokogiriXPathFunctionResolver functionResolver;
private final NokogiriXPathVariableResolver variableResolver;
private PrefixResolver prefixResolver;
private XPathContext xpathSupport = null;
private NokogiriNamespaceContext nsContext;
public XmlXpathContext(Ruby ruby, RubyClass rubyClass) {
super(ruby, rubyClass);
functionResolver = NokogiriXPathFunctionResolver.create(ruby.getCurrentContext().nil);
variableResolver = NokogiriXPathVariableResolver.create();
}
private void setNode(XmlNode node) throws IllegalArgumentException, ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException {
Node doc = node.getNode().getOwnerDocument();
if (doc == null) {
doc = node.getNode();
}
xpathSupport = (XPathContext) doc.getUserData(XPATH_CONTEXT);
if (xpathSupport == null) {
JAXPExtensionsProvider jep = getProviderInstance();
xpathSupport = new XPathContext(jep);
xpathSupport.setVarStack(new JAXPVariableStack(variableResolver));
doc.setUserData(XPATH_CONTEXT, xpathSupport, null);
}
context = node;
nsContext = NokogiriNamespaceContext.create();
prefixResolver = new JAXPPrefixResolver(nsContext);
}
private JAXPExtensionsProvider getProviderInstance() throws ClassNotFoundException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
Class<?> clazz = Class.forName("com.sun.org.apache.xpath.internal.jaxp.JAXPExtensionsProvider");
Constructor[] constructors = clazz.getDeclaredConstructors();
for (int i = 0; i < constructors.length; i++) {
Class[] parameterTypes = constructors[i].getParameterTypes();
if (parameterTypes.length == 2) {
return (JAXPExtensionsProvider) constructors[i].newInstance(functionResolver, false);
} else if (parameterTypes.length == 1) {
return (JAXPExtensionsProvider) constructors[i].newInstance(functionResolver);
}
}
return null;
}
/**
* Create and return a copy of this object.
*
* @return a clone of this object
*/
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
@JRubyMethod(name = "new", meta = true)
public static IRubyObject rbNew(ThreadContext thread_context, IRubyObject klazz, IRubyObject node) {
XmlNode xmlNode = (XmlNode)node;
XmlXpathContext xmlXpathContext = (XmlXpathContext) NokogiriService.XML_XPATHCONTEXT_ALLOCATOR.allocate(thread_context.getRuntime(), (RubyClass)klazz);
XPathFactory.newInstance().newXPath();
try {
xmlXpathContext.setNode(xmlNode);
} catch (IllegalArgumentException e) {
throw thread_context.getRuntime().newRuntimeError(e.getMessage());
} catch (ClassNotFoundException e) {
throw thread_context.getRuntime().newRuntimeError(e.getMessage());
} catch (InstantiationException e) {
throw thread_context.getRuntime().newRuntimeError(e.getMessage());
} catch (IllegalAccessException e) {
throw thread_context.getRuntime().newRuntimeError(e.getMessage());
} catch (InvocationTargetException e) {
throw thread_context.getRuntime().newRuntimeError(e.getMessage());
}
return xmlXpathContext;
}
@JRubyMethod
public IRubyObject evaluate(ThreadContext thread_context, IRubyObject expr, IRubyObject handler) {
functionResolver.setHandler(handler);
String src = (String) expr.toJava(String.class);
if(!handler.isNil()) {
if (!isContainsPrefix(src)) {
Set<String> methodNames = handler.getMetaClass().getMethods().keySet();
for (String name : methodNames) {
src = src.replaceAll(name, NokogiriNamespaceContext.NOKOGIRI_PREFIX+":"+name);
}
}
}
return node_set(thread_context, src);
}
protected IRubyObject node_set(ThreadContext thread_context, String expr) {
try {
return tryGetNodeSet(thread_context, expr);
} catch (XPathExpressionException xpee) {
RubyException e = XmlSyntaxError.createXPathSyntaxError(getRuntime(), xpee);
throw new RaiseException(e);
}
}
private IRubyObject tryGetNodeSet(ThreadContext thread_context, String expr) throws XPathExpressionException {
XObject xobj = null;
Node contextNode = context.node;
try {
com.sun.org.apache.xpath.internal.XPath xpathInternal = new com.sun.org.apache.xpath.internal.XPath (expr, null,
prefixResolver, com.sun.org.apache.xpath.internal.XPath.SELECT );
// We always need to have a ContextNode with Xalan XPath implementation
// To allow simple expression evaluation like 1+1 we are setting
// dummy Document as Context Node
if ( contextNode == null )
xobj = xpathInternal.execute(xpathSupport, DTM.NULL, prefixResolver);
else
xobj = xpathInternal.execute(xpathSupport, contextNode, prefixResolver);
switch (xobj.getType()) {
case XObject.CLASS_BOOLEAN:
return thread_context.getRuntime().newBoolean(xobj.bool());
case XObject.CLASS_NUMBER:
return thread_context.getRuntime().newFloat(xobj.num());
case XObject.CLASS_NODESET:
NodeList nodeList = xobj.nodelist();
XmlNodeSet xmlNodeSet = (XmlNodeSet) NokogiriService.XML_NODESET_ALLOCATOR.allocate(getRuntime(), getNokogiriClass(getRuntime(), "Nokogiri::XML::NodeSet"));
xmlNodeSet.setNodeList(nodeList);
xmlNodeSet.initialize(thread_context.getRuntime(), context);
return xmlNodeSet;
default:
return thread_context.getRuntime().newString(xobj.str());
}
} catch(TransformerException ex) {
throw new XPathExpressionException(expr);
}
}
private boolean isContainsPrefix(String str) {
Set<String> prefixes = nsContext.getAllPrefixes();
for (String prefix : prefixes) {
if (str.contains(prefix + ":")) {
return true;
}
}
return false;
}
@JRubyMethod
public IRubyObject evaluate(ThreadContext context, IRubyObject expr) {
return this.evaluate(context, expr, context.getRuntime().getNil());
}
@JRubyMethod
public IRubyObject register_ns(ThreadContext context, IRubyObject prefix, IRubyObject uri) {
nsContext.registerNamespace((String)prefix.toJava(String.class), (String)uri.toJava(String.class));
return this;
}
@JRubyMethod
public IRubyObject register_variable(ThreadContext context, IRubyObject name, IRubyObject value) {
variableResolver.registerVariable((String)name.toJava(String.class), (String)value.toJava(String.class));
return this;
}
}