001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: * Free SoftwareFoundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Scott Ferguson
027: */
028:
029: package com.caucho.xpath;
030:
031: import com.caucho.loader.EnvironmentLocal;
032: import com.caucho.log.Log;
033: import com.caucho.util.LruCache;
034: import com.caucho.xpath.pattern.AbstractPattern;
035: import com.caucho.xpath.pattern.FromContext;
036:
037: import org.w3c.dom.Node;
038:
039: import java.util.Iterator;
040: import java.util.logging.Level;
041: import java.util.logging.Logger;
042:
043: /**
044: * Public facade for selecting nodes and creating match patterns.
045: *
046: * <p>Applications can select nodes directly from the XPath facade.
047: * For example,
048: * <code><pre>
049: * Node verse = XPath.find("chapter/verse", node);
050: * </pre></code>
051: *
052: * <p>For greater efficiency, applications can also precompile the
053: * match patterns.
054: * <code><pre>
055: * Pattern pattern = XPath.parseSelect("chapter/verse");
056: * Node verse = pattern.find(node);
057: * </pre></code>
058: *
059: * <p>XPath can also return values based on XPath expressions, following
060: * the XPath expression syntax. Applications can use the expressions for
061: * the equivalent of xsl:value-of
062: * <code><pre>
063: * Expr expr = XPath.parseExpr("chapter/verse/@id + 1");
064: * double value = expr.evalNumber(node);
065: * </pre></code>
066: *
067: * <p>To support the XPath pattern variables, XPath uses an environment
068: * object. Most applications will not need to use it.
069: */
070: public class XPath {
071: private static final Logger log = Log.open(XPath.class);
072:
073: private static EnvironmentLocal<LruCache<String, Pattern>> _matchCache = new EnvironmentLocal<LruCache<String, Pattern>>();
074:
075: private static EnvironmentLocal<LruCache<String, Pattern>> _selectCache = new EnvironmentLocal<LruCache<String, Pattern>>();
076:
077: private static EnvironmentLocal<LruCache<String, Expr>> _exprCache = new EnvironmentLocal<LruCache<String, Expr>>();
078:
079: private XPath() {
080: }
081:
082: /**
083: * Finds a node based on an XPath pattern. The pattern is relative
084: * to the node so <code>XPath.find("child", node)</code> will find children,
085: * not grandchildren.
086: *
087: * @param query XPath select pattern.
088: * @param node XML node to start searching from.
089: * @return The first matching node in document order.
090: */
091: public static Node find(String query, Node node)
092: throws XPathException {
093: Pattern pattern = parseSelect(query);
094:
095: return (Node) pattern.find(node);
096: }
097:
098: /**
099: * Selects all node matching an XPath pattern
100: *
101: * @param query XPath select pattern.
102: * @param node XML node to start searching from.
103: * @return An iterator of nodes matching the pattern.
104: */
105: public static Iterator select(String query, Node node)
106: throws XPathException {
107: Pattern pattern = parseSelect(query);
108:
109: return pattern.select(node);
110: }
111:
112: /**
113: * Create a node selection pattern. The pattern matches relative
114: * to the current node.
115: *
116: * @param query XPath select pattern.
117: * @return a pattern that can later select nodes.
118: */
119: public static Pattern parseSelect(String query)
120: throws XPathParseException {
121: LruCache<String, Pattern> cache = _selectCache.get();
122: if (cache == null)
123: cache = new LruCache<String, Pattern>(128);
124:
125: Pattern pattern = cache.get(query);
126:
127: if (pattern == null) {
128: pattern = parseSelect(query, null);
129: cache.put(query, pattern);
130: }
131:
132: return pattern;
133: }
134:
135: /**
136: * Create a node selection pattern. The pattern matches relative
137: * to the current node.
138: *
139: * <p>XSLT uses this version of parseSelect for proper namespace
140: * matching.
141: *
142: * @param query XPath select pattern.
143: * @param namespace the appropriate namespace mappings
144: *
145: * @return a pattern that can later select nodes.
146: */
147: public static Pattern parseSelect(String query,
148: NamespaceContext namespace) throws XPathParseException {
149: XPathParser parser = new XPathParser(query, namespace);
150:
151: AbstractPattern pattern = parser.parseSelect();
152:
153: if (log.isLoggable(Level.FINER))
154: log.finest("select: " + pattern);
155:
156: return new Pattern(pattern);
157: }
158:
159: /**
160: * Create a node match pattern. Match patterns are intended to test
161: * if a node matches the pattern. They do not work well for finding or
162: * selecting patterns. Essentially, a match pattern of 'foo[@bar]' is
163: * equivalent to a select pattern of '//foo[@bar]', but with less overhead.
164: *
165: * @param query XPath match pattern.
166: * @return a pattern that can later be used for isMatch.
167: */
168: public static Pattern parseMatch(String query)
169: throws XPathParseException {
170: LruCache<String, Pattern> cache = _matchCache.get();
171: if (cache == null)
172: cache = new LruCache<String, Pattern>(128);
173:
174: Pattern pattern = cache.get(query);
175:
176: if (pattern == null) {
177: pattern = parseMatch(query, null);
178: cache.put(query, pattern);
179: }
180:
181: return pattern;
182: }
183:
184: /**
185: * Create a node match pattern. Match patterns are intended to test
186: * if a node matches the pattern. They do not work well for finding or
187: * selecting patterns. Essentially, a match pattern of 'foo[@bar]' is
188: * equivalent to a select pattern of '//foo[@bar]', but with less overhead.
189: *
190: * @param query XPath match pattern.
191: * @param namespace the appropriate namespace mappings.
192: *
193: * @return a pattern that can later be used for isMatch.
194: */
195: public static Pattern parseMatch(String query,
196: NamespaceContext namespace) throws XPathParseException {
197: XPathParser parser = new XPathParser(query, namespace);
198:
199: AbstractPattern pattern = parser.parseMatch();
200:
201: if (log.isLoggable(Level.FINER))
202: log.finest("match: " + pattern);
203:
204: return new Pattern(pattern);
205: }
206:
207: /**
208: * Evaluates an XPath expression, returning a string. evalString works
209: * like the XSL <code>value-of</code> element.
210: *
211: * <p>For example, to get the value of an attribute use:
212: *
213: * <code><pre>
214: * String value = XPath.evalString("@id", node);
215: * </pre></code>
216: *
217: * @param query XPath expression
218: * @param node the node context
219: *
220: * @return the string result of the expression.
221: */
222: public static String evalString(String query, Node node)
223: throws XPathException {
224: Expr expr = parseExpr(query);
225:
226: return expr.evalString(node);
227: }
228:
229: /**
230: * Evaluates an XPath expression, returning a double.
231: *
232: * @param query XPath expression
233: * @param node the node context
234: *
235: * @return the number result of the expression.
236: */
237: public static double evalNumber(String query, Node node)
238: throws XPathException {
239: Expr expr = parseExpr(query);
240:
241: return expr.evalNumber(node);
242: }
243:
244: /**
245: * Evaluates an XPath expression, returning a boolean.
246: *
247: * @param query XPath expression
248: * @param node the node context
249: *
250: * @return the boolean result of the expression.
251: */
252: public static boolean evalBoolean(String query, Node node)
253: throws XPathException {
254: Expr expr = parseExpr(query);
255:
256: return expr.evalBoolean(node);
257: }
258:
259: /**
260: * Evaluates an XPath expression, returning an object
261: *
262: * @param query XPath expression
263: * @param node the node context
264: *
265: * @return the result of the expression.
266: */
267: public static Object evalObject(String query, Node node)
268: throws XPathException {
269: Expr expr = parseExpr(query);
270:
271: return expr.evalObject(node);
272: }
273:
274: /**
275: * Parses an XPath expression for later evaluation.
276: *
277: * @param query XPath expression
278: * @return the result of the expression.
279: */
280: public static Expr parseExpr(String query)
281: throws XPathParseException {
282: LruCache<String, Expr> cache = _exprCache.get();
283: if (cache == null)
284: cache = new LruCache<String, Expr>(128);
285:
286: Expr expr = cache.get(query);
287:
288: if (expr == null) {
289: expr = parseExpr(query, null);
290: cache.put(query, expr);
291: }
292:
293: return expr;
294: }
295:
296: /**
297: * Parses an XPath expression for later evaluation.
298: *
299: * @param query XPath expression
300: * @param namespace namespace context
301: *
302: * @return the compiled expression
303: */
304: public static Expr parseExpr(String query,
305: NamespaceContext namespace) throws XPathParseException {
306: XPathParser parser = new XPathParser(query, namespace);
307:
308: Expr expr = parser.parseExpr();
309:
310: if (log.isLoggable(Level.FINER))
311: log.finest("expr: " + expr);
312:
313: return expr;
314: }
315:
316: /**
317: * Parses an XPath expression for later evaluation.
318: *
319: * @param query XPath expression
320: * @param namespace namespace context
321: * @param nodeList containing nodeList pattern
322: *
323: * @return the compiled expression
324: */
325: public static Expr parseExpr(String query,
326: NamespaceContext namespace, AbstractPattern nodeList)
327: throws XPathParseException {
328: XPathParser parser = new XPathParser(query, namespace);
329:
330: Expr expr = parser.parseExpr(new FromContext(), nodeList);
331:
332: if (expr != null)
333: expr.setListContext(nodeList);
334:
335: if (log.isLoggable(Level.FINER))
336: log.finest("expr: " + expr);
337:
338: return expr;
339: }
340:
341: /**
342: * Creates a new variable environment.
343: */
344: public static Env createEnv() {
345: return Env.create();
346: }
347:
348: /**
349: * Creates a new variable environment based on an old environment.
350: *
351: * <p>This lets environments share globals even through function calls.
352: */
353: public static Env createEnv(Env global) {
354: Env env = Env.create();
355:
356: env.init(global);
357:
358: return env;
359: }
360:
361: /**
362: * Creates a new variable environment based on an old environment.
363: *
364: * <p>This lets environments share globals even through function calls.
365: */
366: public static Env createCall(Env parent) {
367: Env env = Env.create();
368:
369: env.initMacro(parent);
370:
371: return env;
372: }
373:
374: /**
375: * Free an environment.
376: */
377: public static void freeEnv(Env env) {
378: // env.free();
379: }
380: }
|