0001: /*
0002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
0003: *
0004: * This file is part of Resin(R) Open Source
0005: *
0006: * Each copy or derived work must preserve the copyright notice and this
0007: * notice unmodified.
0008: *
0009: * Resin Open Source is free software; you can redistribute it and/or modify
0010: * it under the terms of the GNU General Public License as published by
0011: * the Free Software Foundation; either version 2 of the License, or
0012: * (at your option) any later version.
0013: *
0014: * Resin Open Source is distributed in the hope that it will be useful,
0015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
0017: * of NON-INFRINGEMENT. See the GNU General Public License for more
0018: * details.
0019: *
0020: * You should have received a copy of the GNU General Public License
0021: * along with Resin Open Source; if not, write to the
0022: * Free SoftwareFoundation, Inc.
0023: * 59 Temple Place, Suite 330
0024: * Boston, MA 02111-1307 USA
0025: *
0026: * @author Scott Ferguson
0027: */
0028:
0029: package com.caucho.xpath;
0030:
0031: import com.caucho.log.Log;
0032: import com.caucho.server.util.CauchoSystem;
0033: import com.caucho.util.CharBuffer;
0034: import com.caucho.util.IntMap;
0035: import com.caucho.util.L10N;
0036: import com.caucho.xml.XmlChar;
0037: import com.caucho.xpath.expr.*;
0038: import com.caucho.xpath.functions.BaseURI;
0039: import com.caucho.xpath.functions.ResolveURI;
0040: import com.caucho.xpath.functions.Trace;
0041: import com.caucho.xpath.pattern.*;
0042:
0043: import org.w3c.dom.Node;
0044:
0045: import java.lang.reflect.Constructor;
0046: import java.lang.reflect.Method;
0047: import java.lang.reflect.Modifier;
0048: import java.util.ArrayList;
0049: import java.util.HashMap;
0050: import java.util.logging.Logger;
0051:
0052: /**
0053: * Parses an XPath expression.
0054: */
0055: class XPathParser {
0056: private static final Logger log = Log.open(XPathParser.class);
0057: private static final L10N L = new L10N(XPathParser.class);
0058:
0059: private final static int ANCESTOR_AXIS = 0;
0060: private final static int ANCESTOR_OR_SELF_AXIS = ANCESTOR_AXIS + 1;
0061: private final static int ATTRIBUTE_AXIS = ANCESTOR_OR_SELF_AXIS + 1;
0062: private final static int CHILD_AXIS = ATTRIBUTE_AXIS + 1;
0063: private final static int DESCENDANT_AXIS = CHILD_AXIS + 1;
0064: private final static int DESCENDANT_OR_SELF_AXIS = DESCENDANT_AXIS + 1;
0065: private final static int FOLLOWING_AXIS = DESCENDANT_OR_SELF_AXIS + 1;
0066: private final static int FOLLOWING_SIBLING_AXIS = FOLLOWING_AXIS + 1;
0067: private final static int NAMESPACE_AXIS = FOLLOWING_SIBLING_AXIS + 1;
0068: private final static int PARENT_AXIS = NAMESPACE_AXIS + 1;
0069: private final static int PRECEDING_AXIS = PARENT_AXIS + 1;
0070: private final static int PRECEDING_SIBLING_AXIS = PRECEDING_AXIS + 1;
0071: private final static int SELF_AXIS = PRECEDING_SIBLING_AXIS + 1;
0072:
0073: private final static int TEXT = Expr.LAST_FUN + 1;
0074: private final static int COMMENT = TEXT + 1;
0075: private final static int ER = COMMENT + 1;
0076: private final static int PI = ER + 1;
0077: private final static int NODE = PI + 1;
0078: private final static int CURRENT = NODE + 1;
0079: private final static int NODE_TEXT = CURRENT + 1;
0080: private final static int CONTEXT = NODE_TEXT + 1;
0081:
0082: private static IntMap exprFunctions;
0083: private static IntMap axisMap;
0084:
0085: private static HashMap<String, Constructor> _exprFunctions;
0086:
0087: private CharBuffer tag = new CharBuffer();
0088:
0089: private String _string;
0090: private int index;
0091: private int peek;
0092:
0093: private NamespaceContext _namespace;
0094:
0095: XPathParser(String string, NamespaceContext namespace) {
0096: _string = string;
0097: _namespace = namespace;
0098: }
0099:
0100: /**
0101: * Parse a select pattern, i.e. a path rooted in a context.
0102: */
0103: AbstractPattern parseSelect() throws XPathParseException {
0104: AbstractPattern top = new FromContext();
0105:
0106: AbstractPattern pattern = parseUnion(parseTop(top), top);
0107:
0108: if (index < _string.length())
0109: throw error(L.l("unexpected character at `{0}'",
0110: badChar(read())));
0111:
0112: return pattern;
0113: }
0114:
0115: /**
0116: * Parse a match pattern, i.e. a path with no root.
0117: */
0118: AbstractPattern parseMatch() throws XPathParseException {
0119: AbstractPattern root = new FromAny();
0120: AbstractPattern pattern = parseUnion(parseTop(root), root);
0121:
0122: if (index < _string.length())
0123: throw error(L.l("unexpected character at `{0}'",
0124: badChar(read())));
0125:
0126: return pattern;
0127: }
0128:
0129: /**
0130: * Parse an expression.
0131: */
0132: Expr parseExpr() throws XPathParseException {
0133: Expr expr = parseExpr(null, null);
0134:
0135: if (index < _string.length())
0136: throw error(L.l("unexpected character at `{0}'",
0137: badChar(read())));
0138:
0139: return expr;
0140: }
0141:
0142: private AbstractPattern parseStep(AbstractPattern root)
0143: throws XPathParseException {
0144: return parseUnion(parseTop(root), root);
0145: }
0146:
0147: private AbstractPattern parseUnion(AbstractPattern left,
0148: AbstractPattern root) throws XPathParseException {
0149: int ch = skipWhitespace(read());
0150:
0151: while (ch >= 0) {
0152: if (ch == '|') {
0153: AbstractPattern tail = parseUnion(parseTop(root), root);
0154: left = new UnionPattern(left, tail);
0155: } else
0156: break;
0157:
0158: for (ch = read(); XmlChar.isWhitespace(ch); ch = read()) {
0159: }
0160: }
0161:
0162: unread();
0163:
0164: return left;
0165: }
0166:
0167: /**
0168: * Parses the top expression.
0169: *
0170: * <pre>
0171: * top ::= (expr)
0172: * ::= term
0173: * </pre>
0174: */
0175: private AbstractPattern parseTop(AbstractPattern pattern)
0176: throws XPathParseException {
0177: int ch = skipWhitespace(read());
0178: unread();
0179:
0180: if (ch == '(') {
0181: Expr expr = parseTerm(pattern, pattern);
0182:
0183: // If the expression is really a pattern and the iterator is
0184: // ascending, then just unwrap it.
0185: if (expr instanceof NodeSetExpr) {
0186: AbstractPattern nodeSet = ((NodeSetExpr) expr)
0187: .getPattern();
0188:
0189: if (nodeSet.isAscending())
0190: return nodeSet;
0191: }
0192:
0193: return new FromExpr(null, expr);
0194: } else
0195: return parseTerm(pattern, pattern).toNodeList();
0196: }
0197:
0198: private AbstractPattern parseBasisTop(AbstractPattern pattern)
0199: throws XPathParseException {
0200: int ch;
0201:
0202: ch = skipWhitespace(read());
0203: if (ch == '/') {
0204: ch = read();
0205: if (ch == '/') {
0206: pattern = new FromRoot();
0207: pattern = new FromDescendants(pattern, false);
0208: pattern = parseBasis(pattern);
0209: pattern = parseFilter(pattern);
0210: pattern = parsePath(pattern);
0211:
0212: return pattern;
0213: }
0214: pattern = new FromRoot();
0215: ch = skipWhitespace(ch);
0216:
0217: if (ch == -1)
0218: return pattern;
0219: }
0220:
0221: unread();
0222:
0223: if (pattern == null)
0224: pattern = new FromContext();
0225:
0226: pattern = parseBasis(pattern);
0227: pattern = parseFilter(pattern);
0228:
0229: return parsePath(pattern);
0230: }
0231:
0232: /**
0233: * path ::= top ('/' filter)*
0234: */
0235: private AbstractPattern parsePath(AbstractPattern pattern)
0236: throws XPathParseException {
0237: int ch = skipWhitespace(read());
0238:
0239: while (ch == '/') {
0240: ch = read();
0241:
0242: if (ch == '/') {
0243: pattern = new FromDescendants(pattern, false);
0244: pattern = parseBasis(pattern);
0245: pattern = parseFilter(pattern);
0246: } else {
0247: unread();
0248: pattern = parseBasis(pattern);
0249: pattern = parseFilter(pattern);
0250: }
0251:
0252: ch = skipWhitespace(read());
0253: }
0254:
0255: unread();
0256:
0257: return pattern;
0258: }
0259:
0260: /**
0261: * filter ::= atom('[' expr ']')*
0262: */
0263: private AbstractPattern parseFilter(AbstractPattern pattern)
0264: throws XPathParseException {
0265: int ch = skipWhitespace(read());
0266: while (ch == '[') {
0267: AbstractPattern context = new FromContext();
0268: Expr expr = parseExpr(context, pattern);
0269: pattern = new FilterPattern(pattern, expr);
0270:
0271: ch = skipWhitespace(read());
0272: if (ch != ']')
0273: throw error(L.l("expected `{0}' at {1}", "]",
0274: badChar(ch)));
0275:
0276: ch = skipWhitespace(read());
0277: }
0278: unread();
0279:
0280: return pattern;
0281: }
0282:
0283: /**
0284: * basis ::= name::node-test
0285: * | node-test
0286: * | @node-test
0287: * | .
0288: * | ..
0289: * ;
0290: */
0291: private AbstractPattern parseBasis(AbstractPattern pattern)
0292: throws XPathParseException {
0293: boolean fromChildren = true;
0294: int ch = skipWhitespace(read());
0295:
0296: int nodeType = Node.ELEMENT_NODE;
0297: String namespace = null;
0298: tag.clear();
0299:
0300: if (ch == '@') {
0301: if (pattern instanceof FromDescendants)
0302: pattern = NodeTypePattern.create(pattern,
0303: NodeTypePattern.NODE);
0304:
0305: pattern = new FromAttributes(pattern);
0306: nodeType = Node.ATTRIBUTE_NODE;
0307: fromChildren = false;
0308: ch = read();
0309: } else if (ch == '.') {
0310: ch = read();
0311: if (ch == '.')
0312: return NodeTypePattern.create(new FromParent(pattern),
0313: NodeTypePattern.NODE);
0314: else {
0315: unread();
0316: if (pattern != null)
0317: return pattern;
0318: else
0319: return NodeTypePattern.create(
0320: new FromSelf(pattern), NodeTypePattern.ANY);
0321: }
0322: } else if (ch == '(') {
0323: // XXX: not strictly correct for counting
0324: Expr expr = parseExpr(null, null);
0325:
0326: if ((ch = read()) != ')')
0327: throw error(L.l("expected `{0}' at {1}", ")",
0328: badChar(ch)));
0329:
0330: return new FromExpr(pattern, expr);
0331: }
0332:
0333: if (ch == '*') {
0334: tag.append('*');
0335: }
0336:
0337: else if (XmlChar.isNameStart(ch)) {
0338: for (; XmlChar.isNameChar(ch); ch = read())
0339: tag.append((char) ch);
0340:
0341: if (ch == '*' && tag.endsWith(":"))
0342: tag.append('*');
0343: else
0344: unread();
0345: } else
0346: unread();
0347:
0348: String name = tag.toString();
0349:
0350: if (name.equals(""))
0351: throw error(L.l("expected name at {0}", badChar(ch)));
0352:
0353: return parseAxis(pattern, name, fromChildren, nodeType);
0354: }
0355:
0356: private AbstractPattern parseAxis(AbstractPattern pattern,
0357: String name, boolean fromChildren, int nodeType)
0358: throws XPathParseException {
0359: String axis = "";
0360: int axisIndex = name.indexOf("::");
0361:
0362: if (axisIndex >= 0 && nodeType != Node.ATTRIBUTE_NODE) {
0363: axis = name.substring(0, axisIndex);
0364: name = name.substring(axisIndex + 2);
0365: }
0366:
0367: if (pattern instanceof FromDescendants)
0368: return parseNodeTest(pattern, name, false,
0369: Node.ELEMENT_NODE);
0370:
0371: switch (axisMap.get(axis)) {
0372: case ANCESTOR_AXIS:
0373: return parseNodeTest(new FromAncestors(pattern, false),
0374: name, false, Node.ELEMENT_NODE);
0375:
0376: case ANCESTOR_OR_SELF_AXIS:
0377: return parseNodeTest(new FromAncestors(pattern, true),
0378: name, false, Node.ELEMENT_NODE);
0379:
0380: case ATTRIBUTE_AXIS:
0381: return parseNodeTest(new FromAttributes(pattern), name,
0382: false, Node.ATTRIBUTE_NODE);
0383:
0384: case CHILD_AXIS:
0385: return parseNodeTest(new FromChildren(pattern), name,
0386: false, Node.ELEMENT_NODE);
0387:
0388: case DESCENDANT_AXIS:
0389: return parseNodeTest(new FromDescendants(pattern, false),
0390: name, false, Node.ELEMENT_NODE);
0391:
0392: case DESCENDANT_OR_SELF_AXIS:
0393: return parseNodeTest(new FromDescendants(pattern, true),
0394: name, false, Node.ELEMENT_NODE);
0395:
0396: case FOLLOWING_AXIS:
0397: return parseNodeTest(new FromNext(pattern), name, false,
0398: Node.ELEMENT_NODE);
0399:
0400: case FOLLOWING_SIBLING_AXIS:
0401: return parseNodeTest(new FromNextSibling(pattern), name,
0402: false, Node.ELEMENT_NODE);
0403:
0404: case NAMESPACE_AXIS:
0405: return parseNodeTest(new FromNamespace(pattern), name,
0406: false, Node.ATTRIBUTE_NODE);
0407:
0408: case PARENT_AXIS:
0409: return parseNodeTest(new FromParent(pattern), name, false,
0410: Node.ELEMENT_NODE);
0411:
0412: case PRECEDING_AXIS:
0413: return parseNodeTest(new FromPrevious(pattern), name,
0414: false, Node.ELEMENT_NODE);
0415:
0416: case PRECEDING_SIBLING_AXIS:
0417: return parseNodeTest(new FromPreviousSibling(pattern),
0418: name, false, Node.ELEMENT_NODE);
0419:
0420: case SELF_AXIS:
0421: return parseNodeTest(new FromSelf(pattern), name, false,
0422: Node.ELEMENT_NODE);
0423:
0424: default:
0425: return parseNodeTest(pattern, name, fromChildren, nodeType);
0426: }
0427: }
0428:
0429: /**
0430: * node-test ::= qname
0431: * | *:name
0432: * | *
0433: * | name '(' args ')'
0434: * ;
0435: */
0436: private AbstractPattern parseNodeTest(AbstractPattern pattern,
0437: String name, boolean fromChildren, int nodeType)
0438: throws XPathParseException {
0439: int ch = skipWhitespace(read());
0440:
0441: AbstractPattern tagPattern;
0442:
0443: if (ch == '(') {
0444: Expr expr = parseFunction(pattern, pattern, name,
0445: fromChildren);
0446: return expr.toNodeList();
0447: } else if (ch == '{') {
0448: tag.clear();
0449: while ((ch = read()) >= 0 && ch != '}')
0450: tag.append((char) ch);
0451: String url = tag.toString();
0452: tag.clear();
0453: for (ch = read(); XmlChar.isNameChar(ch); ch = read())
0454: tag.append((char) ch);
0455:
0456: pattern = new NSNamePattern(pattern, url, tag.toString(),
0457: nodeType);
0458: } else {
0459: if (fromChildren)
0460: pattern = new FromChildren(pattern);
0461:
0462: if (name.equals("*"))
0463: pattern = NodeTypePattern.create(pattern, nodeType);
0464: else if (name.endsWith(":*")) {
0465: pattern = new NamespacePattern(pattern, name.substring(
0466: 0, name.length() - 2), nodeType);
0467: } else {
0468: int p = name.indexOf(':');
0469: String ns = null;
0470: String local = name;
0471:
0472: if (p > 0) {
0473: String prefix = name.substring(0, p);
0474: ns = NamespaceContext.find(_namespace, prefix);
0475: local = name.substring(p + 1);
0476: } else if (nodeType != Node.ATTRIBUTE_NODE)
0477: ns = _namespace.find(_namespace, "");
0478: else
0479: ns = null; // _namespace.find(_namespace, "");
0480:
0481: if (ns == null)
0482: pattern = new NodePattern(pattern, name, nodeType);
0483: else
0484: pattern = new NSNamePattern(pattern, ns, local,
0485: nodeType);
0486: }
0487: }
0488: unread();
0489:
0490: return pattern;
0491: }
0492:
0493: /**
0494: * expr ::= or-expr
0495: */
0496: Expr parseExpr(AbstractPattern parent, AbstractPattern listParent)
0497: throws XPathParseException {
0498: peek = -1;
0499: Expr left = parseTerm(parent, listParent);
0500:
0501: while (true) {
0502: int token = scanToken();
0503: switch (token) {
0504: case Expr.OR:
0505: left = parseOrExpr(token, left, parseTerm(parent,
0506: listParent), parent, listParent);
0507: break;
0508:
0509: case Expr.AND:
0510: left = parseAndExpr(token, left, parseTerm(parent,
0511: listParent), parent, listParent);
0512: break;
0513:
0514: case Expr.EQ:
0515: case Expr.NEQ:
0516: case Expr.LT:
0517: case Expr.LE:
0518: case Expr.GT:
0519: case Expr.GE:
0520: left = parseCmpExpr(token, left, parseTerm(parent,
0521: listParent), parent, listParent);
0522: break;
0523:
0524: case Expr.ADD:
0525: case Expr.SUB:
0526: left = parseAddExpr(token, left, parseTerm(parent,
0527: listParent), parent, listParent);
0528: break;
0529:
0530: case Expr.MUL:
0531: case Expr.DIV:
0532: case Expr.QUO:
0533: case Expr.MOD:
0534: left = parseMulExpr(token, left, parseTerm(parent,
0535: listParent), parent, listParent);
0536: break;
0537:
0538: default:
0539: return left;
0540: }
0541: }
0542: }
0543:
0544: /**
0545: * or-expr ::= or-expr 'OR' expr
0546: * | and-expr
0547: */
0548: private Expr parseOrExpr(int code, Expr left, Expr right,
0549: AbstractPattern parent, AbstractPattern listParent)
0550: throws XPathParseException {
0551: while (true) {
0552: int token = scanToken();
0553: switch (token) {
0554: case Expr.OR:
0555: left = new BooleanExpr(code, left, right);
0556: code = token;
0557: right = parseTerm(parent, listParent);
0558: break;
0559:
0560: case Expr.AND:
0561: right = parseAndExpr(token, right, parseTerm(parent,
0562: listParent), parent, listParent);
0563: break;
0564:
0565: case Expr.EQ:
0566: case Expr.NEQ:
0567: case Expr.LT:
0568: case Expr.LE:
0569: case Expr.GT:
0570: case Expr.GE:
0571: right = parseCmpExpr(token, right, parseTerm(parent,
0572: listParent), parent, listParent);
0573: break;
0574:
0575: case Expr.ADD:
0576: case Expr.SUB:
0577: right = parseAddExpr(token, right, parseTerm(parent,
0578: listParent), parent, listParent);
0579: break;
0580:
0581: case Expr.MUL:
0582: case Expr.DIV:
0583: case Expr.QUO:
0584: case Expr.MOD:
0585: right = parseMulExpr(token, right, parseTerm(parent,
0586: listParent), parent, listParent);
0587: break;
0588:
0589: default:
0590: undoToken(token);
0591: return new BooleanExpr(code, left, right);
0592: }
0593: }
0594: }
0595:
0596: /**
0597: * and-expr ::= and-expr 'AND' expr
0598: * | cmp-expr
0599: */
0600: private Expr parseAndExpr(int code, Expr left, Expr right,
0601: AbstractPattern parent, AbstractPattern listParent)
0602: throws XPathParseException {
0603: while (true) {
0604: int token = scanToken();
0605: switch (token) {
0606: case Expr.AND:
0607: left = new BooleanExpr(code, left, right);
0608: code = token;
0609: right = parseTerm(parent, listParent);
0610: break;
0611:
0612: case Expr.EQ:
0613: case Expr.NEQ:
0614: case Expr.LT:
0615: case Expr.LE:
0616: case Expr.GT:
0617: case Expr.GE:
0618: right = parseCmpExpr(token, right, parseTerm(parent,
0619: listParent), parent, listParent);
0620: break;
0621:
0622: case Expr.ADD:
0623: case Expr.SUB:
0624: right = parseAddExpr(token, right, parseTerm(parent,
0625: listParent), parent, listParent);
0626: break;
0627:
0628: case Expr.MUL:
0629: case Expr.DIV:
0630: case Expr.QUO:
0631: case Expr.MOD:
0632: right = parseMulExpr(token, right, parseTerm(parent,
0633: listParent), parent, listParent);
0634: break;
0635:
0636: default:
0637: undoToken(token);
0638: return new BooleanExpr(code, left, right);
0639: }
0640: }
0641: }
0642:
0643: /**
0644: * cmp-expr ::= cmp-expr '<' expr
0645: * | add-expr
0646: */
0647: private Expr parseCmpExpr(int code, Expr left, Expr right,
0648: AbstractPattern parent, AbstractPattern listParent)
0649: throws XPathParseException {
0650: while (true) {
0651: int token = scanToken();
0652: switch (token) {
0653: case Expr.EQ:
0654: case Expr.NEQ:
0655: case Expr.LT:
0656: case Expr.LE:
0657: case Expr.GT:
0658: case Expr.GE:
0659: left = new BooleanExpr(code, left, right);
0660: code = token;
0661: right = parseTerm(parent, listParent);
0662: break;
0663:
0664: case Expr.ADD:
0665: case Expr.SUB:
0666: right = parseAddExpr(token, right, parseTerm(parent,
0667: listParent), parent, listParent);
0668: break;
0669:
0670: case Expr.MUL:
0671: case Expr.DIV:
0672: case Expr.QUO:
0673: case Expr.MOD:
0674: right = parseMulExpr(token, right, parseTerm(parent,
0675: listParent), parent, listParent);
0676: break;
0677:
0678: default:
0679: undoToken(token);
0680: return new BooleanExpr(code, left, right);
0681: }
0682: }
0683: }
0684:
0685: /**
0686: * add-expr ::= add-expr '+' expr
0687: * | mul-expr
0688: */
0689: private Expr parseAddExpr(int code, Expr left, Expr right,
0690: AbstractPattern parent, AbstractPattern listParent)
0691: throws XPathParseException {
0692: while (true) {
0693: int token = scanToken();
0694: switch (token) {
0695: case Expr.ADD:
0696: case Expr.SUB:
0697: left = new NumericExpr(code, left, right);
0698: code = token;
0699: right = parseTerm(parent, listParent);
0700: break;
0701:
0702: case Expr.MUL:
0703: case Expr.DIV:
0704: case Expr.QUO:
0705: case Expr.MOD:
0706: right = parseMulExpr(token, right, parseTerm(parent,
0707: listParent), parent, listParent);
0708: break;
0709:
0710: default:
0711: undoToken(token);
0712: return new NumericExpr(code, left, right);
0713: }
0714: }
0715: }
0716:
0717: /**
0718: * mul-expr ::= mul-expr '*' expr
0719: * | term
0720: */
0721: private Expr parseMulExpr(int code, Expr left, Expr right,
0722: AbstractPattern parent, AbstractPattern listParent)
0723: throws XPathParseException {
0724: while (true) {
0725: int token = scanToken();
0726: switch (token) {
0727: case Expr.MUL:
0728: case Expr.DIV:
0729: case Expr.QUO:
0730: case Expr.MOD:
0731: left = new NumericExpr(code, left, right);
0732: code = token;
0733: right = parseTerm(parent, listParent);
0734: break;
0735:
0736: default:
0737: undoToken(token);
0738: return new NumericExpr(code, left, right);
0739: }
0740: }
0741: }
0742:
0743: /**
0744: * term ::= simple-term path?
0745: */
0746: private Expr parseTerm(AbstractPattern parent,
0747: AbstractPattern listParent) throws XPathParseException {
0748: int ch = skipWhitespace(read());
0749: unread();
0750: Expr expr = parseSimpleTerm(parent, listParent);
0751:
0752: int nextCh = skipWhitespace(read());
0753: unread();
0754: if (nextCh == '/' || nextCh == '[') {
0755: AbstractPattern pattern = expr.toNodeList();
0756:
0757: if (ch == '(' && !pattern.isStrictlyAscending())
0758: pattern = new FromExpr(null, expr);
0759:
0760: return NodeSetExpr.create(parseUnion(
0761: parsePath(parseFilter(pattern)), pattern));
0762: } else if (nextCh == '|') {
0763: AbstractPattern pattern = expr.toNodeList();
0764:
0765: return NodeSetExpr.create(parseUnion(
0766: parsePath(parseFilter(pattern)), listParent));
0767: } else
0768: return expr;
0769: }
0770:
0771: /**
0772: * simple-term ::= number
0773: * ::= '(' expr ')'
0774: * ::= '$' variable
0775: * ::= '"' string '"'
0776: * ::= node-set
0777: */
0778: private Expr parseSimpleTerm(AbstractPattern parent,
0779: AbstractPattern listParent) throws XPathParseException {
0780: int ch = read();
0781:
0782: ch = skipWhitespace(ch);
0783:
0784: switch (ch) {
0785: case '.':
0786: ch = read();
0787: unread();
0788: unread();
0789: if (!('0' <= ch && ch <= '9')) {
0790: return NodeSetExpr.create(parseUnion(
0791: parseBasisTop(parent), parent));
0792: }
0793: ch = read();
0794: // fall through to parse the number
0795:
0796: case '0':
0797: case '1':
0798: case '2':
0799: case '3':
0800: case '4':
0801: case '5':
0802: case '6':
0803: case '7':
0804: case '8':
0805: case '9': {
0806: long value = 0;
0807: double exp = 1;
0808: int digits = 0;
0809: for (; ch >= '0' && ch <= '9'; ch = read())
0810: value = 10 * value + ch - '0';
0811: if (ch == '.') {
0812: for (ch = read(); ch >= '0' && ch <= '9'; ch = read()) {
0813: value = 10 * value + ch - '0';
0814: exp *= 10;
0815: digits--;
0816: }
0817: }
0818:
0819: if (ch == 'e' || ch == 'E') {
0820: int sign = 1;
0821: int expValue = 0;
0822:
0823: ch = read();
0824: if (ch == '-') {
0825: sign = -1;
0826: ch = read();
0827: } else if (ch == '+')
0828: ch = read();
0829:
0830: for (; ch >= '0' && ch <= '9'; ch = read())
0831: expValue = 10 * expValue + ch - '0';
0832:
0833: exp = Math.pow(10, digits + sign * expValue);
0834:
0835: unread();
0836:
0837: return new NumericExpr((double) value * (double) exp);
0838: }
0839:
0840: unread();
0841: return new NumericExpr((double) value / (double) exp);
0842: }
0843:
0844: case '-':
0845: return new NumericExpr(Expr.NEG, parseTerm(parent,
0846: listParent));
0847:
0848: case '+':
0849: return parseTerm(parent, listParent);
0850:
0851: case '(': {
0852: Expr expr = parseExpr(parent, listParent);
0853: if ((ch = skipWhitespace(read())) != ')')
0854: throw error(L.l("expected `{0}' at {1}", ")",
0855: badChar(ch)));
0856:
0857: return expr;
0858: }
0859:
0860: case '/':
0861: case '@':
0862: case '*':
0863: unread();
0864: return NodeSetExpr.create(parseUnion(parseBasisTop(parent),
0865: parent));
0866:
0867: case '\'':
0868: case '"': {
0869: int end = ch;
0870: CharBuffer cb = new CharBuffer();
0871: for (ch = read(); ch >= 0; ch = read()) {
0872: if (ch != end)
0873: cb.append((char) ch);
0874: else if ((ch = read()) == end)
0875: cb.append((char) ch);
0876: else {
0877: unread();
0878: break;
0879: }
0880: }
0881:
0882: return new StringExpr(cb.toString());
0883: }
0884:
0885: case '$': {
0886: String name = readName(read());
0887: return new VarExpr(name);
0888: }
0889:
0890: default:
0891: if (!XmlChar.isNameStart(ch))
0892: throw error(L
0893: .l("unknown character at {0}", badChar(ch)));
0894:
0895: String name = readName(ch);
0896:
0897: ch = skipWhitespace(read());
0898: int axisIndex = name.indexOf("::");
0899:
0900: // make sure axis are treated as node sets
0901: if (ch == '(' && axisIndex < 0) {
0902: return parseFunction(parent, listParent, name, true);
0903: } else if (ch == '(') {
0904: String axis = name.substring(0, axisIndex);
0905: if (axisMap.get(axis) <= 0)
0906: return parseFunction(parent, listParent, name, true);
0907: }
0908:
0909: unread();
0910:
0911: if (parent == null)
0912: parent = new FromContext();
0913: return parseNodeSetExpr(parent, name, Node.ELEMENT_NODE);
0914: }
0915: }
0916:
0917: /**
0918: * function ::= name '(' args ')'
0919: *
0920: * The XPath library functions are hard-coded for a little extra
0921: * execution efficiency.
0922: */
0923: Expr parseFunction(AbstractPattern parent,
0924: AbstractPattern listParent, String name,
0925: boolean fromChildren) throws XPathParseException {
0926: int ch = skipWhitespace(read());
0927:
0928: ArrayList<Expr> args = new ArrayList<Expr>();
0929:
0930: for (; ch >= 0 && ch != ')'; ch = skipWhitespace(read())) {
0931: if (ch != ',')
0932: unread();
0933: Expr expr = parseExpr(parent, listParent);
0934:
0935: if (expr == null)
0936: throw error(L.l("null expression"));
0937:
0938: args.add(expr);
0939: }
0940:
0941: int code = exprFunctions.get(name);
0942: switch (code) {
0943: case Expr.TRUE:
0944: case Expr.FALSE:
0945: case Expr.NOT:
0946: case Expr.BOOLEAN:
0947: case Expr.STARTS_WITH:
0948: case Expr.CONTAINS:
0949: case Expr.LANG:
0950: case Expr.FUNCTION_AVAILABLE:
0951: return new BooleanExpr(code, args);
0952:
0953: case Expr.NUMBER:
0954: case Expr.FLOOR:
0955: case Expr.CEILING:
0956: case Expr.ROUND:
0957: case Expr.STRING_LENGTH:
0958: return new NumericExpr(code, args);
0959:
0960: case Expr.POSITION:
0961: case Expr.LAST:
0962: return new NumericExpr(code, listParent);
0963:
0964: case Expr.COUNT:
0965: case Expr.SUM:
0966: if (args.size() == 0)
0967: args.add(NodeSetExpr.create(new FromContext()));
0968: return new NumericExpr(code, ((Expr) args.get(0))
0969: .toNodeList());
0970:
0971: case Expr.CONCAT:
0972: case Expr.SUBSTRING_BEFORE:
0973: case Expr.SUBSTRING:
0974: case Expr.SUBSTRING_AFTER:
0975: case Expr.TRANSLATE:
0976: case Expr.SYSTEM_PROPERTY:
0977: return new StringExpr(code, args);
0978:
0979: case Expr.STRING:
0980: case Expr.NORMALIZE:
0981: return new StringExpr(code, args);
0982:
0983: case Expr.LOCAL_PART:
0984: case Expr.NAMESPACE:
0985: case Expr.QNAME:
0986: case Expr.GENERATE_ID:
0987: if (args.size() == 0)
0988: args.add(NodeSetExpr.create(new FromContext()));
0989: return new StringExpr(code, args);
0990:
0991: case Expr.ID:
0992: if (args.size() == 0) {
0993: args.add(NodeSetExpr.create(parent));
0994: return new IdExpr(args);
0995: } else
0996: return new IdExpr(args);
0997:
0998: case Expr.IF:
0999: if (args.size() != 3)
1000: throw error(L.l("`if' needs three args."));
1001:
1002: return new ObjectExpr(code, args);
1003:
1004: case Expr.BASE_URI:
1005: if (args.size() != 1)
1006: throw error(L.l("`base-uri' needs one args."));
1007:
1008: return new StringExpr(code, args.get(0));
1009:
1010: case TEXT:
1011: if (fromChildren)
1012: parent = new FromChildren(parent);
1013: AbstractPattern pattern = NodeTypePattern.create(parent,
1014: Node.TEXT_NODE);
1015: return NodeSetExpr.create(pattern);
1016:
1017: case COMMENT:
1018: if (fromChildren)
1019: parent = new FromChildren(parent);
1020: pattern = NodeTypePattern.create(parent, Node.COMMENT_NODE);
1021: return NodeSetExpr.create(pattern);
1022:
1023: case ER:
1024: if (fromChildren)
1025: parent = new FromChildren(parent);
1026: pattern = NodeTypePattern.create(parent,
1027: Node.ENTITY_REFERENCE_NODE);
1028: return NodeSetExpr.create(pattern);
1029:
1030: case PI:
1031: if (fromChildren)
1032: parent = new FromChildren(parent);
1033: if (args.size() == 1) {
1034: Expr expr = (Expr) args.get(0);
1035: String value = null;
1036: if (expr instanceof StringExpr)
1037: value = ((StringExpr) expr).getValue();
1038: if (value == null)
1039: throw error(L
1040: .l("processing-instruction expects string literal"));
1041: pattern = new NodePattern(parent, value,
1042: Node.PROCESSING_INSTRUCTION_NODE);
1043: } else
1044: pattern = NodeTypePattern.create(parent,
1045: Node.PROCESSING_INSTRUCTION_NODE);
1046: return NodeSetExpr.create(pattern);
1047:
1048: case NODE:
1049: if (fromChildren)
1050: parent = new FromChildren(parent);
1051: pattern = NodeTypePattern.create(parent,
1052: NodeTypePattern.NODE);
1053: return NodeSetExpr.create(pattern);
1054:
1055: case CURRENT:
1056: return NodeSetExpr.create(new CurrentPattern());
1057:
1058: case CONTEXT:
1059: return NodeSetExpr.create(new FromContext());
1060:
1061: default:
1062: Expr function = constructorFunction(name, args);
1063:
1064: if (function != null)
1065: return function;
1066:
1067: int p = name.lastIndexOf(':');
1068: String prefix;
1069:
1070: if (p > 0)
1071: prefix = name.substring(0, p);
1072: else
1073: prefix = "";
1074:
1075: String context = NamespaceContext.find(_namespace, prefix);
1076:
1077: if (context == null) {
1078: } else if (context.startsWith("java:"))
1079: name = context + "." + name.substring(p + 1);
1080: else if (context.indexOf(':') < 0)
1081: name = "java:" + context + "." + name.substring(p + 1);
1082:
1083: if (name.startsWith("java:")) {
1084: p = name.lastIndexOf('.');
1085: if (p < 0)
1086: throw error(L
1087: .l(
1088: "`{0}' is an illegal extension function. Java extension functions must look like java:mypkg.MyClass.mymethod.",
1089: name));
1090:
1091: String className = name.substring(5, p);
1092: String methodName = name.substring(p + 1);
1093:
1094: Class cl;
1095: try {
1096: cl = CauchoSystem.loadClass(className);
1097: } catch (ClassNotFoundException e) {
1098: throw error(L
1099: .l(
1100: "`{0}' is an unknown Java class. Java extension functions must use public classes.",
1101: className));
1102: }
1103:
1104: if (methodName.equals("new")) {
1105: Constructor[] constructors = cl.getConstructors();
1106:
1107: for (int i = 0; i < constructors.length; i++) {
1108: if (constructors[i].getParameterTypes().length == args
1109: .size())
1110: return new NewJavaExpr(constructors[i],
1111: args);
1112: }
1113:
1114: throw error(L.l(
1115: "No matching public constructor in `{0}'",
1116: className));
1117: }
1118:
1119: Method method = null;
1120: Method[] methods = cl.getMethods();
1121:
1122: if (args.size() > 0) {
1123: for (int i = 0; i < methods.length; i++) {
1124: if (methods[i].getName().equals(methodName)
1125: && methods[i].getParameterTypes().length == args
1126: .size() - 1
1127: && !Modifier.isStatic(methods[i]
1128: .getModifiers())) {
1129: Expr objArg = (Expr) args.remove(0);
1130:
1131: return new ObjectJavaExpr(methods[i],
1132: objArg, args);
1133: }
1134: }
1135: }
1136:
1137: for (int i = 0; i < methods.length; i++) {
1138: if (methods[i].getName().equals(methodName)
1139: && methods[i].getParameterTypes().length == args
1140: .size()) {
1141: method = methods[i];
1142: break;
1143: }
1144: }
1145:
1146: if (method == null)
1147: throw error(L
1148: .l(
1149: "`{0}' does not match a public method in `{1}'",
1150: methodName, className));
1151:
1152: if (!Modifier.isStatic(method.getModifiers()))
1153: throw error(L.l(
1154: "`{0}' is not a static method in `{1}'",
1155: methodName, className));
1156:
1157: return new StaticJavaExpr(method, args);
1158: } else if (name.equals(""))
1159: throw error(L.l("expected node-test at `{0}'", "("));
1160:
1161: return new FunExpr(name, parent, args);
1162: }
1163: }
1164:
1165: private Expr constructorFunction(String name, ArrayList<Expr> args)
1166: throws XPathParseException {
1167: Constructor constructor = _exprFunctions.get(name);
1168:
1169: if (constructor == null)
1170: return null;
1171:
1172: Class[] params = constructor.getParameterTypes();
1173:
1174: if (params.length < args.size())
1175: throw error(L.l("`{0}' needs {1} arguments", name, ""
1176: + params.length));
1177:
1178: Object[] values = new Object[params.length];
1179:
1180: for (int i = 0; i < args.size(); i++)
1181: values[i] = args.get(i);
1182:
1183: try {
1184: return (Expr) constructor.newInstance(values);
1185: } catch (Throwable e) {
1186: throw new XPathParseException(e);
1187: }
1188: }
1189:
1190: private Expr parseNodeSetExpr(AbstractPattern parent, String name,
1191: int nodeType) throws XPathParseException {
1192: AbstractPattern top = parseAxis(parent, name, true, nodeType);
1193: top = parseFilter(top);
1194:
1195: return NodeSetExpr.create(parseUnion(parsePath(top), parent));
1196: }
1197:
1198: /**
1199: * Scans the next token.
1200: *
1201: * @return token code, expressed as an Expr enumeration.
1202: */
1203: private int scanToken() throws XPathParseException {
1204: if (peek >= 0) {
1205: int value = peek;
1206: peek = -1;
1207: return value;
1208: }
1209:
1210: int ch = skipWhitespace(read());
1211:
1212: switch (ch) {
1213: case '+':
1214: return Expr.ADD;
1215: case '-':
1216: return Expr.SUB;
1217: case '*':
1218: return Expr.MUL;
1219: case '=':
1220: return Expr.EQ;
1221:
1222: case '!':
1223: ch = read();
1224: if (ch == '=')
1225: return Expr.NEQ;
1226: else
1227: throw error(L.l("expected `{0}' at {1}", "=",
1228: badChar(ch)));
1229:
1230: case '<':
1231: ch = read();
1232: if (ch == '=')
1233: return Expr.LE;
1234: else {
1235: unread();
1236: return Expr.LT;
1237: }
1238:
1239: case '>':
1240: ch = read();
1241: if (ch == '=')
1242: return Expr.GE;
1243: else {
1244: unread();
1245: return Expr.GT;
1246: }
1247:
1248: default:
1249: if (XmlChar.isNameStart(ch)) {
1250: String name = readName(ch);
1251:
1252: if (name.equals("div"))
1253: return Expr.DIV;
1254: else if (name.equals("quo"))
1255: return Expr.QUO;
1256: else if (name.equals("mod"))
1257: return Expr.MOD;
1258: else if (name.equals("and"))
1259: return Expr.AND;
1260: else if (name.equals("or"))
1261: return Expr.OR;
1262: else
1263: throw error(L.l(
1264: "expected binary operation at `{0}'", name));
1265: }
1266:
1267: unread();
1268: return -1;
1269: }
1270: }
1271:
1272: private String readName(int ch) {
1273: tag.clear();
1274: for (; XmlChar.isNameChar(ch); ch = read())
1275: tag.append((char) ch);
1276:
1277: if (ch == '*' && tag.endsWith(":"))
1278: tag.append((char) ch);
1279: else
1280: unread();
1281:
1282: return tag.toString();
1283: }
1284:
1285: private void undoToken(int token) {
1286: peek = token;
1287: }
1288:
1289: private int read() {
1290: if (index < _string.length()) {
1291: return _string.charAt(index++);
1292: } else {
1293: index++;
1294: return -1;
1295: }
1296: }
1297:
1298: private void unread() {
1299: index--;
1300: }
1301:
1302: private XPathParseException error(String message) {
1303: return new XPathParseException(message + " in " + _string);
1304: }
1305:
1306: private String badChar(int ch) {
1307: if (ch < 0)
1308: return L.l("end of file");
1309: else if (ch == '\n')
1310: return L.l("end of line");
1311: else
1312: return "`" + (char) ch + "'";
1313: }
1314:
1315: private int skipWhitespace(int ch) throws XPathParseException {
1316: for (; ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'; ch = read()) {
1317: }
1318:
1319: return ch;
1320: }
1321:
1322: private static void addFunction(String name, Class cl) {
1323: Constructor[] constructors = cl.getConstructors();
1324:
1325: _exprFunctions.put(name, constructors[0]);
1326: }
1327:
1328: static {
1329: exprFunctions = new IntMap();
1330: exprFunctions.put("id", Expr.ID);
1331:
1332: exprFunctions.put("true", Expr.TRUE);
1333: exprFunctions.put("false", Expr.FALSE);
1334: exprFunctions.put("not", Expr.NOT);
1335: exprFunctions.put("boolean", Expr.BOOLEAN);
1336: exprFunctions.put("starts-with", Expr.STARTS_WITH);
1337: exprFunctions.put("contains", Expr.CONTAINS);
1338: exprFunctions.put("lang", Expr.LANG);
1339:
1340: exprFunctions.put("number", Expr.NUMBER);
1341: exprFunctions.put("sum", Expr.SUM);
1342: exprFunctions.put("floor", Expr.FLOOR);
1343: exprFunctions.put("ceiling", Expr.CEILING);
1344: exprFunctions.put("round", Expr.ROUND);
1345: exprFunctions.put("position", Expr.POSITION);
1346: exprFunctions.put("count", Expr.COUNT);
1347: exprFunctions.put("last", Expr.LAST);
1348: exprFunctions.put("string-length", Expr.STRING_LENGTH);
1349:
1350: exprFunctions.put("string", Expr.STRING);
1351: exprFunctions.put("concat", Expr.CONCAT);
1352: exprFunctions.put("substring", Expr.SUBSTRING);
1353: exprFunctions.put("substring-before", Expr.SUBSTRING_BEFORE);
1354: exprFunctions.put("substring-after", Expr.SUBSTRING_AFTER);
1355: exprFunctions.put("normalize-space", Expr.NORMALIZE);
1356: exprFunctions.put("translate", Expr.TRANSLATE);
1357:
1358: exprFunctions.put("local-name", Expr.LOCAL_PART);
1359: exprFunctions.put("local-part", Expr.LOCAL_PART);
1360: exprFunctions.put("namespace-uri", Expr.NAMESPACE);
1361: exprFunctions.put("name", Expr.QNAME);
1362: exprFunctions.put("generate-id", Expr.GENERATE_ID);
1363:
1364: exprFunctions.put("if", Expr.IF);
1365:
1366: exprFunctions.put("text", TEXT);
1367: exprFunctions.put("comment", COMMENT);
1368: exprFunctions.put("er", ER);
1369: exprFunctions.put("entity-reference", ER);
1370: exprFunctions.put("pi", PI);
1371: exprFunctions.put("processing-instruction", PI);
1372: exprFunctions.put("node", NODE);
1373: exprFunctions.put("current", CURRENT);
1374: exprFunctions.put("context", CONTEXT);
1375:
1376: axisMap = new IntMap();
1377: axisMap.put("ancestor", ANCESTOR_AXIS);
1378: axisMap.put("ancestor-or-self", ANCESTOR_OR_SELF_AXIS);
1379: axisMap.put("attribute", ATTRIBUTE_AXIS);
1380: axisMap.put("child", CHILD_AXIS);
1381: axisMap.put("descendant", DESCENDANT_AXIS);
1382: axisMap.put("descendant-or-self", DESCENDANT_OR_SELF_AXIS);
1383: axisMap.put("following", FOLLOWING_AXIS);
1384: axisMap.put("following-sibling", FOLLOWING_SIBLING_AXIS);
1385: axisMap.put("namespace", NAMESPACE_AXIS);
1386: axisMap.put("parent", PARENT_AXIS);
1387: axisMap.put("preceding", PRECEDING_AXIS);
1388: axisMap.put("preceding-sibling", PRECEDING_SIBLING_AXIS);
1389: axisMap.put("self", SELF_AXIS);
1390:
1391: _exprFunctions = new HashMap<String, Constructor>();
1392: addFunction("fn:base-uri", BaseURI.class);
1393: addFunction("fn:resolve-uri", ResolveURI.class);
1394: addFunction("fn:trace", Trace.class);
1395: }
1396: }
|