0001: /*
0002: * Copyright 1999-2004 The Apache Software Foundation.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: /*
0017: * $Id: XPathParser.java,v 1.33 2005/05/17 21:25:46 jycli Exp $
0018: */
0019: package org.apache.xpath.compiler;
0020:
0021: import javax.xml.transform.ErrorListener;
0022: import javax.xml.transform.TransformerException;
0023:
0024: import org.apache.xalan.res.XSLMessages;
0025: import org.apache.xml.utils.PrefixResolver;
0026: import org.apache.xpath.XPathProcessorException;
0027: import org.apache.xpath.domapi.XPathStylesheetDOM3Exception;
0028: import org.apache.xpath.objects.XNumber;
0029: import org.apache.xpath.objects.XString;
0030: import org.apache.xpath.res.XPATHErrorResources;
0031:
0032: /**
0033: * Tokenizes and parses XPath expressions. This should really be named
0034: * XPathParserImpl, and may be renamed in the future.
0035: * @xsl.usage general
0036: */
0037: public class XPathParser {
0038: // %REVIEW% Is there a better way of doing this?
0039: // Upside is minimum object churn. Downside is that we don't have a useful
0040: // backtrace in the exception itself -- but we don't expect to need one.
0041: static public final String CONTINUE_AFTER_FATAL_ERROR = "CONTINUE_AFTER_FATAL_ERROR";
0042:
0043: /**
0044: * The XPath to be processed.
0045: */
0046: private OpMap m_ops;
0047:
0048: /**
0049: * The next token in the pattern.
0050: */
0051: transient String m_token;
0052:
0053: /**
0054: * The first char in m_token, the theory being that this
0055: * is an optimization because we won't have to do charAt(0) as
0056: * often.
0057: */
0058: transient char m_tokenChar = 0;
0059:
0060: /**
0061: * The position in the token queue is tracked by m_queueMark.
0062: */
0063: int m_queueMark = 0;
0064:
0065: /**
0066: * Results from checking FilterExpr syntax
0067: */
0068: protected final static int FILTER_MATCH_FAILED = 0;
0069: protected final static int FILTER_MATCH_PRIMARY = 1;
0070: protected final static int FILTER_MATCH_PREDICATES = 2;
0071:
0072: /**
0073: * The parser constructor.
0074: */
0075: public XPathParser(ErrorListener errorListener,
0076: javax.xml.transform.SourceLocator sourceLocator) {
0077: m_errorListener = errorListener;
0078: m_sourceLocator = sourceLocator;
0079: }
0080:
0081: /**
0082: * The prefix resolver to map prefixes to namespaces in the OpMap.
0083: */
0084: PrefixResolver m_namespaceContext;
0085:
0086: /**
0087: * Given an string, init an XPath object for selections,
0088: * in order that a parse doesn't
0089: * have to be done each time the expression is evaluated.
0090: *
0091: * @param compiler The compiler object.
0092: * @param expression A string conforming to the XPath grammar.
0093: * @param namespaceContext An object that is able to resolve prefixes in
0094: * the XPath to namespaces.
0095: *
0096: * @throws javax.xml.transform.TransformerException
0097: */
0098: public void initXPath(Compiler compiler, String expression,
0099: PrefixResolver namespaceContext)
0100: throws javax.xml.transform.TransformerException {
0101:
0102: m_ops = compiler;
0103: m_namespaceContext = namespaceContext;
0104: m_functionTable = compiler.getFunctionTable();
0105:
0106: Lexer lexer = new Lexer(compiler, namespaceContext, this );
0107:
0108: lexer.tokenize(expression);
0109:
0110: m_ops.setOp(0, OpCodes.OP_XPATH);
0111: m_ops.setOp(OpMap.MAPINDEX_LENGTH, 2);
0112:
0113: // Patch for Christine's gripe. She wants her errorHandler to return from
0114: // a fatal error and continue trying to parse, rather than throwing an exception.
0115: // Without the patch, that put us into an endless loop.
0116: //
0117: // %REVIEW% Is there a better way of doing this?
0118: // %REVIEW% Are there any other cases which need the safety net?
0119: // (and if so do we care right now, or should we rewrite the XPath
0120: // grammar engine and can fix it at that time?)
0121: try {
0122:
0123: nextToken();
0124: Expr();
0125:
0126: if (null != m_token) {
0127: String extraTokens = "";
0128:
0129: while (null != m_token) {
0130: extraTokens += "'" + m_token + "'";
0131:
0132: nextToken();
0133:
0134: if (null != m_token)
0135: extraTokens += ", ";
0136: }
0137:
0138: error(XPATHErrorResources.ER_EXTRA_ILLEGAL_TOKENS,
0139: new Object[] { extraTokens }); //"Extra illegal tokens: "+extraTokens);
0140: }
0141:
0142: } catch (org.apache.xpath.XPathProcessorException e) {
0143: if (CONTINUE_AFTER_FATAL_ERROR.equals(e.getMessage())) {
0144: // What I _want_ to do is null out this XPath.
0145: // I doubt this has the desired effect, but I'm not sure what else to do.
0146: // %REVIEW%!!!
0147: initXPath(compiler, "/..", namespaceContext);
0148: } else
0149: throw e;
0150: }
0151:
0152: compiler.shrink();
0153: }
0154:
0155: /**
0156: * Given an string, init an XPath object for pattern matches,
0157: * in order that a parse doesn't
0158: * have to be done each time the expression is evaluated.
0159: * @param compiler The XPath object to be initialized.
0160: * @param expression A String representing the XPath.
0161: * @param namespaceContext An object that is able to resolve prefixes in
0162: * the XPath to namespaces.
0163: *
0164: * @throws javax.xml.transform.TransformerException
0165: */
0166: public void initMatchPattern(Compiler compiler, String expression,
0167: PrefixResolver namespaceContext)
0168: throws javax.xml.transform.TransformerException {
0169:
0170: m_ops = compiler;
0171: m_namespaceContext = namespaceContext;
0172: m_functionTable = compiler.getFunctionTable();
0173:
0174: Lexer lexer = new Lexer(compiler, namespaceContext, this );
0175:
0176: lexer.tokenize(expression);
0177:
0178: m_ops.setOp(0, OpCodes.OP_MATCHPATTERN);
0179: m_ops.setOp(OpMap.MAPINDEX_LENGTH, 2);
0180:
0181: nextToken();
0182: Pattern();
0183:
0184: if (null != m_token) {
0185: String extraTokens = "";
0186:
0187: while (null != m_token) {
0188: extraTokens += "'" + m_token + "'";
0189:
0190: nextToken();
0191:
0192: if (null != m_token)
0193: extraTokens += ", ";
0194: }
0195:
0196: error(XPATHErrorResources.ER_EXTRA_ILLEGAL_TOKENS,
0197: new Object[] { extraTokens }); //"Extra illegal tokens: "+extraTokens);
0198: }
0199:
0200: // Terminate for safety.
0201: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
0202: m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops
0203: .getOp(OpMap.MAPINDEX_LENGTH) + 1);
0204:
0205: m_ops.shrink();
0206: }
0207:
0208: /** The error listener where syntax errors are to be sent.
0209: */
0210: private ErrorListener m_errorListener;
0211:
0212: /** The source location of the XPath. */
0213: javax.xml.transform.SourceLocator m_sourceLocator;
0214:
0215: /** The table contains build-in functions and customized functions */
0216: private FunctionTable m_functionTable;
0217:
0218: /**
0219: * Allow an application to register an error event handler, where syntax
0220: * errors will be sent. If the error listener is not set, syntax errors
0221: * will be sent to System.err.
0222: *
0223: * @param handler Reference to error listener where syntax errors will be
0224: * sent.
0225: */
0226: public void setErrorHandler(ErrorListener handler) {
0227: m_errorListener = handler;
0228: }
0229:
0230: /**
0231: * Return the current error listener.
0232: *
0233: * @return The error listener, which should not normally be null, but may be.
0234: */
0235: public ErrorListener getErrorListener() {
0236: return m_errorListener;
0237: }
0238:
0239: /**
0240: * Check whether m_token matches the target string.
0241: *
0242: * @param s A string reference or null.
0243: *
0244: * @return If m_token is null, returns false (or true if s is also null), or
0245: * return true if the current token matches the string, else false.
0246: */
0247: final boolean tokenIs(String s) {
0248: return (m_token != null) ? (m_token.equals(s)) : (s == null);
0249: }
0250:
0251: /**
0252: * Check whether m_tokenChar==c.
0253: *
0254: * @param c A character to be tested.
0255: *
0256: * @return If m_token is null, returns false, or return true if c matches
0257: * the current token.
0258: */
0259: final boolean tokenIs(char c) {
0260: return (m_token != null) ? (m_tokenChar == c) : false;
0261: }
0262:
0263: /**
0264: * Look ahead of the current token in order to
0265: * make a branching decision.
0266: *
0267: * @param c the character to be tested for.
0268: * @param n number of tokens to look ahead. Must be
0269: * greater than 1.
0270: *
0271: * @return true if the next token matches the character argument.
0272: */
0273: final boolean lookahead(char c, int n) {
0274:
0275: int pos = (m_queueMark + n);
0276: boolean b;
0277:
0278: if ((pos <= m_ops.getTokenQueueSize()) && (pos > 0)
0279: && (m_ops.getTokenQueueSize() != 0)) {
0280: String tok = ((String) m_ops.m_tokenQueue
0281: .elementAt(pos - 1));
0282:
0283: b = (tok.length() == 1) ? (tok.charAt(0) == c) : false;
0284: } else {
0285: b = false;
0286: }
0287:
0288: return b;
0289: }
0290:
0291: /**
0292: * Look behind the first character of the current token in order to
0293: * make a branching decision.
0294: *
0295: * @param c the character to compare it to.
0296: * @param n number of tokens to look behind. Must be
0297: * greater than 1. Note that the look behind terminates
0298: * at either the beginning of the string or on a '|'
0299: * character. Because of this, this method should only
0300: * be used for pattern matching.
0301: *
0302: * @return true if the token behind the current token matches the character
0303: * argument.
0304: */
0305: private final boolean lookbehind(char c, int n) {
0306:
0307: boolean isToken;
0308: int lookBehindPos = m_queueMark - (n + 1);
0309:
0310: if (lookBehindPos >= 0) {
0311: String lookbehind = (String) m_ops.m_tokenQueue
0312: .elementAt(lookBehindPos);
0313:
0314: if (lookbehind.length() == 1) {
0315: char c0 = (lookbehind == null) ? '|' : lookbehind
0316: .charAt(0);
0317:
0318: isToken = (c0 == '|') ? false : (c0 == c);
0319: } else {
0320: isToken = false;
0321: }
0322: } else {
0323: isToken = false;
0324: }
0325:
0326: return isToken;
0327: }
0328:
0329: /**
0330: * look behind the current token in order to
0331: * see if there is a useable token.
0332: *
0333: * @param n number of tokens to look behind. Must be
0334: * greater than 1. Note that the look behind terminates
0335: * at either the beginning of the string or on a '|'
0336: * character. Because of this, this method should only
0337: * be used for pattern matching.
0338: *
0339: * @return true if look behind has a token, false otherwise.
0340: */
0341: private final boolean lookbehindHasToken(int n) {
0342:
0343: boolean hasToken;
0344:
0345: if ((m_queueMark - n) > 0) {
0346: String lookbehind = (String) m_ops.m_tokenQueue
0347: .elementAt(m_queueMark - (n - 1));
0348: char c0 = (lookbehind == null) ? '|' : lookbehind.charAt(0);
0349:
0350: hasToken = (c0 == '|') ? false : true;
0351: } else {
0352: hasToken = false;
0353: }
0354:
0355: return hasToken;
0356: }
0357:
0358: /**
0359: * Look ahead of the current token in order to
0360: * make a branching decision.
0361: *
0362: * @param s the string to compare it to.
0363: * @param n number of tokens to lookahead. Must be
0364: * greater than 1.
0365: *
0366: * @return true if the token behind the current token matches the string
0367: * argument.
0368: */
0369: private final boolean lookahead(String s, int n) {
0370:
0371: boolean isToken;
0372:
0373: if ((m_queueMark + n) <= m_ops.getTokenQueueSize()) {
0374: String lookahead = (String) m_ops.m_tokenQueue
0375: .elementAt(m_queueMark + (n - 1));
0376:
0377: isToken = (lookahead != null) ? lookahead.equals(s)
0378: : (s == null);
0379: } else {
0380: isToken = (null == s);
0381: }
0382:
0383: return isToken;
0384: }
0385:
0386: /**
0387: * Retrieve the next token from the command and
0388: * store it in m_token string.
0389: */
0390: private final void nextToken() {
0391:
0392: if (m_queueMark < m_ops.getTokenQueueSize()) {
0393: m_token = (String) m_ops.m_tokenQueue
0394: .elementAt(m_queueMark++);
0395: m_tokenChar = m_token.charAt(0);
0396: } else {
0397: m_token = null;
0398: m_tokenChar = 0;
0399: }
0400: }
0401:
0402: /**
0403: * Retrieve a token relative to the current token.
0404: *
0405: * @param i Position relative to current token.
0406: *
0407: * @return The string at the given index, or null if the index is out
0408: * of range.
0409: */
0410: private final String getTokenRelative(int i) {
0411:
0412: String tok;
0413: int relative = m_queueMark + i;
0414:
0415: if ((relative > 0) && (relative < m_ops.getTokenQueueSize())) {
0416: tok = (String) m_ops.m_tokenQueue.elementAt(relative);
0417: } else {
0418: tok = null;
0419: }
0420:
0421: return tok;
0422: }
0423:
0424: /**
0425: * Retrieve the previous token from the command and
0426: * store it in m_token string.
0427: */
0428: private final void prevToken() {
0429:
0430: if (m_queueMark > 0) {
0431: m_queueMark--;
0432:
0433: m_token = (String) m_ops.m_tokenQueue
0434: .elementAt(m_queueMark);
0435: m_tokenChar = m_token.charAt(0);
0436: } else {
0437: m_token = null;
0438: m_tokenChar = 0;
0439: }
0440: }
0441:
0442: /**
0443: * Consume an expected token, throwing an exception if it
0444: * isn't there.
0445: *
0446: * @param expected The string to be expected.
0447: *
0448: * @throws javax.xml.transform.TransformerException
0449: */
0450: private final void consumeExpected(String expected)
0451: throws javax.xml.transform.TransformerException {
0452:
0453: if (tokenIs(expected)) {
0454: nextToken();
0455: } else {
0456: error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND,
0457: new Object[] { expected, m_token }); //"Expected "+expected+", but found: "+m_token);
0458:
0459: // Patch for Christina's gripe. She wants her errorHandler to return from
0460: // this error and continue trying to parse, rather than throwing an exception.
0461: // Without the patch, that put us into an endless loop.
0462: throw new XPathProcessorException(
0463: CONTINUE_AFTER_FATAL_ERROR);
0464: }
0465: }
0466:
0467: /**
0468: * Consume an expected token, throwing an exception if it
0469: * isn't there.
0470: *
0471: * @param expected the character to be expected.
0472: *
0473: * @throws javax.xml.transform.TransformerException
0474: */
0475: private final void consumeExpected(char expected)
0476: throws javax.xml.transform.TransformerException {
0477:
0478: if (tokenIs(expected)) {
0479: nextToken();
0480: } else {
0481: error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND,
0482: new Object[] { String.valueOf(expected), m_token }); //"Expected "+expected+", but found: "+m_token);
0483:
0484: // Patch for Christina's gripe. She wants her errorHandler to return from
0485: // this error and continue trying to parse, rather than throwing an exception.
0486: // Without the patch, that put us into an endless loop.
0487: throw new XPathProcessorException(
0488: CONTINUE_AFTER_FATAL_ERROR);
0489: }
0490: }
0491:
0492: /**
0493: * Warn the user of a problem.
0494: *
0495: * @param msg An error msgkey that corresponds to one of the constants found
0496: * in {@link org.apache.xpath.res.XPATHErrorResources}, which is
0497: * a key for a format string.
0498: * @param args An array of arguments represented in the format string, which
0499: * may be null.
0500: *
0501: * @throws TransformerException if the current ErrorListoner determines to
0502: * throw an exception.
0503: */
0504: void warn(String msg, Object[] args) throws TransformerException {
0505:
0506: String fmsg = XSLMessages.createXPATHWarning(msg, args);
0507: ErrorListener ehandler = this .getErrorListener();
0508:
0509: if (null != ehandler) {
0510: // TO DO: Need to get stylesheet Locator from here.
0511: ehandler.warning(new TransformerException(fmsg,
0512: m_sourceLocator));
0513: } else {
0514: // Should never happen.
0515: System.err.println(fmsg);
0516: }
0517: }
0518:
0519: /**
0520: * Notify the user of an assertion error, and probably throw an
0521: * exception.
0522: *
0523: * @param b If false, a runtime exception will be thrown.
0524: * @param msg The assertion message, which should be informative.
0525: *
0526: * @throws RuntimeException if the b argument is false.
0527: */
0528: private void assertion(boolean b, String msg) {
0529:
0530: if (!b) {
0531: String fMsg = XSLMessages
0532: .createXPATHMessage(
0533: XPATHErrorResources.ER_INCORRECT_PROGRAMMER_ASSERTION,
0534: new Object[] { msg });
0535:
0536: throw new RuntimeException(fMsg);
0537: }
0538: }
0539:
0540: /**
0541: * Notify the user of an error, and probably throw an
0542: * exception.
0543: *
0544: * @param msg An error msgkey that corresponds to one of the constants found
0545: * in {@link org.apache.xpath.res.XPATHErrorResources}, which is
0546: * a key for a format string.
0547: * @param args An array of arguments represented in the format string, which
0548: * may be null.
0549: *
0550: * @throws TransformerException if the current ErrorListoner determines to
0551: * throw an exception.
0552: */
0553: void error(String msg, Object[] args) throws TransformerException {
0554:
0555: String fmsg = XSLMessages.createXPATHMessage(msg, args);
0556: ErrorListener ehandler = this .getErrorListener();
0557:
0558: TransformerException te = new TransformerException(fmsg,
0559: m_sourceLocator);
0560: if (null != ehandler) {
0561: // TO DO: Need to get stylesheet Locator from here.
0562: ehandler.fatalError(te);
0563: } else {
0564: // System.err.println(fmsg);
0565: throw te;
0566: }
0567: }
0568:
0569: /**
0570: * This method is added to support DOM 3 XPath API.
0571: * <p>
0572: * This method is exactly like error(String, Object[]); except that
0573: * the underlying TransformerException is
0574: * XpathStylesheetDOM3Exception (which extends TransformerException).
0575: * <p>
0576: * So older XPath code in Xalan is not affected by this. To older XPath code
0577: * the behavior of whether error() or errorForDOM3() is called because it is
0578: * always catching TransformerException objects and is oblivious to
0579: * the new subclass of XPathStylesheetDOM3Exception. Older XPath code
0580: * runs as before.
0581: * <p>
0582: * However, newer DOM3 XPath code upon catching a TransformerException can
0583: * can check if the exception is an instance of XPathStylesheetDOM3Exception
0584: * and take appropriate action.
0585: *
0586: * @param msg An error msgkey that corresponds to one of the constants found
0587: * in {@link org.apache.xpath.res.XPATHErrorResources}, which is
0588: * a key for a format string.
0589: * @param args An array of arguments represented in the format string, which
0590: * may be null.
0591: *
0592: * @throws TransformerException if the current ErrorListoner determines to
0593: * throw an exception.
0594: */
0595: void errorForDOM3(String msg, Object[] args)
0596: throws TransformerException {
0597:
0598: String fmsg = XSLMessages.createXPATHMessage(msg, args);
0599: ErrorListener ehandler = this .getErrorListener();
0600:
0601: TransformerException te = new XPathStylesheetDOM3Exception(
0602: fmsg, m_sourceLocator);
0603: if (null != ehandler) {
0604: // TO DO: Need to get stylesheet Locator from here.
0605: ehandler.fatalError(te);
0606: } else {
0607: // System.err.println(fmsg);
0608: throw te;
0609: }
0610: }
0611:
0612: /**
0613: * Dump the remaining token queue.
0614: * Thanks to Craig for this.
0615: *
0616: * @return A dump of the remaining token queue, which may be appended to
0617: * an error message.
0618: */
0619: protected String dumpRemainingTokenQueue() {
0620:
0621: int q = m_queueMark;
0622: String returnMsg;
0623:
0624: if (q < m_ops.getTokenQueueSize()) {
0625: String msg = "\n Remaining tokens: (";
0626:
0627: while (q < m_ops.getTokenQueueSize()) {
0628: String t = (String) m_ops.m_tokenQueue.elementAt(q++);
0629:
0630: msg += (" '" + t + "'");
0631: }
0632:
0633: returnMsg = msg + ")";
0634: } else {
0635: returnMsg = "";
0636: }
0637:
0638: return returnMsg;
0639: }
0640:
0641: /**
0642: * Given a string, return the corresponding function token.
0643: *
0644: * @param key A local name of a function.
0645: *
0646: * @return The function ID, which may correspond to one of the FUNC_XXX
0647: * values found in {@link org.apache.xpath.compiler.FunctionTable}, but may
0648: * be a value installed by an external module.
0649: */
0650: final int getFunctionToken(String key) {
0651:
0652: int tok;
0653:
0654: Object id;
0655:
0656: try {
0657: // These are nodetests, xpathparser treats them as functions when parsing
0658: // a FilterExpr.
0659: id = Keywords.lookupNodeTest(key);
0660: if (null == id)
0661: id = m_functionTable.getFunctionID(key);
0662: tok = ((Integer) id).intValue();
0663: } catch (NullPointerException npe) {
0664: tok = -1;
0665: } catch (ClassCastException cce) {
0666: tok = -1;
0667: }
0668:
0669: return tok;
0670: }
0671:
0672: /**
0673: * Insert room for operation. This will NOT set
0674: * the length value of the operation, but will update
0675: * the length value for the total expression.
0676: *
0677: * @param pos The position where the op is to be inserted.
0678: * @param length The length of the operation space in the op map.
0679: * @param op The op code to the inserted.
0680: */
0681: void insertOp(int pos, int length, int op) {
0682:
0683: int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
0684:
0685: for (int i = totalLen - 1; i >= pos; i--) {
0686: m_ops.setOp(i + length, m_ops.getOp(i));
0687: }
0688:
0689: m_ops.setOp(pos, op);
0690: m_ops.setOp(OpMap.MAPINDEX_LENGTH, totalLen + length);
0691: }
0692:
0693: /**
0694: * Insert room for operation. This WILL set
0695: * the length value of the operation, and will update
0696: * the length value for the total expression.
0697: *
0698: * @param length The length of the operation.
0699: * @param op The op code to the inserted.
0700: */
0701: void appendOp(int length, int op) {
0702:
0703: int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
0704:
0705: m_ops.setOp(totalLen, op);
0706: m_ops.setOp(totalLen + OpMap.MAPINDEX_LENGTH, length);
0707: m_ops.setOp(OpMap.MAPINDEX_LENGTH, totalLen + length);
0708: }
0709:
0710: // ============= EXPRESSIONS FUNCTIONS =================
0711:
0712: /**
0713: *
0714: *
0715: * Expr ::= OrExpr
0716: *
0717: *
0718: * @throws javax.xml.transform.TransformerException
0719: */
0720: protected void Expr()
0721: throws javax.xml.transform.TransformerException {
0722: OrExpr();
0723: }
0724:
0725: /**
0726: *
0727: *
0728: * OrExpr ::= AndExpr
0729: * | OrExpr 'or' AndExpr
0730: *
0731: *
0732: * @throws javax.xml.transform.TransformerException
0733: */
0734: protected void OrExpr()
0735: throws javax.xml.transform.TransformerException {
0736:
0737: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
0738:
0739: AndExpr();
0740:
0741: if ((null != m_token) && tokenIs("or")) {
0742: nextToken();
0743: insertOp(opPos, 2, OpCodes.OP_OR);
0744: OrExpr();
0745:
0746: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, m_ops
0747: .getOp(OpMap.MAPINDEX_LENGTH)
0748: - opPos);
0749: }
0750: }
0751:
0752: /**
0753: *
0754: *
0755: * AndExpr ::= EqualityExpr
0756: * | AndExpr 'and' EqualityExpr
0757: *
0758: *
0759: * @throws javax.xml.transform.TransformerException
0760: */
0761: protected void AndExpr()
0762: throws javax.xml.transform.TransformerException {
0763:
0764: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
0765:
0766: EqualityExpr(-1);
0767:
0768: if ((null != m_token) && tokenIs("and")) {
0769: nextToken();
0770: insertOp(opPos, 2, OpCodes.OP_AND);
0771: AndExpr();
0772:
0773: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, m_ops
0774: .getOp(OpMap.MAPINDEX_LENGTH)
0775: - opPos);
0776: }
0777: }
0778:
0779: /**
0780: *
0781: * @returns an Object which is either a String, a Number, a Boolean, or a vector
0782: * of nodes.
0783: *
0784: * EqualityExpr ::= RelationalExpr
0785: * | EqualityExpr '=' RelationalExpr
0786: *
0787: *
0788: * @param addPos Position where expression is to be added, or -1 for append.
0789: *
0790: * @return the position at the end of the equality expression.
0791: *
0792: * @throws javax.xml.transform.TransformerException
0793: */
0794: protected int EqualityExpr(int addPos)
0795: throws javax.xml.transform.TransformerException {
0796:
0797: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
0798:
0799: if (-1 == addPos)
0800: addPos = opPos;
0801:
0802: RelationalExpr(-1);
0803:
0804: if (null != m_token) {
0805: if (tokenIs('!') && lookahead('=', 1)) {
0806: nextToken();
0807: nextToken();
0808: insertOp(addPos, 2, OpCodes.OP_NOTEQUALS);
0809:
0810: int opPlusLeftHandLen = m_ops
0811: .getOp(OpMap.MAPINDEX_LENGTH)
0812: - addPos;
0813:
0814: addPos = EqualityExpr(addPos);
0815: m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, m_ops
0816: .getOp(addPos + opPlusLeftHandLen + 1)
0817: + opPlusLeftHandLen);
0818: addPos += 2;
0819: } else if (tokenIs('=')) {
0820: nextToken();
0821: insertOp(addPos, 2, OpCodes.OP_EQUALS);
0822:
0823: int opPlusLeftHandLen = m_ops
0824: .getOp(OpMap.MAPINDEX_LENGTH)
0825: - addPos;
0826:
0827: addPos = EqualityExpr(addPos);
0828: m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, m_ops
0829: .getOp(addPos + opPlusLeftHandLen + 1)
0830: + opPlusLeftHandLen);
0831: addPos += 2;
0832: }
0833: }
0834:
0835: return addPos;
0836: }
0837:
0838: /**
0839: * .
0840: * @returns an Object which is either a String, a Number, a Boolean, or a vector
0841: * of nodes.
0842: *
0843: * RelationalExpr ::= AdditiveExpr
0844: * | RelationalExpr '<' AdditiveExpr
0845: * | RelationalExpr '>' AdditiveExpr
0846: * | RelationalExpr '<=' AdditiveExpr
0847: * | RelationalExpr '>=' AdditiveExpr
0848: *
0849: *
0850: * @param addPos Position where expression is to be added, or -1 for append.
0851: *
0852: * @return the position at the end of the relational expression.
0853: *
0854: * @throws javax.xml.transform.TransformerException
0855: */
0856: protected int RelationalExpr(int addPos)
0857: throws javax.xml.transform.TransformerException {
0858:
0859: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
0860:
0861: if (-1 == addPos)
0862: addPos = opPos;
0863:
0864: AdditiveExpr(-1);
0865:
0866: if (null != m_token) {
0867: if (tokenIs('<')) {
0868: nextToken();
0869:
0870: if (tokenIs('=')) {
0871: nextToken();
0872: insertOp(addPos, 2, OpCodes.OP_LTE);
0873: } else {
0874: insertOp(addPos, 2, OpCodes.OP_LT);
0875: }
0876:
0877: int opPlusLeftHandLen = m_ops
0878: .getOp(OpMap.MAPINDEX_LENGTH)
0879: - addPos;
0880:
0881: addPos = RelationalExpr(addPos);
0882: m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, m_ops
0883: .getOp(addPos + opPlusLeftHandLen + 1)
0884: + opPlusLeftHandLen);
0885: addPos += 2;
0886: } else if (tokenIs('>')) {
0887: nextToken();
0888:
0889: if (tokenIs('=')) {
0890: nextToken();
0891: insertOp(addPos, 2, OpCodes.OP_GTE);
0892: } else {
0893: insertOp(addPos, 2, OpCodes.OP_GT);
0894: }
0895:
0896: int opPlusLeftHandLen = m_ops
0897: .getOp(OpMap.MAPINDEX_LENGTH)
0898: - addPos;
0899:
0900: addPos = RelationalExpr(addPos);
0901: m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, m_ops
0902: .getOp(addPos + opPlusLeftHandLen + 1)
0903: + opPlusLeftHandLen);
0904: addPos += 2;
0905: }
0906: }
0907:
0908: return addPos;
0909: }
0910:
0911: /**
0912: * This has to handle construction of the operations so that they are evaluated
0913: * in pre-fix order. So, for 9+7-6, instead of |+|9|-|7|6|, this needs to be
0914: * evaluated as |-|+|9|7|6|.
0915: *
0916: * AdditiveExpr ::= MultiplicativeExpr
0917: * | AdditiveExpr '+' MultiplicativeExpr
0918: * | AdditiveExpr '-' MultiplicativeExpr
0919: *
0920: *
0921: * @param addPos Position where expression is to be added, or -1 for append.
0922: *
0923: * @return the position at the end of the equality expression.
0924: *
0925: * @throws javax.xml.transform.TransformerException
0926: */
0927: protected int AdditiveExpr(int addPos)
0928: throws javax.xml.transform.TransformerException {
0929:
0930: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
0931:
0932: if (-1 == addPos)
0933: addPos = opPos;
0934:
0935: MultiplicativeExpr(-1);
0936:
0937: if (null != m_token) {
0938: if (tokenIs('+')) {
0939: nextToken();
0940: insertOp(addPos, 2, OpCodes.OP_PLUS);
0941:
0942: int opPlusLeftHandLen = m_ops
0943: .getOp(OpMap.MAPINDEX_LENGTH)
0944: - addPos;
0945:
0946: addPos = AdditiveExpr(addPos);
0947: m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, m_ops
0948: .getOp(addPos + opPlusLeftHandLen + 1)
0949: + opPlusLeftHandLen);
0950: addPos += 2;
0951: } else if (tokenIs('-')) {
0952: nextToken();
0953: insertOp(addPos, 2, OpCodes.OP_MINUS);
0954:
0955: int opPlusLeftHandLen = m_ops
0956: .getOp(OpMap.MAPINDEX_LENGTH)
0957: - addPos;
0958:
0959: addPos = AdditiveExpr(addPos);
0960: m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, m_ops
0961: .getOp(addPos + opPlusLeftHandLen + 1)
0962: + opPlusLeftHandLen);
0963: addPos += 2;
0964: }
0965: }
0966:
0967: return addPos;
0968: }
0969:
0970: /**
0971: * This has to handle construction of the operations so that they are evaluated
0972: * in pre-fix order. So, for 9+7-6, instead of |+|9|-|7|6|, this needs to be
0973: * evaluated as |-|+|9|7|6|.
0974: *
0975: * MultiplicativeExpr ::= UnaryExpr
0976: * | MultiplicativeExpr MultiplyOperator UnaryExpr
0977: * | MultiplicativeExpr 'div' UnaryExpr
0978: * | MultiplicativeExpr 'mod' UnaryExpr
0979: * | MultiplicativeExpr 'quo' UnaryExpr
0980: *
0981: * @param addPos Position where expression is to be added, or -1 for append.
0982: *
0983: * @return the position at the end of the equality expression.
0984: *
0985: * @throws javax.xml.transform.TransformerException
0986: */
0987: protected int MultiplicativeExpr(int addPos)
0988: throws javax.xml.transform.TransformerException {
0989:
0990: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
0991:
0992: if (-1 == addPos)
0993: addPos = opPos;
0994:
0995: UnaryExpr();
0996:
0997: if (null != m_token) {
0998: if (tokenIs('*')) {
0999: nextToken();
1000: insertOp(addPos, 2, OpCodes.OP_MULT);
1001:
1002: int opPlusLeftHandLen = m_ops
1003: .getOp(OpMap.MAPINDEX_LENGTH)
1004: - addPos;
1005:
1006: addPos = MultiplicativeExpr(addPos);
1007: m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, m_ops
1008: .getOp(addPos + opPlusLeftHandLen + 1)
1009: + opPlusLeftHandLen);
1010: addPos += 2;
1011: } else if (tokenIs("div")) {
1012: nextToken();
1013: insertOp(addPos, 2, OpCodes.OP_DIV);
1014:
1015: int opPlusLeftHandLen = m_ops
1016: .getOp(OpMap.MAPINDEX_LENGTH)
1017: - addPos;
1018:
1019: addPos = MultiplicativeExpr(addPos);
1020: m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, m_ops
1021: .getOp(addPos + opPlusLeftHandLen + 1)
1022: + opPlusLeftHandLen);
1023: addPos += 2;
1024: } else if (tokenIs("mod")) {
1025: nextToken();
1026: insertOp(addPos, 2, OpCodes.OP_MOD);
1027:
1028: int opPlusLeftHandLen = m_ops
1029: .getOp(OpMap.MAPINDEX_LENGTH)
1030: - addPos;
1031:
1032: addPos = MultiplicativeExpr(addPos);
1033: m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, m_ops
1034: .getOp(addPos + opPlusLeftHandLen + 1)
1035: + opPlusLeftHandLen);
1036: addPos += 2;
1037: } else if (tokenIs("quo")) {
1038: nextToken();
1039: insertOp(addPos, 2, OpCodes.OP_QUO);
1040:
1041: int opPlusLeftHandLen = m_ops
1042: .getOp(OpMap.MAPINDEX_LENGTH)
1043: - addPos;
1044:
1045: addPos = MultiplicativeExpr(addPos);
1046: m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, m_ops
1047: .getOp(addPos + opPlusLeftHandLen + 1)
1048: + opPlusLeftHandLen);
1049: addPos += 2;
1050: }
1051: }
1052:
1053: return addPos;
1054: }
1055:
1056: /**
1057: *
1058: * UnaryExpr ::= UnionExpr
1059: * | '-' UnaryExpr
1060: *
1061: *
1062: * @throws javax.xml.transform.TransformerException
1063: */
1064: protected void UnaryExpr()
1065: throws javax.xml.transform.TransformerException {
1066:
1067: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1068: boolean isNeg = false;
1069:
1070: if (m_tokenChar == '-') {
1071: nextToken();
1072: appendOp(2, OpCodes.OP_NEG);
1073:
1074: isNeg = true;
1075: }
1076:
1077: UnionExpr();
1078:
1079: if (isNeg)
1080: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, m_ops
1081: .getOp(OpMap.MAPINDEX_LENGTH)
1082: - opPos);
1083: }
1084:
1085: /**
1086: *
1087: * StringExpr ::= Expr
1088: *
1089: *
1090: * @throws javax.xml.transform.TransformerException
1091: */
1092: protected void StringExpr()
1093: throws javax.xml.transform.TransformerException {
1094:
1095: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1096:
1097: appendOp(2, OpCodes.OP_STRING);
1098: Expr();
1099:
1100: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, m_ops
1101: .getOp(OpMap.MAPINDEX_LENGTH)
1102: - opPos);
1103: }
1104:
1105: /**
1106: *
1107: *
1108: * StringExpr ::= Expr
1109: *
1110: *
1111: * @throws javax.xml.transform.TransformerException
1112: */
1113: protected void BooleanExpr()
1114: throws javax.xml.transform.TransformerException {
1115:
1116: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1117:
1118: appendOp(2, OpCodes.OP_BOOL);
1119: Expr();
1120:
1121: int opLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos;
1122:
1123: if (opLen == 2) {
1124: error(
1125: XPATHErrorResources.ER_BOOLEAN_ARG_NO_LONGER_OPTIONAL,
1126: null); //"boolean(...) argument is no longer optional with 19990709 XPath draft.");
1127: }
1128:
1129: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, opLen);
1130: }
1131:
1132: /**
1133: *
1134: *
1135: * NumberExpr ::= Expr
1136: *
1137: *
1138: * @throws javax.xml.transform.TransformerException
1139: */
1140: protected void NumberExpr()
1141: throws javax.xml.transform.TransformerException {
1142:
1143: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1144:
1145: appendOp(2, OpCodes.OP_NUMBER);
1146: Expr();
1147:
1148: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, m_ops
1149: .getOp(OpMap.MAPINDEX_LENGTH)
1150: - opPos);
1151: }
1152:
1153: /**
1154: * The context of the right hand side expressions is the context of the
1155: * left hand side expression. The results of the right hand side expressions
1156: * are node sets. The result of the left hand side UnionExpr is the union
1157: * of the results of the right hand side expressions.
1158: *
1159: *
1160: * UnionExpr ::= PathExpr
1161: * | UnionExpr '|' PathExpr
1162: *
1163: *
1164: * @throws javax.xml.transform.TransformerException
1165: */
1166: protected void UnionExpr()
1167: throws javax.xml.transform.TransformerException {
1168:
1169: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1170: boolean continueOrLoop = true;
1171: boolean foundUnion = false;
1172:
1173: do {
1174: PathExpr();
1175:
1176: if (tokenIs('|')) {
1177: if (false == foundUnion) {
1178: foundUnion = true;
1179:
1180: insertOp(opPos, 2, OpCodes.OP_UNION);
1181: }
1182:
1183: nextToken();
1184: } else {
1185: break;
1186: }
1187:
1188: // this.m_testForDocOrder = true;
1189: } while (continueOrLoop);
1190:
1191: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, m_ops
1192: .getOp(OpMap.MAPINDEX_LENGTH)
1193: - opPos);
1194: }
1195:
1196: /**
1197: * PathExpr ::= LocationPath
1198: * | FilterExpr
1199: * | FilterExpr '/' RelativeLocationPath
1200: * | FilterExpr '//' RelativeLocationPath
1201: *
1202: * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
1203: * the error condition is severe enough to halt processing.
1204: *
1205: * @throws javax.xml.transform.TransformerException
1206: */
1207: protected void PathExpr()
1208: throws javax.xml.transform.TransformerException {
1209:
1210: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1211:
1212: int filterExprMatch = FilterExpr();
1213:
1214: if (filterExprMatch != FILTER_MATCH_FAILED) {
1215: // If FilterExpr had Predicates, a OP_LOCATIONPATH opcode would already
1216: // have been inserted.
1217: boolean locationPathStarted = (filterExprMatch == FILTER_MATCH_PREDICATES);
1218:
1219: if (tokenIs('/')) {
1220: nextToken();
1221:
1222: if (!locationPathStarted) {
1223: // int locationPathOpPos = opPos;
1224: insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH);
1225:
1226: locationPathStarted = true;
1227: }
1228:
1229: if (!RelativeLocationPath()) {
1230: // "Relative location path expected following '/' or '//'"
1231: error(XPATHErrorResources.ER_EXPECTED_REL_LOC_PATH,
1232: null);
1233: }
1234:
1235: }
1236:
1237: // Terminate for safety.
1238: if (locationPathStarted) {
1239: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH),
1240: OpCodes.ENDOP);
1241: m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops
1242: .getOp(OpMap.MAPINDEX_LENGTH) + 1);
1243: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, m_ops
1244: .getOp(OpMap.MAPINDEX_LENGTH)
1245: - opPos);
1246: }
1247: } else {
1248: LocationPath();
1249: }
1250: }
1251:
1252: /**
1253: *
1254: *
1255: * FilterExpr ::= PrimaryExpr
1256: * | FilterExpr Predicate
1257: *
1258: * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
1259: * the error condition is severe enough to halt processing.
1260: *
1261: * @return FILTER_MATCH_PREDICATES, if this method successfully matched a
1262: * FilterExpr with one or more Predicates;
1263: * FILTER_MATCH_PRIMARY, if this method successfully matched a
1264: * FilterExpr that was just a PrimaryExpr; or
1265: * FILTER_MATCH_FAILED, if this method did not match a FilterExpr
1266: *
1267: * @throws javax.xml.transform.TransformerException
1268: */
1269: protected int FilterExpr()
1270: throws javax.xml.transform.TransformerException {
1271:
1272: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1273:
1274: int filterMatch;
1275:
1276: if (PrimaryExpr()) {
1277: if (tokenIs('[')) {
1278:
1279: // int locationPathOpPos = opPos;
1280: insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH);
1281:
1282: while (tokenIs('[')) {
1283: Predicate();
1284: }
1285:
1286: filterMatch = FILTER_MATCH_PREDICATES;
1287: } else {
1288: filterMatch = FILTER_MATCH_PRIMARY;
1289: }
1290: } else {
1291: filterMatch = FILTER_MATCH_FAILED;
1292: }
1293:
1294: return filterMatch;
1295:
1296: /*
1297: * if(tokenIs('['))
1298: * {
1299: * Predicate();
1300: * m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos;
1301: * }
1302: */
1303: }
1304:
1305: /**
1306: *
1307: * PrimaryExpr ::= VariableReference
1308: * | '(' Expr ')'
1309: * | Literal
1310: * | Number
1311: * | FunctionCall
1312: *
1313: * @return true if this method successfully matched a PrimaryExpr
1314: *
1315: * @throws javax.xml.transform.TransformerException
1316: *
1317: */
1318: protected boolean PrimaryExpr()
1319: throws javax.xml.transform.TransformerException {
1320:
1321: boolean matchFound;
1322: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1323:
1324: if ((m_tokenChar == '\'') || (m_tokenChar == '"')) {
1325: appendOp(2, OpCodes.OP_LITERAL);
1326: Literal();
1327:
1328: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, m_ops
1329: .getOp(OpMap.MAPINDEX_LENGTH)
1330: - opPos);
1331:
1332: matchFound = true;
1333: } else if (m_tokenChar == '$') {
1334: nextToken(); // consume '$'
1335: appendOp(2, OpCodes.OP_VARIABLE);
1336: QName();
1337:
1338: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, m_ops
1339: .getOp(OpMap.MAPINDEX_LENGTH)
1340: - opPos);
1341:
1342: matchFound = true;
1343: } else if (m_tokenChar == '(') {
1344: nextToken();
1345: appendOp(2, OpCodes.OP_GROUP);
1346: Expr();
1347: consumeExpected(')');
1348:
1349: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, m_ops
1350: .getOp(OpMap.MAPINDEX_LENGTH)
1351: - opPos);
1352:
1353: matchFound = true;
1354: } else if ((null != m_token)
1355: && ((('.' == m_tokenChar) && (m_token.length() > 1) && Character
1356: .isDigit(m_token.charAt(1))) || Character
1357: .isDigit(m_tokenChar))) {
1358: appendOp(2, OpCodes.OP_NUMBERLIT);
1359: Number();
1360:
1361: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, m_ops
1362: .getOp(OpMap.MAPINDEX_LENGTH)
1363: - opPos);
1364:
1365: matchFound = true;
1366: } else if (lookahead('(', 1)
1367: || (lookahead(':', 1) && lookahead('(', 3))) {
1368: matchFound = FunctionCall();
1369: } else {
1370: matchFound = false;
1371: }
1372:
1373: return matchFound;
1374: }
1375:
1376: /**
1377: *
1378: * Argument ::= Expr
1379: *
1380: *
1381: * @throws javax.xml.transform.TransformerException
1382: */
1383: protected void Argument()
1384: throws javax.xml.transform.TransformerException {
1385:
1386: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1387:
1388: appendOp(2, OpCodes.OP_ARGUMENT);
1389: Expr();
1390:
1391: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, m_ops
1392: .getOp(OpMap.MAPINDEX_LENGTH)
1393: - opPos);
1394: }
1395:
1396: /**
1397: *
1398: * FunctionCall ::= FunctionName '(' ( Argument ( ',' Argument)*)? ')'
1399: *
1400: * @return true if, and only if, a FunctionCall was matched
1401: *
1402: * @throws javax.xml.transform.TransformerException
1403: */
1404: protected boolean FunctionCall()
1405: throws javax.xml.transform.TransformerException {
1406:
1407: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1408:
1409: if (lookahead(':', 1)) {
1410: appendOp(4, OpCodes.OP_EXTFUNCTION);
1411:
1412: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
1413: m_queueMark - 1);
1414:
1415: nextToken();
1416: consumeExpected(':');
1417:
1418: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 2,
1419: m_queueMark - 1);
1420:
1421: nextToken();
1422: } else {
1423: int funcTok = getFunctionToken(m_token);
1424:
1425: if (-1 == funcTok) {
1426: error(XPATHErrorResources.ER_COULDNOT_FIND_FUNCTION,
1427: new Object[] { m_token }); //"Could not find function: "+m_token+"()");
1428: }
1429:
1430: switch (funcTok) {
1431: case OpCodes.NODETYPE_PI:
1432: case OpCodes.NODETYPE_COMMENT:
1433: case OpCodes.NODETYPE_TEXT:
1434: case OpCodes.NODETYPE_NODE:
1435: // Node type tests look like function calls, but they're not
1436: return false;
1437: default:
1438: appendOp(3, OpCodes.OP_FUNCTION);
1439:
1440: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, funcTok);
1441: }
1442:
1443: nextToken();
1444: }
1445:
1446: consumeExpected('(');
1447:
1448: while (!tokenIs(')') && m_token != null) {
1449: if (tokenIs(',')) {
1450: error(
1451: XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_PRECEDING_ARG,
1452: null); //"Found ',' but no preceding argument!");
1453: }
1454:
1455: Argument();
1456:
1457: if (!tokenIs(')')) {
1458: consumeExpected(',');
1459:
1460: if (tokenIs(')')) {
1461: error(
1462: XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_FOLLOWING_ARG,
1463: null); //"Found ',' but no following argument!");
1464: }
1465: }
1466: }
1467:
1468: consumeExpected(')');
1469:
1470: // Terminate for safety.
1471: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1472: m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops
1473: .getOp(OpMap.MAPINDEX_LENGTH) + 1);
1474: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, m_ops
1475: .getOp(OpMap.MAPINDEX_LENGTH)
1476: - opPos);
1477:
1478: return true;
1479: }
1480:
1481: // ============= GRAMMAR FUNCTIONS =================
1482:
1483: /**
1484: *
1485: * LocationPath ::= RelativeLocationPath
1486: * | AbsoluteLocationPath
1487: *
1488: *
1489: * @throws javax.xml.transform.TransformerException
1490: */
1491: protected void LocationPath()
1492: throws javax.xml.transform.TransformerException {
1493:
1494: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1495:
1496: // int locationPathOpPos = opPos;
1497: appendOp(2, OpCodes.OP_LOCATIONPATH);
1498:
1499: boolean seenSlash = tokenIs('/');
1500:
1501: if (seenSlash) {
1502: appendOp(4, OpCodes.FROM_ROOT);
1503:
1504: // Tell how long the step is without the predicate
1505: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
1506: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1,
1507: OpCodes.NODETYPE_ROOT);
1508:
1509: nextToken();
1510: } else if (m_token == null) {
1511: error(XPATHErrorResources.ER_EXPECTED_LOC_PATH_AT_END_EXPR,
1512: null);
1513: }
1514:
1515: if (m_token != null) {
1516: if (!RelativeLocationPath() && !seenSlash) {
1517: // Neither a '/' nor a RelativeLocationPath - i.e., matched nothing
1518: // "Location path expected, but found "+m_token+" was encountered."
1519: error(XPATHErrorResources.ER_EXPECTED_LOC_PATH,
1520: new Object[] { m_token });
1521: }
1522: }
1523:
1524: // Terminate for safety.
1525: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1526: m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops
1527: .getOp(OpMap.MAPINDEX_LENGTH) + 1);
1528: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, m_ops
1529: .getOp(OpMap.MAPINDEX_LENGTH)
1530: - opPos);
1531: }
1532:
1533: /**
1534: *
1535: * RelativeLocationPath ::= Step
1536: * | RelativeLocationPath '/' Step
1537: * | AbbreviatedRelativeLocationPath
1538: *
1539: * @returns true if, and only if, a RelativeLocationPath was matched
1540: *
1541: * @throws javax.xml.transform.TransformerException
1542: */
1543: protected boolean RelativeLocationPath()
1544: throws javax.xml.transform.TransformerException {
1545: if (!Step()) {
1546: return false;
1547: }
1548:
1549: while (tokenIs('/')) {
1550: nextToken();
1551:
1552: if (!Step()) {
1553: // RelativeLocationPath can't end with a trailing '/'
1554: // "Location step expected following '/' or '//'"
1555: error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null);
1556: }
1557: }
1558:
1559: return true;
1560: }
1561:
1562: /**
1563: *
1564: * Step ::= Basis Predicate
1565: * | AbbreviatedStep
1566: *
1567: * @returns false if step was empty (or only a '/'); true, otherwise
1568: *
1569: * @throws javax.xml.transform.TransformerException
1570: */
1571: protected boolean Step()
1572: throws javax.xml.transform.TransformerException {
1573: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1574:
1575: boolean doubleSlash = tokenIs('/');
1576:
1577: // At most a single '/' before each Step is consumed by caller; if the
1578: // first thing is a '/', that means we had '//' and the Step must not
1579: // be empty.
1580: if (doubleSlash) {
1581: nextToken();
1582:
1583: appendOp(2, OpCodes.FROM_DESCENDANTS_OR_SELF);
1584:
1585: // Have to fix up for patterns such as '//@foo' or '//attribute::foo',
1586: // which translate to 'descendant-or-self::node()/attribute::foo'.
1587: // notice I leave the '/' on the queue, so the next will be processed
1588: // by a regular step pattern.
1589:
1590: // Make room for telling how long the step is without the predicate
1591: m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops
1592: .getOp(OpMap.MAPINDEX_LENGTH) + 1);
1593: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH),
1594: OpCodes.NODETYPE_NODE);
1595: m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops
1596: .getOp(OpMap.MAPINDEX_LENGTH) + 1);
1597:
1598: // Tell how long the step is without the predicate
1599: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, m_ops
1600: .getOp(OpMap.MAPINDEX_LENGTH)
1601: - opPos);
1602:
1603: // Tell how long the step is with the predicate
1604: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, m_ops
1605: .getOp(OpMap.MAPINDEX_LENGTH)
1606: - opPos);
1607:
1608: opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1609: }
1610:
1611: if (tokenIs(".")) {
1612: nextToken();
1613:
1614: if (tokenIs('[')) {
1615: error(XPATHErrorResources.ER_PREDICATE_ILLEGAL_SYNTAX,
1616: null); //"'..[predicate]' or '.[predicate]' is illegal syntax. Use 'self::node()[predicate]' instead.");
1617: }
1618:
1619: appendOp(4, OpCodes.FROM_SELF);
1620:
1621: // Tell how long the step is without the predicate
1622: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
1623: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1,
1624: OpCodes.NODETYPE_NODE);
1625: } else if (tokenIs("..")) {
1626: nextToken();
1627: appendOp(4, OpCodes.FROM_PARENT);
1628:
1629: // Tell how long the step is without the predicate
1630: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
1631: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1,
1632: OpCodes.NODETYPE_NODE);
1633: }
1634:
1635: // There is probably a better way to test for this
1636: // transition... but it gets real hairy if you try
1637: // to do it in basis().
1638: else if (tokenIs('*')
1639: || tokenIs('@')
1640: || tokenIs('_')
1641: || (m_token != null && Character.isLetter(m_token
1642: .charAt(0)))) {
1643: Basis();
1644:
1645: while (tokenIs('[')) {
1646: Predicate();
1647: }
1648:
1649: // Tell how long the entire step is.
1650: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, m_ops
1651: .getOp(OpMap.MAPINDEX_LENGTH)
1652: - opPos);
1653: } else {
1654: // No Step matched - that's an error if previous thing was a '//'
1655: if (doubleSlash) {
1656: // "Location step expected following '/' or '//'"
1657: error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null);
1658: }
1659:
1660: return false;
1661: }
1662:
1663: return true;
1664: }
1665:
1666: /**
1667: *
1668: * Basis ::= AxisName '::' NodeTest
1669: * | AbbreviatedBasis
1670: *
1671: * @throws javax.xml.transform.TransformerException
1672: */
1673: protected void Basis()
1674: throws javax.xml.transform.TransformerException {
1675:
1676: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1677: int axesType;
1678:
1679: // The next blocks guarantee that a FROM_XXX will be added.
1680: if (lookahead("::", 1)) {
1681: axesType = AxisName();
1682:
1683: nextToken();
1684: nextToken();
1685: } else if (tokenIs('@')) {
1686: axesType = OpCodes.FROM_ATTRIBUTES;
1687:
1688: appendOp(2, axesType);
1689: nextToken();
1690: } else {
1691: axesType = OpCodes.FROM_CHILDREN;
1692:
1693: appendOp(2, axesType);
1694: }
1695:
1696: // Make room for telling how long the step is without the predicate
1697: m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops
1698: .getOp(OpMap.MAPINDEX_LENGTH) + 1);
1699:
1700: NodeTest(axesType);
1701:
1702: // Tell how long the step is without the predicate
1703: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, m_ops
1704: .getOp(OpMap.MAPINDEX_LENGTH)
1705: - opPos);
1706: }
1707:
1708: /**
1709: *
1710: * Basis ::= AxisName '::' NodeTest
1711: * | AbbreviatedBasis
1712: *
1713: * @return FROM_XXX axes type, found in {@link org.apache.xpath.compiler.Keywords}.
1714: *
1715: * @throws javax.xml.transform.TransformerException
1716: */
1717: protected int AxisName()
1718: throws javax.xml.transform.TransformerException {
1719:
1720: Object val = Keywords.getAxisName(m_token);
1721:
1722: if (null == val) {
1723: error(XPATHErrorResources.ER_ILLEGAL_AXIS_NAME,
1724: new Object[] { m_token }); //"illegal axis name: "+m_token);
1725: }
1726:
1727: int axesType = ((Integer) val).intValue();
1728:
1729: appendOp(2, axesType);
1730:
1731: return axesType;
1732: }
1733:
1734: /**
1735: *
1736: * NodeTest ::= WildcardName
1737: * | NodeType '(' ')'
1738: * | 'processing-instruction' '(' Literal ')'
1739: *
1740: * @param axesType FROM_XXX axes type, found in {@link org.apache.xpath.compiler.Keywords}.
1741: *
1742: * @throws javax.xml.transform.TransformerException
1743: */
1744: protected void NodeTest(int axesType)
1745: throws javax.xml.transform.TransformerException {
1746:
1747: if (lookahead('(', 1)) {
1748: Object nodeTestOp = Keywords.getNodeType(m_token);
1749:
1750: if (null == nodeTestOp) {
1751: error(XPATHErrorResources.ER_UNKNOWN_NODETYPE,
1752: new Object[] { m_token }); //"Unknown nodetype: "+m_token);
1753: } else {
1754: nextToken();
1755:
1756: int nt = ((Integer) nodeTestOp).intValue();
1757:
1758: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), nt);
1759: m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops
1760: .getOp(OpMap.MAPINDEX_LENGTH) + 1);
1761:
1762: consumeExpected('(');
1763:
1764: if (OpCodes.NODETYPE_PI == nt) {
1765: if (!tokenIs(')')) {
1766: Literal();
1767: }
1768: }
1769:
1770: consumeExpected(')');
1771: }
1772: } else {
1773:
1774: // Assume name of attribute or element.
1775: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH),
1776: OpCodes.NODENAME);
1777: m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops
1778: .getOp(OpMap.MAPINDEX_LENGTH) + 1);
1779:
1780: if (lookahead(':', 1)) {
1781: if (tokenIs('*')) {
1782: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH),
1783: OpCodes.ELEMWILDCARD);
1784: } else {
1785: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH),
1786: m_queueMark - 1);
1787:
1788: // Minimalist check for an NCName - just check first character
1789: // to distinguish from other possible tokens
1790: if (!Character.isLetter(m_tokenChar)
1791: && !tokenIs('_')) {
1792: // "Node test that matches either NCName:* or QName was expected."
1793: error(
1794: XPATHErrorResources.ER_EXPECTED_NODE_TEST,
1795: null);
1796: }
1797: }
1798:
1799: nextToken();
1800: consumeExpected(':');
1801: } else {
1802: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH),
1803: OpCodes.EMPTY);
1804: }
1805:
1806: m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops
1807: .getOp(OpMap.MAPINDEX_LENGTH) + 1);
1808:
1809: if (tokenIs('*')) {
1810: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH),
1811: OpCodes.ELEMWILDCARD);
1812: } else {
1813: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH),
1814: m_queueMark - 1);
1815:
1816: // Minimalist check for an NCName - just check first character
1817: // to distinguish from other possible tokens
1818: if (!Character.isLetter(m_tokenChar) && !tokenIs('_')) {
1819: // "Node test that matches either NCName:* or QName was expected."
1820: error(XPATHErrorResources.ER_EXPECTED_NODE_TEST,
1821: null);
1822: }
1823: }
1824:
1825: m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops
1826: .getOp(OpMap.MAPINDEX_LENGTH) + 1);
1827:
1828: nextToken();
1829: }
1830: }
1831:
1832: /**
1833: *
1834: * Predicate ::= '[' PredicateExpr ']'
1835: *
1836: *
1837: * @throws javax.xml.transform.TransformerException
1838: */
1839: protected void Predicate()
1840: throws javax.xml.transform.TransformerException {
1841:
1842: if (tokenIs('[')) {
1843: nextToken();
1844: PredicateExpr();
1845: consumeExpected(']');
1846: }
1847: }
1848:
1849: /**
1850: *
1851: * PredicateExpr ::= Expr
1852: *
1853: *
1854: * @throws javax.xml.transform.TransformerException
1855: */
1856: protected void PredicateExpr()
1857: throws javax.xml.transform.TransformerException {
1858:
1859: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1860:
1861: appendOp(2, OpCodes.OP_PREDICATE);
1862: Expr();
1863:
1864: // Terminate for safety.
1865: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1866: m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops
1867: .getOp(OpMap.MAPINDEX_LENGTH) + 1);
1868: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, m_ops
1869: .getOp(OpMap.MAPINDEX_LENGTH)
1870: - opPos);
1871: }
1872:
1873: /**
1874: * QName ::= (Prefix ':')? LocalPart
1875: * Prefix ::= NCName
1876: * LocalPart ::= NCName
1877: *
1878: * @throws javax.xml.transform.TransformerException
1879: */
1880: protected void QName()
1881: throws javax.xml.transform.TransformerException {
1882: // Namespace
1883: if (lookahead(':', 1)) {
1884: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH),
1885: m_queueMark - 1);
1886: m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops
1887: .getOp(OpMap.MAPINDEX_LENGTH) + 1);
1888:
1889: nextToken();
1890: consumeExpected(':');
1891: } else {
1892: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH),
1893: OpCodes.EMPTY);
1894: m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops
1895: .getOp(OpMap.MAPINDEX_LENGTH) + 1);
1896: }
1897:
1898: // Local name
1899: m_ops
1900: .setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH),
1901: m_queueMark - 1);
1902: m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops
1903: .getOp(OpMap.MAPINDEX_LENGTH) + 1);
1904:
1905: nextToken();
1906: }
1907:
1908: /**
1909: * NCName ::= (Letter | '_') (NCNameChar)
1910: * NCNameChar ::= Letter | Digit | '.' | '-' | '_' | CombiningChar | Extender
1911: */
1912: protected void NCName() {
1913:
1914: m_ops
1915: .setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH),
1916: m_queueMark - 1);
1917: m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops
1918: .getOp(OpMap.MAPINDEX_LENGTH) + 1);
1919:
1920: nextToken();
1921: }
1922:
1923: /**
1924: * The value of the Literal is the sequence of characters inside
1925: * the " or ' characters>.
1926: *
1927: * Literal ::= '"' [^"]* '"'
1928: * | "'" [^']* "'"
1929: *
1930: *
1931: * @throws javax.xml.transform.TransformerException
1932: */
1933: protected void Literal()
1934: throws javax.xml.transform.TransformerException {
1935:
1936: int last = m_token.length() - 1;
1937: char c0 = m_tokenChar;
1938: char cX = m_token.charAt(last);
1939:
1940: if (((c0 == '\"') && (cX == '\"'))
1941: || ((c0 == '\'') && (cX == '\''))) {
1942:
1943: // Mutate the token to remove the quotes and have the XString object
1944: // already made.
1945: int tokenQueuePos = m_queueMark - 1;
1946:
1947: m_ops.m_tokenQueue.setElementAt(null, tokenQueuePos);
1948:
1949: Object obj = new XString(m_token.substring(1, last));
1950:
1951: m_ops.m_tokenQueue.setElementAt(obj, tokenQueuePos);
1952:
1953: // lit = m_token.substring(1, last);
1954: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH),
1955: tokenQueuePos);
1956: m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops
1957: .getOp(OpMap.MAPINDEX_LENGTH) + 1);
1958:
1959: nextToken();
1960: } else {
1961: error(
1962: XPATHErrorResources.ER_PATTERN_LITERAL_NEEDS_BE_QUOTED,
1963: new Object[] { m_token }); //"Pattern literal ("+m_token+") needs to be quoted!");
1964: }
1965: }
1966:
1967: /**
1968: *
1969: * Number ::= [0-9]+('.'[0-9]+)? | '.'[0-9]+
1970: *
1971: *
1972: * @throws javax.xml.transform.TransformerException
1973: */
1974: protected void Number()
1975: throws javax.xml.transform.TransformerException {
1976:
1977: if (null != m_token) {
1978:
1979: // Mutate the token to remove the quotes and have the XNumber object
1980: // already made.
1981: double num;
1982:
1983: try {
1984: // XPath 1.0 does not support number in exp notation
1985: if ((m_token.indexOf('e') > -1)
1986: || (m_token.indexOf('E') > -1))
1987: throw new NumberFormatException();
1988: num = Double.valueOf(m_token).doubleValue();
1989: } catch (NumberFormatException nfe) {
1990: num = 0.0; // to shut up compiler.
1991:
1992: error(
1993: XPATHErrorResources.ER_COULDNOT_BE_FORMATTED_TO_NUMBER,
1994: new Object[] { m_token }); //m_token+" could not be formatted to a number!");
1995: }
1996:
1997: m_ops.m_tokenQueue.setElementAt(new XNumber(num),
1998: m_queueMark - 1);
1999: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH),
2000: m_queueMark - 1);
2001: m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops
2002: .getOp(OpMap.MAPINDEX_LENGTH) + 1);
2003:
2004: nextToken();
2005: }
2006: }
2007:
2008: // ============= PATTERN FUNCTIONS =================
2009:
2010: /**
2011: *
2012: * Pattern ::= LocationPathPattern
2013: * | Pattern '|' LocationPathPattern
2014: *
2015: *
2016: * @throws javax.xml.transform.TransformerException
2017: */
2018: protected void Pattern()
2019: throws javax.xml.transform.TransformerException {
2020:
2021: while (true) {
2022: LocationPathPattern();
2023:
2024: if (tokenIs('|')) {
2025: nextToken();
2026: } else {
2027: break;
2028: }
2029: }
2030: }
2031:
2032: /**
2033: *
2034: *
2035: * LocationPathPattern ::= '/' RelativePathPattern?
2036: * | IdKeyPattern (('/' | '//') RelativePathPattern)?
2037: * | '//'? RelativePathPattern
2038: *
2039: *
2040: * @throws javax.xml.transform.TransformerException
2041: */
2042: protected void LocationPathPattern()
2043: throws javax.xml.transform.TransformerException {
2044:
2045: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2046:
2047: final int RELATIVE_PATH_NOT_PERMITTED = 0;
2048: final int RELATIVE_PATH_PERMITTED = 1;
2049: final int RELATIVE_PATH_REQUIRED = 2;
2050:
2051: int relativePathStatus = RELATIVE_PATH_NOT_PERMITTED;
2052:
2053: appendOp(2, OpCodes.OP_LOCATIONPATHPATTERN);
2054:
2055: if (lookahead('(', 1)
2056: && (tokenIs(Keywords.FUNC_ID_STRING) || tokenIs(Keywords.FUNC_KEY_STRING))) {
2057: IdKeyPattern();
2058:
2059: if (tokenIs('/')) {
2060: nextToken();
2061:
2062: if (tokenIs('/')) {
2063: appendOp(4, OpCodes.MATCH_ANY_ANCESTOR);
2064:
2065: nextToken();
2066: } else {
2067: appendOp(4, OpCodes.MATCH_IMMEDIATE_ANCESTOR);
2068: }
2069:
2070: // Tell how long the step is without the predicate
2071: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
2072: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1,
2073: OpCodes.NODETYPE_FUNCTEST);
2074:
2075: relativePathStatus = RELATIVE_PATH_REQUIRED;
2076: }
2077: } else if (tokenIs('/')) {
2078: if (lookahead('/', 1)) {
2079: appendOp(4, OpCodes.MATCH_ANY_ANCESTOR);
2080:
2081: // Added this to fix bug reported by Myriam for match="//x/a"
2082: // patterns. If you don't do this, the 'x' step will think it's part
2083: // of a '//' pattern, and so will cause 'a' to be matched when it has
2084: // any ancestor that is 'x'.
2085: nextToken();
2086:
2087: relativePathStatus = RELATIVE_PATH_REQUIRED;
2088: } else {
2089: appendOp(4, OpCodes.FROM_ROOT);
2090:
2091: relativePathStatus = RELATIVE_PATH_PERMITTED;
2092: }
2093:
2094: // Tell how long the step is without the predicate
2095: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
2096: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1,
2097: OpCodes.NODETYPE_ROOT);
2098:
2099: nextToken();
2100: } else {
2101: relativePathStatus = RELATIVE_PATH_REQUIRED;
2102: }
2103:
2104: if (relativePathStatus != RELATIVE_PATH_NOT_PERMITTED) {
2105: if (!tokenIs('|') && (null != m_token)) {
2106: RelativePathPattern();
2107: } else if (relativePathStatus == RELATIVE_PATH_REQUIRED) {
2108: // "A relative path pattern was expected."
2109: error(XPATHErrorResources.ER_EXPECTED_REL_PATH_PATTERN,
2110: null);
2111: }
2112: }
2113:
2114: // Terminate for safety.
2115: m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
2116: m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops
2117: .getOp(OpMap.MAPINDEX_LENGTH) + 1);
2118: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, m_ops
2119: .getOp(OpMap.MAPINDEX_LENGTH)
2120: - opPos);
2121: }
2122:
2123: /**
2124: *
2125: * IdKeyPattern ::= 'id' '(' Literal ')'
2126: * | 'key' '(' Literal ',' Literal ')'
2127: * (Also handle doc())
2128: *
2129: *
2130: * @throws javax.xml.transform.TransformerException
2131: */
2132: protected void IdKeyPattern()
2133: throws javax.xml.transform.TransformerException {
2134: FunctionCall();
2135: }
2136:
2137: /**
2138: *
2139: * RelativePathPattern ::= StepPattern
2140: * | RelativePathPattern '/' StepPattern
2141: * | RelativePathPattern '//' StepPattern
2142: *
2143: * @throws javax.xml.transform.TransformerException
2144: */
2145: protected void RelativePathPattern()
2146: throws javax.xml.transform.TransformerException {
2147:
2148: // Caller will have consumed any '/' or '//' preceding the
2149: // RelativePathPattern, so let StepPattern know it can't begin with a '/'
2150: boolean trailingSlashConsumed = StepPattern(false);
2151:
2152: while (tokenIs('/')) {
2153: nextToken();
2154:
2155: // StepPattern() may consume first slash of pair in "a//b" while
2156: // processing StepPattern "a". On next iteration, let StepPattern know
2157: // that happened, so it doesn't match ill-formed patterns like "a///b".
2158: trailingSlashConsumed = StepPattern(!trailingSlashConsumed);
2159: }
2160: }
2161:
2162: /**
2163: *
2164: * StepPattern ::= AbbreviatedNodeTestStep
2165: *
2166: * @param isLeadingSlashPermitted a boolean indicating whether a slash can
2167: * appear at the start of this step
2168: *
2169: * @return boolean indicating whether a slash following the step was consumed
2170: *
2171: * @throws javax.xml.transform.TransformerException
2172: */
2173: protected boolean StepPattern(boolean isLeadingSlashPermitted)
2174: throws javax.xml.transform.TransformerException {
2175: return AbbreviatedNodeTestStep(isLeadingSlashPermitted);
2176: }
2177:
2178: /**
2179: *
2180: * AbbreviatedNodeTestStep ::= '@'? NodeTest Predicate
2181: *
2182: * @param isLeadingSlashPermitted a boolean indicating whether a slash can
2183: * appear at the start of this step
2184: *
2185: * @return boolean indicating whether a slash following the step was consumed
2186: *
2187: * @throws javax.xml.transform.TransformerException
2188: */
2189: protected boolean AbbreviatedNodeTestStep(
2190: boolean isLeadingSlashPermitted)
2191: throws javax.xml.transform.TransformerException {
2192:
2193: int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2194: int axesType;
2195:
2196: // The next blocks guarantee that a MATCH_XXX will be added.
2197: int matchTypePos = -1;
2198:
2199: if (tokenIs('@')) {
2200: axesType = OpCodes.MATCH_ATTRIBUTE;
2201:
2202: appendOp(2, axesType);
2203: nextToken();
2204: } else if (this .lookahead("::", 1)) {
2205: if (tokenIs("attribute")) {
2206: axesType = OpCodes.MATCH_ATTRIBUTE;
2207:
2208: appendOp(2, axesType);
2209: } else if (tokenIs("child")) {
2210: matchTypePos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2211: axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR;
2212:
2213: appendOp(2, axesType);
2214: } else {
2215: axesType = -1;
2216:
2217: this .error(XPATHErrorResources.ER_AXES_NOT_ALLOWED,
2218: new Object[] { this .m_token });
2219: }
2220:
2221: nextToken();
2222: nextToken();
2223: } else if (tokenIs('/')) {
2224: if (!isLeadingSlashPermitted) {
2225: // "A step was expected in the pattern, but '/' was encountered."
2226: error(XPATHErrorResources.ER_EXPECTED_STEP_PATTERN,
2227: null);
2228: }
2229: axesType = OpCodes.MATCH_ANY_ANCESTOR;
2230:
2231: appendOp(2, axesType);
2232: nextToken();
2233: } else {
2234: matchTypePos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2235: axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR;
2236:
2237: appendOp(2, axesType);
2238: }
2239:
2240: // Make room for telling how long the step is without the predicate
2241: m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops
2242: .getOp(OpMap.MAPINDEX_LENGTH) + 1);
2243:
2244: NodeTest(axesType);
2245:
2246: // Tell how long the step is without the predicate
2247: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, m_ops
2248: .getOp(OpMap.MAPINDEX_LENGTH)
2249: - opPos);
2250:
2251: while (tokenIs('[')) {
2252: Predicate();
2253: }
2254:
2255: boolean trailingSlashConsumed;
2256:
2257: // For "a//b", where "a" is current step, we need to mark operation of
2258: // current step as "MATCH_ANY_ANCESTOR". Then we'll consume the first
2259: // slash and subsequent step will be treated as a MATCH_IMMEDIATE_ANCESTOR
2260: // (unless it too is followed by '//'.)
2261: //
2262: // %REVIEW% Following is what happens today, but I'm not sure that's
2263: // %REVIEW% correct behaviour. Perhaps no valid case could be constructed
2264: // %REVIEW% where it would matter?
2265: //
2266: // If current step is on the attribute axis (e.g., "@x//b"), we won't
2267: // change the current step, and let following step be marked as
2268: // MATCH_ANY_ANCESTOR on next call instead.
2269: if ((matchTypePos > -1) && tokenIs('/') && lookahead('/', 1)) {
2270: m_ops.setOp(matchTypePos, OpCodes.MATCH_ANY_ANCESTOR);
2271:
2272: nextToken();
2273:
2274: trailingSlashConsumed = true;
2275: } else {
2276: trailingSlashConsumed = false;
2277: }
2278:
2279: // Tell how long the entire step is.
2280: m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, m_ops
2281: .getOp(OpMap.MAPINDEX_LENGTH)
2282: - opPos);
2283:
2284: return trailingSlashConsumed;
2285: }
2286: }
|