/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
* Free SoftwareFoundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.xpath;
import com.caucho.loader.EnvironmentLocal;
import com.caucho.util.LruCache;
import com.caucho.xpath.pattern.AbstractPattern;
import com.caucho.xpath.pattern.FromContext;
import org.w3c.dom.Node;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Public facade for selecting nodes and creating match patterns.
*
* <p>Applications can select nodes directly from the XPath facade.
* For example,
* <code><pre>
* Node verse = XPath.find("chapter/verse", node);
* </pre></code>
*
* <p>For greater efficiency, applications can also precompile the
* match patterns.
* <code><pre>
* Pattern pattern = XPath.parseSelect("chapter/verse");
* Node verse = pattern.find(node);
* </pre></code>
*
* <p>XPath can also return values based on XPath expressions, following
* the XPath expression syntax. Applications can use the expressions for
* the equivalent of xsl:value-of
* <code><pre>
* Expr expr = XPath.parseExpr("chapter/verse/@id + 1");
* double value = expr.evalNumber(node);
* </pre></code>
*
* <p>To support the XPath pattern variables, XPath uses an environment
* object. Most applications will not need to use it.
*/
public class XPath {
private static final Logger log
= Logger.getLogger(XPath.class.getName());
private static EnvironmentLocal<LruCache<String,Pattern>> _matchCache
= new EnvironmentLocal<LruCache<String,Pattern>>();
private static EnvironmentLocal<LruCache<String,Pattern>> _selectCache
= new EnvironmentLocal<LruCache<String,Pattern>>();
private static EnvironmentLocal<LruCache<String,Expr>> _exprCache
= new EnvironmentLocal<LruCache<String,Expr>>();
private XPath()
{
}
/**
* Finds a node based on an XPath pattern. The pattern is relative
* to the node so <code>XPath.find("child", node)</code> will find children,
* not grandchildren.
*
* @param query XPath select pattern.
* @param node XML node to start searching from.
* @return The first matching node in document order.
*/
public static Node find(String query, Node node)
throws XPathException
{
Pattern pattern = parseSelect(query);
return (Node) pattern.find(node);
}
/**
* Selects all node matching an XPath pattern
*
* @param query XPath select pattern.
* @param node XML node to start searching from.
* @return An iterator of nodes matching the pattern.
*/
public static Iterator select(String query, Node node)
throws XPathException
{
Pattern pattern = parseSelect(query);
return pattern.select(node);
}
/**
* Create a node selection pattern. The pattern matches relative
* to the current node.
*
* @param query XPath select pattern.
* @return a pattern that can later select nodes.
*/
public static Pattern parseSelect(String query)
throws XPathParseException
{
LruCache<String,Pattern> cache = _selectCache.get();
if (cache == null)
cache = new LruCache<String,Pattern>(128);
Pattern pattern = cache.get(query);
if (pattern == null) {
pattern = parseSelect(query, null);
cache.put(query, pattern);
}
return pattern;
}
/**
* Create a node selection pattern. The pattern matches relative
* to the current node.
*
* <p>XSLT uses this version of parseSelect for proper namespace
* matching.
*
* @param query XPath select pattern.
* @param namespace the appropriate namespace mappings
*
* @return a pattern that can later select nodes.
*/
public static Pattern parseSelect(String query, NamespaceContext namespace)
throws XPathParseException
{
XPathParser parser = new XPathParser(query, namespace);
AbstractPattern pattern = parser.parseSelect();
if (log.isLoggable(Level.FINER))
log.finest("select: " + pattern);
return new Pattern(pattern);
}
/**
* Create a node match pattern. Match patterns are intended to test
* if a node matches the pattern. They do not work well for finding or
* selecting patterns. Essentially, a match pattern of 'foo[@bar]' is
* equivalent to a select pattern of '//foo[@bar]', but with less overhead.
*
* @param query XPath match pattern.
* @return a pattern that can later be used for isMatch.
*/
public static Pattern parseMatch(String query)
throws XPathParseException
{
LruCache<String,Pattern> cache = _matchCache.get();
if (cache == null)
cache = new LruCache<String,Pattern>(128);
Pattern pattern = cache.get(query);
if (pattern == null) {
pattern = parseMatch(query, null);
cache.put(query, pattern);
}
return pattern;
}
/**
* Create a node match pattern. Match patterns are intended to test
* if a node matches the pattern. They do not work well for finding or
* selecting patterns. Essentially, a match pattern of 'foo[@bar]' is
* equivalent to a select pattern of '//foo[@bar]', but with less overhead.
*
* @param query XPath match pattern.
* @param namespace the appropriate namespace mappings.
*
* @return a pattern that can later be used for isMatch.
*/
public static Pattern parseMatch(String query, NamespaceContext namespace)
throws XPathParseException
{
XPathParser parser = new XPathParser(query, namespace);
AbstractPattern pattern = parser.parseMatch();
if (log.isLoggable(Level.FINER))
log.finest("match: " + pattern);
return new Pattern(pattern);
}
/**
* Evaluates an XPath expression, returning a string. evalString works
* like the XSL <code>value-of</code> element.
*
* <p>For example, to get the value of an attribute use:
*
* <code><pre>
* String value = XPath.evalString("@id", node);
* </pre></code>
*
* @param query XPath expression
* @param node the node context
*
* @return the string result of the expression.
*/
public static String evalString(String query, Node node)
throws XPathException
{
Expr expr = parseExpr(query);
return expr.evalString(node);
}
/**
* Evaluates an XPath expression, returning a double.
*
* @param query XPath expression
* @param node the node context
*
* @return the number result of the expression.
*/
public static double evalNumber(String query, Node node)
throws XPathException
{
Expr expr = parseExpr(query);
return expr.evalNumber(node);
}
/**
* Evaluates an XPath expression, returning a boolean.
*
* @param query XPath expression
* @param node the node context
*
* @return the boolean result of the expression.
*/
public static boolean evalBoolean(String query, Node node)
throws XPathException
{
Expr expr = parseExpr(query);
return expr.evalBoolean(node);
}
/**
* Evaluates an XPath expression, returning an object
*
* @param query XPath expression
* @param node the node context
*
* @return the result of the expression.
*/
public static Object evalObject(String query, Node node)
throws XPathException
{
Expr expr = parseExpr(query);
return expr.evalObject(node);
}
/**
* Parses an XPath expression for later evaluation.
*
* @param query XPath expression
* @return the result of the expression.
*/
public static Expr parseExpr(String query)
throws XPathParseException
{
LruCache<String,Expr> cache = _exprCache.get();
if (cache == null) {
cache = new LruCache<String,Expr>(128);
_exprCache.set(cache);
}
Expr expr = cache.get(query);
if (expr == null) {
expr = parseExpr(query, null);
cache.put(query, expr);
}
return expr;
}
/**
* Parses an XPath expression for later evaluation.
*
* @param query XPath expression
* @param namespace namespace context
*
* @return the compiled expression
*/
public static Expr parseExpr(String query, NamespaceContext namespace)
throws XPathParseException
{
XPathParser parser = new XPathParser(query, namespace);
Expr expr = parser.parseExpr();
if (log.isLoggable(Level.FINER))
log.finest("expr: " + expr);
return expr;
}
/**
* Parses an XPath expression for later evaluation.
*
* @param query XPath expression
* @param namespace namespace context
* @param nodeList containing nodeList pattern
*
* @return the compiled expression
*/
public static Expr parseExpr(String query, NamespaceContext namespace,
AbstractPattern nodeList)
throws XPathParseException
{
XPathParser parser = new XPathParser(query, namespace);
Expr expr = parser.parseExpr(new FromContext(), nodeList);
if (expr != null)
expr.setListContext(nodeList);
if (log.isLoggable(Level.FINER))
log.finest("expr: " + expr);
return expr;
}
/**
* Creates a new variable environment.
*/
public static Env createEnv()
{
return Env.create();
}
/**
* Creates a new variable environment based on an old environment.
*
* <p>This lets environments share globals even through function calls.
*/
public static Env createEnv(Env global)
{
Env env = Env.create();
env.init(global);
return env;
}
/**
* Creates a new variable environment based on an old environment.
*
* <p>This lets environments share globals even through function calls.
*/
public static Env createCall(Env parent)
{
Env env = Env.create();
env.initMacro(parent);
return env;
}
/**
* Free an environment.
*/
public static void freeEnv(Env env)
{
// env.free();
}
}