com.caucho.xpath.XPathParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of resin Show documentation
Show all versions of resin Show documentation
Resin Java Application Server
/*
* Copyright (c) 1998-2018 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.server.util.CauchoSystem;
import com.caucho.util.CharBuffer;
import com.caucho.util.IntMap;
import com.caucho.util.L10N;
import com.caucho.xml.XmlChar;
import com.caucho.xpath.expr.*;
import com.caucho.xpath.functions.BaseURI;
import com.caucho.xpath.functions.ResolveURI;
import com.caucho.xpath.functions.Trace;
import com.caucho.xpath.pattern.*;
import org.w3c.dom.Node;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.Logger;
/**
* Parses an XPath expression.
*/
class XPathParser {
private static final Logger log
= Logger.getLogger(XPathParser.class.getName());
private static final L10N L = new L10N(XPathParser.class);
private final static int ANCESTOR_AXIS = 0;
private final static int ANCESTOR_OR_SELF_AXIS = ANCESTOR_AXIS + 1;
private final static int ATTRIBUTE_AXIS = ANCESTOR_OR_SELF_AXIS + 1;
private final static int CHILD_AXIS = ATTRIBUTE_AXIS + 1;
private final static int DESCENDANT_AXIS = CHILD_AXIS + 1;
private final static int DESCENDANT_OR_SELF_AXIS = DESCENDANT_AXIS + 1;
private final static int FOLLOWING_AXIS = DESCENDANT_OR_SELF_AXIS + 1;
private final static int FOLLOWING_SIBLING_AXIS = FOLLOWING_AXIS + 1;
private final static int NAMESPACE_AXIS = FOLLOWING_SIBLING_AXIS + 1;
private final static int PARENT_AXIS = NAMESPACE_AXIS + 1;
private final static int PRECEDING_AXIS = PARENT_AXIS + 1;
private final static int PRECEDING_SIBLING_AXIS = PRECEDING_AXIS + 1;
private final static int SELF_AXIS = PRECEDING_SIBLING_AXIS + 1;
private final static int TEXT = Expr.LAST_FUN + 1;
private final static int COMMENT = TEXT + 1;
private final static int ER = COMMENT + 1;
private final static int PI = ER + 1;
private final static int NODE = PI + 1;
private final static int CURRENT = NODE + 1;
private final static int NODE_TEXT = CURRENT + 1;
private final static int CONTEXT = NODE_TEXT + 1;
private static IntMap exprFunctions;
private static IntMap axisMap;
private static HashMap _exprFunctions;
private CharBuffer tag = new CharBuffer();
private String _string;
private int index;
private int peek;
private NamespaceContext _namespace;
XPathParser(String string, NamespaceContext namespace)
{
_string = string;
_namespace = namespace;
}
/**
* Parse a select pattern, i.e. a path rooted in a context.
*/
AbstractPattern parseSelect()
throws XPathParseException
{
AbstractPattern top = new FromContext();
AbstractPattern pattern = parseUnion(parseTop(top), top);
if (index < _string.length())
throw error(L.l("unexpected character at `{0}'", badChar(read())));
return pattern;
}
/**
* Parse a match pattern, i.e. a path with no root.
*/
AbstractPattern parseMatch()
throws XPathParseException
{
AbstractPattern root = new FromAny();
AbstractPattern pattern = parseUnion(parseTop(root), root);
if (index < _string.length())
throw error(L.l("unexpected character at `{0}'", badChar(read())));
return pattern;
}
/**
* Parse an expression.
*/
Expr parseExpr()
throws XPathParseException
{
Expr expr = parseExpr(null, null);
if (index < _string.length())
throw error(L.l("unexpected character at `{0}'", badChar(read())));
return expr;
}
private AbstractPattern parseStep(AbstractPattern root)
throws XPathParseException
{
return parseUnion(parseTop(root), root);
}
private AbstractPattern parseUnion(AbstractPattern left, AbstractPattern root)
throws XPathParseException
{
int ch = skipWhitespace(read());
while (ch >= 0) {
if (ch == '|') {
AbstractPattern tail = parseUnion(parseTop(root), root);
left = new UnionPattern(left, tail);
} else
break;
for (ch = read(); XmlChar.isWhitespace(ch); ch = read()) {
}
}
unread();
return left;
}
/**
* Parses the top expression.
*
*
* top ::= (expr)
* ::= term
*
*/
private AbstractPattern parseTop(AbstractPattern pattern)
throws XPathParseException
{
int ch = skipWhitespace(read());
unread();
if (ch == '(') {
Expr expr = parseTerm(pattern, pattern);
// If the expression is really a pattern and the iterator is
// ascending, then just unwrap it.
if (expr instanceof NodeSetExpr) {
AbstractPattern nodeSet = ((NodeSetExpr) expr).getPattern();
if (nodeSet.isAscending())
return nodeSet;
}
return new FromExpr(null, expr);
}
else
return parseTerm(pattern, pattern).toNodeList();
}
private AbstractPattern parseBasisTop(AbstractPattern pattern)
throws XPathParseException
{
int ch;
ch = skipWhitespace(read());
if (ch == '/') {
ch = read();
if (ch == '/') {
pattern = new FromRoot();
pattern = new FromDescendants(pattern, false);
pattern = parseBasis(pattern);
pattern = parseFilter(pattern);
pattern = parsePath(pattern);
return pattern;
}
pattern = new FromRoot();
ch = skipWhitespace(ch);
if (ch == -1)
return pattern;
}
unread();
if (pattern == null)
pattern = new FromContext();
pattern = parseBasis(pattern);
pattern = parseFilter(pattern);
return parsePath(pattern);
}
/**
* path ::= top ('/' filter)*
*/
private AbstractPattern parsePath(AbstractPattern pattern)
throws XPathParseException
{
int ch = skipWhitespace(read());
while (ch == '/') {
ch = read();
if (ch == '/') {
pattern = new FromDescendants(pattern, false);
pattern = parseBasis(pattern);
pattern = parseFilter(pattern);
}
else {
unread();
pattern = parseBasis(pattern);
pattern = parseFilter(pattern);
}
ch = skipWhitespace(read());
}
unread();
return pattern;
}
/**
* filter ::= atom('[' expr ']')*
*/
private AbstractPattern parseFilter(AbstractPattern pattern)
throws XPathParseException
{
int ch = skipWhitespace(read());
while (ch == '[') {
AbstractPattern context = new FromContext();
Expr expr = parseExpr(context, pattern);
pattern = new FilterPattern(pattern, expr);
ch = skipWhitespace(read());
if (ch != ']')
throw error(L.l("expected `{0}' at {1}", "]", badChar(ch)));
ch = skipWhitespace(read());
}
unread();
return pattern;
}
/**
* basis ::= name::node-test
* | node-test
* | @node-test
* | .
* | ..
* ;
*/
private AbstractPattern parseBasis(AbstractPattern pattern)
throws XPathParseException
{
boolean fromChildren = true;
int ch = skipWhitespace(read());
int nodeType = Node.ELEMENT_NODE;
String namespace = null;
tag.clear();
if (ch == '@') {
if (pattern instanceof FromDescendants)
pattern = NodeTypePattern.create(pattern, NodeTypePattern.NODE);
pattern = new FromAttributes(pattern);
nodeType = Node.ATTRIBUTE_NODE;
fromChildren = false;
ch = read();
}
else if (ch == '.') {
ch = read();
if (ch == '.')
return NodeTypePattern.create(new FromParent(pattern),
NodeTypePattern.NODE);
else {
unread();
if (pattern != null)
return pattern;
else
return NodeTypePattern.create(new FromSelf(pattern), NodeTypePattern.ANY);
}
}
else if (ch == '(') {
// XXX: not strictly correct for counting
Expr expr = parseExpr(null, null);
if ((ch = read()) != ')')
throw error(L.l("expected `{0}' at {1}", ")", badChar(ch)));
return new FromExpr(pattern, expr);
}
if (ch == '*') {
tag.append('*');
}
else if (XmlChar.isNameStart(ch)) {
for (; XmlChar.isNameChar(ch); ch = read())
tag.append((char) ch);
if (ch == '*' && tag.endsWith(":"))
tag.append('*');
else
unread();
}
else
unread();
String name = tag.toString();
if (name.equals(""))
throw error(L.l("expected name at {0}", badChar(ch)));
return parseAxis(pattern, name, fromChildren, nodeType);
}
private AbstractPattern parseAxis(AbstractPattern pattern, String name,
boolean fromChildren, int nodeType)
throws XPathParseException
{
String axis = "";
int axisIndex = name.indexOf("::");
if (axisIndex >= 0 && nodeType != Node.ATTRIBUTE_NODE) {
axis = name.substring(0, axisIndex);
name = name.substring(axisIndex + 2);
}
if (pattern instanceof FromDescendants)
return parseNodeTest(pattern, name, false, Node.ELEMENT_NODE);
switch (axisMap.get(axis)) {
case ANCESTOR_AXIS:
return parseNodeTest(new FromAncestors(pattern, false),
name, false, Node.ELEMENT_NODE);
case ANCESTOR_OR_SELF_AXIS:
return parseNodeTest(new FromAncestors(pattern, true),
name, false, Node.ELEMENT_NODE);
case ATTRIBUTE_AXIS:
return parseNodeTest(new FromAttributes(pattern),
name, false, Node.ATTRIBUTE_NODE);
case CHILD_AXIS:
return parseNodeTest(new FromChildren(pattern),
name, false, Node.ELEMENT_NODE);
case DESCENDANT_AXIS:
return parseNodeTest(new FromDescendants(pattern, false),
name, false, Node.ELEMENT_NODE);
case DESCENDANT_OR_SELF_AXIS:
return parseNodeTest(new FromDescendants(pattern, true),
name, false, Node.ELEMENT_NODE);
case FOLLOWING_AXIS:
return parseNodeTest(new FromNext(pattern),
name, false, Node.ELEMENT_NODE);
case FOLLOWING_SIBLING_AXIS:
return parseNodeTest(new FromNextSibling(pattern),
name, false, Node.ELEMENT_NODE);
case NAMESPACE_AXIS:
return parseNodeTest(new FromNamespace(pattern),
name, false, Node.ATTRIBUTE_NODE);
case PARENT_AXIS:
return parseNodeTest(new FromParent(pattern),
name, false, Node.ELEMENT_NODE);
case PRECEDING_AXIS:
return parseNodeTest(new FromPrevious(pattern),
name, false, Node.ELEMENT_NODE);
case PRECEDING_SIBLING_AXIS:
return parseNodeTest(new FromPreviousSibling(pattern),
name, false, Node.ELEMENT_NODE);
case SELF_AXIS:
return parseNodeTest(new FromSelf(pattern),
name, false, Node.ELEMENT_NODE);
default:
return parseNodeTest(pattern, name, fromChildren, nodeType);
}
}
/**
* node-test ::= qname
* | *:name
* | *
* | name '(' args ')'
* ;
*/
private AbstractPattern parseNodeTest(AbstractPattern pattern, String name,
boolean fromChildren, int nodeType)
throws XPathParseException
{
int ch = skipWhitespace(read());
AbstractPattern tagPattern;
if (ch == '(') {
Expr expr = parseFunction(pattern, pattern, name, fromChildren);
return expr.toNodeList();
}
else if (ch == '{') {
tag.clear();
while ((ch = read()) >= 0 && ch != '}')
tag.append((char) ch);
String url = tag.toString();
tag.clear();
for (ch = read(); XmlChar.isNameChar(ch); ch = read())
tag.append((char) ch);
pattern = new NSNamePattern(pattern, url, tag.toString(), nodeType);
}
else {
if (fromChildren)
pattern = new FromChildren(pattern);
if (name.equals("*"))
pattern = NodeTypePattern.create(pattern, nodeType);
else if (name.endsWith(":*")) {
pattern = new NamespacePattern(pattern,
name.substring(0, name.length() - 2),
nodeType);
}
else {
int p = name.indexOf(':');
String ns = null;
String local = name;
if (p > 0) {
String prefix = name.substring(0, p);
ns = NamespaceContext.find(_namespace, prefix);
local = name.substring(p + 1);
}
else if (nodeType != Node.ATTRIBUTE_NODE)
ns = _namespace.find(_namespace, "");
else
ns = null; // _namespace.find(_namespace, "");
if (ns == null)
pattern = new NodePattern(pattern, name, nodeType);
else
pattern = new NSNamePattern(pattern, ns, local, nodeType);
}
}
unread();
return pattern;
}
/**
* expr ::= or-expr
*/
Expr parseExpr(AbstractPattern parent, AbstractPattern listParent)
throws XPathParseException
{
peek = -1;
Expr left = parseTerm(parent, listParent);
while (true) {
int token = scanToken();
switch (token) {
case Expr.OR:
left = parseOrExpr(token, left, parseTerm(parent, listParent),
parent, listParent);
break;
case Expr.AND:
left = parseAndExpr(token, left, parseTerm(parent, listParent),
parent, listParent);
break;
case Expr.EQ: case Expr.NEQ: case Expr.LT:
case Expr.LE: case Expr.GT: case Expr.GE:
left = parseCmpExpr(token, left, parseTerm(parent, listParent),
parent, listParent);
break;
case Expr.ADD: case Expr.SUB:
left = parseAddExpr(token, left, parseTerm(parent, listParent),
parent, listParent);
break;
case Expr.MUL: case Expr.DIV: case Expr.QUO: case Expr.MOD:
left = parseMulExpr(token, left, parseTerm(parent, listParent),
parent, listParent);
break;
default:
return left;
}
}
}
/**
* or-expr ::= or-expr 'OR' expr
* | and-expr
*/
private Expr parseOrExpr(int code, Expr left, Expr right,
AbstractPattern parent, AbstractPattern listParent)
throws XPathParseException
{
while (true) {
int token = scanToken();
switch (token) {
case Expr.OR:
left = new BooleanExpr(code, left, right);
code = token;
right = parseTerm(parent, listParent);
break;
case Expr.AND:
right = parseAndExpr(token, right, parseTerm(parent, listParent),
parent, listParent);
break;
case Expr.EQ: case Expr.NEQ: case Expr.LT:
case Expr.LE: case Expr.GT: case Expr.GE:
right = parseCmpExpr(token, right, parseTerm(parent, listParent),
parent, listParent);
break;
case Expr.ADD: case Expr.SUB:
right = parseAddExpr(token, right, parseTerm(parent, listParent),
parent, listParent);
break;
case Expr.MUL: case Expr.DIV: case Expr.QUO: case Expr.MOD:
right = parseMulExpr(token, right, parseTerm(parent, listParent),
parent, listParent);
break;
default:
undoToken(token);
return new BooleanExpr(code, left, right);
}
}
}
/**
* and-expr ::= and-expr 'AND' expr
* | cmp-expr
*/
private Expr parseAndExpr(int code, Expr left, Expr right,
AbstractPattern parent, AbstractPattern listParent)
throws XPathParseException
{
while (true) {
int token = scanToken();
switch (token) {
case Expr.AND:
left = new BooleanExpr(code, left, right);
code = token;
right = parseTerm(parent, listParent);
break;
case Expr.EQ: case Expr.NEQ: case Expr.LT:
case Expr.LE: case Expr.GT: case Expr.GE:
right = parseCmpExpr(token, right, parseTerm(parent, listParent),
parent, listParent);
break;
case Expr.ADD: case Expr.SUB:
right = parseAddExpr(token, right, parseTerm(parent, listParent),
parent, listParent);
break;
case Expr.MUL: case Expr.DIV: case Expr.QUO: case Expr.MOD:
right = parseMulExpr(token, right, parseTerm(parent, listParent),
parent, listParent);
break;
default:
undoToken(token);
return new BooleanExpr(code, left, right);
}
}
}
/**
* cmp-expr ::= cmp-expr '<' expr
* | add-expr
*/
private Expr parseCmpExpr(int code, Expr left, Expr right,
AbstractPattern parent, AbstractPattern listParent)
throws XPathParseException
{
while (true) {
int token = scanToken();
switch (token) {
case Expr.EQ: case Expr.NEQ: case Expr.LT:
case Expr.LE: case Expr.GT: case Expr.GE:
left = new BooleanExpr(code, left, right);
code = token;
right = parseTerm(parent, listParent);
break;
case Expr.ADD: case Expr.SUB:
right = parseAddExpr(token, right, parseTerm(parent, listParent),
parent, listParent);
break;
case Expr.MUL: case Expr.DIV: case Expr.QUO: case Expr.MOD:
right = parseMulExpr(token, right, parseTerm(parent, listParent),
parent, listParent);
break;
default:
undoToken(token);
return new BooleanExpr(code, left, right);
}
}
}
/**
* add-expr ::= add-expr '+' expr
* | mul-expr
*/
private Expr parseAddExpr(int code, Expr left, Expr right,
AbstractPattern parent, AbstractPattern listParent)
throws XPathParseException
{
while (true) {
int token = scanToken();
switch (token) {
case Expr.ADD: case Expr.SUB:
left = new NumericExpr(code, left, right);
code = token;
right = parseTerm(parent, listParent);
break;
case Expr.MUL: case Expr.DIV: case Expr.QUO: case Expr.MOD:
right = parseMulExpr(token, right, parseTerm(parent, listParent),
parent, listParent);
break;
default:
undoToken(token);
return new NumericExpr(code, left, right);
}
}
}
/**
* mul-expr ::= mul-expr '*' expr
* | term
*/
private Expr parseMulExpr(int code, Expr left, Expr right,
AbstractPattern parent, AbstractPattern listParent)
throws XPathParseException
{
while (true) {
int token = scanToken();
switch (token) {
case Expr.MUL: case Expr.DIV: case Expr.QUO: case Expr.MOD:
left = new NumericExpr(code, left, right);
code = token;
right = parseTerm(parent, listParent);
break;
default:
undoToken(token);
return new NumericExpr(code, left, right);
}
}
}
/**
* term ::= simple-term path?
*/
private Expr parseTerm(AbstractPattern parent, AbstractPattern listParent)
throws XPathParseException
{
int ch = skipWhitespace(read());
unread();
Expr expr = parseSimpleTerm(parent, listParent);
int nextCh = skipWhitespace(read());
unread();
if (nextCh == '/' || nextCh == '[') {
AbstractPattern pattern = expr.toNodeList();
if (ch == '(' && ! pattern.isStrictlyAscending())
pattern = new FromExpr(null, expr);
return NodeSetExpr.create(parseUnion(parsePath(parseFilter(pattern)),
pattern));
}
else if (nextCh == '|') {
AbstractPattern pattern = expr.toNodeList();
return NodeSetExpr.create(parseUnion(parsePath(parseFilter(pattern)),
listParent));
}
else
return expr;
}
/**
* simple-term ::= number
* ::= '(' expr ')'
* ::= '$' variable
* ::= '"' string '"'
* ::= node-set
*/
private Expr parseSimpleTerm(AbstractPattern parent, AbstractPattern listParent)
throws XPathParseException
{
int ch = read();
ch = skipWhitespace(ch);
switch (ch) {
case '.':
ch = read();
unread();
unread();
if (! ('0' <= ch && ch <= '9')) {
return NodeSetExpr.create(parseUnion(parseBasisTop(parent), parent));
}
ch = read();
// fall through to parse the number
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
{
long value = 0;
double exp = 1;
int digits = 0;
for (; ch >= '0' && ch <= '9'; ch = read())
value = 10 * value + ch - '0';
if (ch == '.') {
for (ch = read(); ch >= '0' && ch <= '9'; ch = read()) {
value = 10 * value + ch - '0';
exp *= 10;
digits--;
}
}
if (ch == 'e' || ch == 'E') {
int sign = 1;
int expValue = 0;
ch = read();
if (ch == '-') {
sign = -1;
ch = read();
}
else if (ch == '+')
ch = read();
for (; ch >= '0' && ch <= '9'; ch = read())
expValue = 10 * expValue + ch - '0';
exp = Math.pow(10, digits + sign * expValue);
unread();
return new NumericExpr((double) value * (double) exp);
}
unread();
return new NumericExpr((double) value / (double) exp);
}
case '-':
return new NumericExpr(Expr.NEG, parseTerm(parent, listParent));
case '+':
return parseTerm(parent, listParent);
case '(':
{
Expr expr = parseExpr(parent, listParent);
if ((ch = skipWhitespace(read())) != ')')
throw error(L.l("expected `{0}' at {1}", ")", badChar(ch)));
return expr;
}
case '/': case '@': case '*':
unread();
return NodeSetExpr.create(parseUnion(parseBasisTop(parent), parent));
case '\'': case '"':
{
int end = ch;
CharBuffer cb = new CharBuffer();
for (ch = read(); ch >= 0; ch = read()) {
if (ch != end)
cb.append((char) ch);
else if ((ch = read()) == end)
cb.append((char) ch);
else {
unread();
break;
}
}
return new StringExpr(cb.toString());
}
case '$':
{
String name = readName(read());
return new VarExpr(name);
}
default:
if (! XmlChar.isNameStart(ch))
throw error(L.l("unknown character at {0}", badChar(ch)));
String name = readName(ch);
ch = skipWhitespace(read());
int axisIndex = name.indexOf("::");
// make sure axis are treated as node sets
if (ch == '(' && axisIndex < 0) {
return parseFunction(parent, listParent, name, true);
}
else if (ch == '(') {
String axis = name.substring(0, axisIndex);
if (axisMap.get(axis) <= 0)
return parseFunction(parent, listParent, name, true);
}
unread();
if (parent == null)
parent = new FromContext();
return parseNodeSetExpr(parent, name, Node.ELEMENT_NODE);
}
}
/**
* function ::= name '(' args ')'
*
* The XPath library functions are hard-coded for a little extra
* execution efficiency.
*/
Expr parseFunction(AbstractPattern parent,
AbstractPattern listParent,
String name,
boolean fromChildren)
throws XPathParseException
{
int ch = skipWhitespace(read());
ArrayList args = new ArrayList();
for (; ch >= 0 && ch != ')'; ch = skipWhitespace(read())) {
if (ch != ',')
unread();
Expr expr = parseExpr(parent, listParent);
if (expr == null)
throw error(L.l("null expression"));
args.add(expr);
}
int code = exprFunctions.get(name);
switch (code) {
case Expr.TRUE: case Expr.FALSE: case Expr.NOT: case Expr.BOOLEAN:
case Expr.STARTS_WITH: case Expr.CONTAINS: case Expr.LANG:
case Expr.FUNCTION_AVAILABLE:
return new BooleanExpr(code, args);
case Expr.NUMBER: case Expr.FLOOR: case Expr.CEILING: case Expr.ROUND:
case Expr.STRING_LENGTH:
return new NumericExpr(code, args);
case Expr.POSITION:
case Expr.LAST:
return new NumericExpr(code, listParent);
case Expr.COUNT: case Expr.SUM:
if (args.size() == 0)
args.add(NodeSetExpr.create(new FromContext()));
return new NumericExpr(code, ((Expr) args.get(0)).toNodeList());
case Expr.CONCAT:
case Expr.SUBSTRING_BEFORE:
case Expr.SUBSTRING: case Expr.SUBSTRING_AFTER:
case Expr.TRANSLATE:
case Expr.SYSTEM_PROPERTY:
return new StringExpr(code, args);
case Expr.STRING: case Expr.NORMALIZE:
return new StringExpr(code, args);
case Expr.LOCAL_PART: case Expr.NAMESPACE:
case Expr.QNAME: case Expr.GENERATE_ID:
if (args.size() == 0)
args.add(NodeSetExpr.create(new FromContext()));
return new StringExpr(code, args);
case Expr.ID:
if (args.size() == 0) {
args.add(NodeSetExpr.create(parent));
return new IdExpr(args);
}
else
return new IdExpr(args);
case Expr.IF:
if (args.size() != 3)
throw error(L.l("`if' needs three args."));
return new ObjectExpr(code, args);
case Expr.BASE_URI:
if (args.size() != 1)
throw error(L.l("`base-uri' needs one args."));
return new StringExpr(code, args.get(0));
case TEXT:
if (fromChildren)
parent = new FromChildren(parent);
AbstractPattern pattern = NodeTypePattern.create(parent, Node.TEXT_NODE);
return NodeSetExpr.create(pattern);
case COMMENT:
if (fromChildren)
parent = new FromChildren(parent);
pattern = NodeTypePattern.create(parent, Node.COMMENT_NODE);
return NodeSetExpr.create(pattern);
case ER:
if (fromChildren)
parent = new FromChildren(parent);
pattern = NodeTypePattern.create(parent, Node.ENTITY_REFERENCE_NODE);
return NodeSetExpr.create(pattern);
case PI:
if (fromChildren)
parent = new FromChildren(parent);
if (args.size() == 1) {
Expr expr = (Expr) args.get(0);
String value = null;
if (expr instanceof StringExpr)
value = ((StringExpr) expr).getValue();
if (value == null)
throw error(L.l("processing-instruction expects string literal"));
pattern = new NodePattern(parent, value,
Node.PROCESSING_INSTRUCTION_NODE);
}
else
pattern = NodeTypePattern.create(parent, Node.PROCESSING_INSTRUCTION_NODE);
return NodeSetExpr.create(pattern);
case NODE:
if (fromChildren)
parent = new FromChildren(parent);
pattern = NodeTypePattern.create(parent, NodeTypePattern.NODE);
return NodeSetExpr.create(pattern);
case CURRENT:
return NodeSetExpr.create(new CurrentPattern());
case CONTEXT:
return NodeSetExpr.create(new FromContext());
default:
Expr function = constructorFunction(name, args);
if (function != null)
return function;
int p = name.lastIndexOf(':');
String prefix;
if (p > 0)
prefix = name.substring(0, p);
else
prefix = "";
String context = NamespaceContext.find(_namespace, prefix);
if (context == null) {
}
else if (context.startsWith("java:"))
name = context + "." + name.substring(p + 1);
else if (context.indexOf(':') < 0)
name = "java:" + context + "." + name.substring(p + 1);
if (name.startsWith("java:")) {
p = name.lastIndexOf('.');
if (p < 0)
throw error(L.l("`{0}' is an illegal extension function. Java extension functions must look like java:mypkg.MyClass.mymethod.", name));
String className = name.substring(5, p);
String methodName = name.substring(p + 1);
Class cl;
try {
cl = CauchoSystem.loadClass(className);
} catch (ClassNotFoundException e) {
throw error(L.l("`{0}' is an unknown Java class. Java extension functions must use public classes.", className));
}
if (methodName.equals("new")) {
Constructor []constructors = cl.getConstructors();
for (int i = 0; i < constructors.length; i++) {
if (constructors[i].getParameterTypes().length == args.size())
return new NewJavaExpr(constructors[i], args);
}
throw error(L.l("No matching public constructor in `{0}'",
className));
}
Method method = null;
Method []methods = cl.getMethods();
if (args.size() > 0) {
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().equals(methodName) &&
methods[i].getParameterTypes().length == args.size() - 1 &&
! Modifier.isStatic(methods[i].getModifiers())) {
Expr objArg = (Expr) args.remove(0);
return new ObjectJavaExpr(methods[i], objArg, args);
}
}
}
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().equals(methodName) &&
methods[i].getParameterTypes().length == args.size()) {
method = methods[i];
break;
}
}
if (method == null)
throw error(L.l("`{0}' does not match a public method in `{1}'",
methodName, className));
if (! Modifier.isStatic(method.getModifiers()))
throw error(L.l("`{0}' is not a static method in `{1}'",
methodName, className));
return new StaticJavaExpr(method, args);
}
else if (name.equals(""))
throw error(L.l("expected node-test at `{0}'", "("));
return new FunExpr(name, parent, args);
}
}
private Expr constructorFunction(String name, ArrayList args)
throws XPathParseException
{
Constructor constructor = _exprFunctions.get(name);
if (constructor == null)
return null;
Class []params = constructor.getParameterTypes();
if (params.length < args.size())
throw error(L.l("`{0}' needs {1} arguments",
name, "" + params.length));
Object []values = new Object[params.length];
for (int i = 0; i < args.size(); i++)
values[i] = args.get(i);
try {
return (Expr) constructor.newInstance(values);
} catch (Throwable e) {
throw new XPathParseException(e);
}
}
private Expr parseNodeSetExpr(AbstractPattern parent, String name, int nodeType)
throws XPathParseException
{
AbstractPattern top = parseAxis(parent, name, true, nodeType);
top = parseFilter(top);
return NodeSetExpr.create(parseUnion(parsePath(top), parent));
}
/**
* Scans the next token.
*
* @return token code, expressed as an Expr enumeration.
*/
private int scanToken()
throws XPathParseException
{
if (peek >= 0) {
int value = peek;
peek = -1;
return value;
}
int ch = skipWhitespace(read());
switch (ch) {
case '+': return Expr.ADD;
case '-': return Expr.SUB;
case '*': return Expr.MUL;
case '=': return Expr.EQ;
case '!':
ch = read();
if (ch == '=')
return Expr.NEQ;
else
throw error(L.l("expected `{0}' at {1}", "=", badChar(ch)));
case '<':
ch = read();
if (ch == '=')
return Expr.LE;
else {
unread();
return Expr.LT;
}
case '>':
ch = read();
if (ch == '=')
return Expr.GE;
else {
unread();
return Expr.GT;
}
default:
if (XmlChar.isNameStart(ch)) {
String name = readName(ch);
if (name.equals("div"))
return Expr.DIV;
else if (name.equals("quo"))
return Expr.QUO;
else if (name.equals("mod"))
return Expr.MOD;
else if (name.equals("and"))
return Expr.AND;
else if (name.equals("or"))
return Expr.OR;
else
throw error(L.l("expected binary operation at `{0}'", name));
}
unread();
return -1;
}
}
private String readName(int ch)
{
tag.clear();
for (; XmlChar.isNameChar(ch); ch = read())
tag.append((char) ch);
if (ch == '*' && tag.endsWith(":"))
tag.append((char) ch);
else
unread();
return tag.toString();
}
private void undoToken(int token)
{
peek = token;
}
private int read()
{
if (index < _string.length()) {
return _string.charAt(index++);
}
else {
index++;
return -1;
}
}
private void unread()
{
index--;
}
private XPathParseException error(String message)
{
return new XPathParseException(message + " in " + _string);
}
private String badChar(int ch)
{
if (ch < 0)
return L.l("end of file");
else if (ch == '\n')
return L.l("end of line");
else
return "`" + (char) ch + "'";
}
private int skipWhitespace(int ch)
throws XPathParseException
{
for (; ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'; ch = read()) {
}
return ch;
}
private static void addFunction(String name, Class cl)
{
Constructor []constructors = cl.getConstructors();
_exprFunctions.put(name, constructors[0]);
}
static {
exprFunctions = new IntMap();
exprFunctions.put("id", Expr.ID);
exprFunctions.put("true", Expr.TRUE);
exprFunctions.put("false", Expr.FALSE);
exprFunctions.put("not", Expr.NOT);
exprFunctions.put("boolean", Expr.BOOLEAN);
exprFunctions.put("starts-with", Expr.STARTS_WITH);
exprFunctions.put("contains", Expr.CONTAINS);
exprFunctions.put("lang", Expr.LANG);
exprFunctions.put("number", Expr.NUMBER);
exprFunctions.put("sum", Expr.SUM);
exprFunctions.put("floor", Expr.FLOOR);
exprFunctions.put("ceiling", Expr.CEILING);
exprFunctions.put("round", Expr.ROUND);
exprFunctions.put("position", Expr.POSITION);
exprFunctions.put("count", Expr.COUNT);
exprFunctions.put("last", Expr.LAST);
exprFunctions.put("string-length", Expr.STRING_LENGTH);
exprFunctions.put("string", Expr.STRING);
exprFunctions.put("concat", Expr.CONCAT);
exprFunctions.put("substring", Expr.SUBSTRING);
exprFunctions.put("substring-before", Expr.SUBSTRING_BEFORE);
exprFunctions.put("substring-after", Expr.SUBSTRING_AFTER);
exprFunctions.put("normalize-space", Expr.NORMALIZE);
exprFunctions.put("translate", Expr.TRANSLATE);
exprFunctions.put("local-name", Expr.LOCAL_PART);
exprFunctions.put("local-part", Expr.LOCAL_PART);
exprFunctions.put("namespace-uri", Expr.NAMESPACE);
exprFunctions.put("name", Expr.QNAME);
exprFunctions.put("generate-id", Expr.GENERATE_ID);
exprFunctions.put("if", Expr.IF);
exprFunctions.put("text", TEXT);
exprFunctions.put("comment", COMMENT);
exprFunctions.put("er", ER);
exprFunctions.put("entity-reference", ER);
exprFunctions.put("pi", PI);
exprFunctions.put("processing-instruction", PI);
exprFunctions.put("node", NODE);
exprFunctions.put("current", CURRENT);
exprFunctions.put("context", CONTEXT);
axisMap = new IntMap();
axisMap.put("ancestor", ANCESTOR_AXIS);
axisMap.put("ancestor-or-self", ANCESTOR_OR_SELF_AXIS);
axisMap.put("attribute", ATTRIBUTE_AXIS);
axisMap.put("child", CHILD_AXIS);
axisMap.put("descendant", DESCENDANT_AXIS);
axisMap.put("descendant-or-self", DESCENDANT_OR_SELF_AXIS);
axisMap.put("following", FOLLOWING_AXIS);
axisMap.put("following-sibling", FOLLOWING_SIBLING_AXIS);
axisMap.put("namespace", NAMESPACE_AXIS);
axisMap.put("parent", PARENT_AXIS);
axisMap.put("preceding", PRECEDING_AXIS);
axisMap.put("preceding-sibling", PRECEDING_SIBLING_AXIS);
axisMap.put("self", SELF_AXIS);
_exprFunctions = new HashMap();
addFunction("fn:base-uri", BaseURI.class);
addFunction("fn:resolve-uri", ResolveURI.class);
addFunction("fn:trace", Trace.class);
}
}