--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jaxp/src/com/sun/org/apache/xpath/internal/compiler/XPathParser.java Thu Apr 12 08:38:26 2012 -0700
@@ -0,0 +1,2403 @@
+/*
+ * reserved comment block
+ * DO NOT REMOVE OR ALTER!
+ */
+/*
+ * Copyright 1999-2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * $Id: XPathParser.java,v 1.2.4.1 2005/09/14 19:46:02 jeffsuttor Exp $
+ */
+package com.sun.org.apache.xpath.internal.compiler;
+
+import javax.xml.transform.ErrorListener;
+import javax.xml.transform.TransformerException;
+
+import com.sun.org.apache.xalan.internal.res.XSLMessages;
+import com.sun.org.apache.xml.internal.utils.PrefixResolver;
+import com.sun.org.apache.xpath.internal.XPathProcessorException;
+import com.sun.org.apache.xpath.internal.domapi.XPathStylesheetDOM3Exception;
+import com.sun.org.apache.xpath.internal.objects.XNumber;
+import com.sun.org.apache.xpath.internal.objects.XString;
+import com.sun.org.apache.xpath.internal.res.XPATHErrorResources;
+
+/**
+ * Tokenizes and parses XPath expressions. This should really be named
+ * XPathParserImpl, and may be renamed in the future.
+ * @xsl.usage general
+ */
+public class XPathParser
+{
+ // %REVIEW% Is there a better way of doing this?
+ // Upside is minimum object churn. Downside is that we don't have a useful
+ // backtrace in the exception itself -- but we don't expect to need one.
+ static public final String CONTINUE_AFTER_FATAL_ERROR="CONTINUE_AFTER_FATAL_ERROR";
+
+ /**
+ * The XPath to be processed.
+ */
+ private OpMap m_ops;
+
+ /**
+ * The next token in the pattern.
+ */
+ transient String m_token;
+
+ /**
+ * The first char in m_token, the theory being that this
+ * is an optimization because we won't have to do charAt(0) as
+ * often.
+ */
+ transient char m_tokenChar = 0;
+
+ /**
+ * The position in the token queue is tracked by m_queueMark.
+ */
+ int m_queueMark = 0;
+
+ /**
+ * Results from checking FilterExpr syntax
+ */
+ protected final static int FILTER_MATCH_FAILED = 0;
+ protected final static int FILTER_MATCH_PRIMARY = 1;
+ protected final static int FILTER_MATCH_PREDICATES = 2;
+
+ /**
+ * The parser constructor.
+ */
+ public XPathParser(ErrorListener errorListener, javax.xml.transform.SourceLocator sourceLocator)
+ {
+ m_errorListener = errorListener;
+ m_sourceLocator = sourceLocator;
+ }
+
+ /**
+ * The prefix resolver to map prefixes to namespaces in the OpMap.
+ */
+ PrefixResolver m_namespaceContext;
+
+ /**
+ * Given an string, init an XPath object for selections,
+ * in order that a parse doesn't
+ * have to be done each time the expression is evaluated.
+ *
+ * @param compiler The compiler object.
+ * @param expression A string conforming to the XPath grammar.
+ * @param namespaceContext An object that is able to resolve prefixes in
+ * the XPath to namespaces.
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ public void initXPath(
+ Compiler compiler, String expression, PrefixResolver namespaceContext)
+ throws javax.xml.transform.TransformerException
+ {
+
+ m_ops = compiler;
+ m_namespaceContext = namespaceContext;
+ m_functionTable = compiler.getFunctionTable();
+
+ Lexer lexer = new Lexer(compiler, namespaceContext, this);
+
+ lexer.tokenize(expression);
+
+ m_ops.setOp(0,OpCodes.OP_XPATH);
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH,2);
+
+
+ // Patch for Christine's gripe. She wants her errorHandler to return from
+ // a fatal error and continue trying to parse, rather than throwing an exception.
+ // Without the patch, that put us into an endless loop.
+ //
+ // %REVIEW% Is there a better way of doing this?
+ // %REVIEW% Are there any other cases which need the safety net?
+ // (and if so do we care right now, or should we rewrite the XPath
+ // grammar engine and can fix it at that time?)
+ try {
+
+ nextToken();
+ Expr();
+
+ if (null != m_token)
+ {
+ String extraTokens = "";
+
+ while (null != m_token)
+ {
+ extraTokens += "'" + m_token + "'";
+
+ nextToken();
+
+ if (null != m_token)
+ extraTokens += ", ";
+ }
+
+ error(XPATHErrorResources.ER_EXTRA_ILLEGAL_TOKENS,
+ new Object[]{ extraTokens }); //"Extra illegal tokens: "+extraTokens);
+ }
+
+ }
+ catch (com.sun.org.apache.xpath.internal.XPathProcessorException e)
+ {
+ if(CONTINUE_AFTER_FATAL_ERROR.equals(e.getMessage()))
+ {
+ // What I _want_ to do is null out this XPath.
+ // I doubt this has the desired effect, but I'm not sure what else to do.
+ // %REVIEW%!!!
+ initXPath(compiler, "/..", namespaceContext);
+ }
+ else
+ throw e;
+ }
+
+ compiler.shrink();
+ }
+
+ /**
+ * Given an string, init an XPath object for pattern matches,
+ * in order that a parse doesn't
+ * have to be done each time the expression is evaluated.
+ * @param compiler The XPath object to be initialized.
+ * @param expression A String representing the XPath.
+ * @param namespaceContext An object that is able to resolve prefixes in
+ * the XPath to namespaces.
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ public void initMatchPattern(
+ Compiler compiler, String expression, PrefixResolver namespaceContext)
+ throws javax.xml.transform.TransformerException
+ {
+
+ m_ops = compiler;
+ m_namespaceContext = namespaceContext;
+ m_functionTable = compiler.getFunctionTable();
+
+ Lexer lexer = new Lexer(compiler, namespaceContext, this);
+
+ lexer.tokenize(expression);
+
+ m_ops.setOp(0, OpCodes.OP_MATCHPATTERN);
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH, 2);
+
+ nextToken();
+ Pattern();
+
+ if (null != m_token)
+ {
+ String extraTokens = "";
+
+ while (null != m_token)
+ {
+ extraTokens += "'" + m_token + "'";
+
+ nextToken();
+
+ if (null != m_token)
+ extraTokens += ", ";
+ }
+
+ error(XPATHErrorResources.ER_EXTRA_ILLEGAL_TOKENS,
+ new Object[]{ extraTokens }); //"Extra illegal tokens: "+extraTokens);
+ }
+
+ // Terminate for safety.
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH)+1);
+
+ m_ops.shrink();
+ }
+
+ /** The error listener where syntax errors are to be sent.
+ */
+ private ErrorListener m_errorListener;
+
+ /** The source location of the XPath. */
+ javax.xml.transform.SourceLocator m_sourceLocator;
+
+ /** The table contains build-in functions and customized functions */
+ private FunctionTable m_functionTable;
+
+ /**
+ * Allow an application to register an error event handler, where syntax
+ * errors will be sent. If the error listener is not set, syntax errors
+ * will be sent to System.err.
+ *
+ * @param handler Reference to error listener where syntax errors will be
+ * sent.
+ */
+ public void setErrorHandler(ErrorListener handler)
+ {
+ m_errorListener = handler;
+ }
+
+ /**
+ * Return the current error listener.
+ *
+ * @return The error listener, which should not normally be null, but may be.
+ */
+ public ErrorListener getErrorListener()
+ {
+ return m_errorListener;
+ }
+
+ /**
+ * Check whether m_token matches the target string.
+ *
+ * @param s A string reference or null.
+ *
+ * @return If m_token is null, returns false (or true if s is also null), or
+ * return true if the current token matches the string, else false.
+ */
+ final boolean tokenIs(String s)
+ {
+ return (m_token != null) ? (m_token.equals(s)) : (s == null);
+ }
+
+ /**
+ * Check whether m_tokenChar==c.
+ *
+ * @param c A character to be tested.
+ *
+ * @return If m_token is null, returns false, or return true if c matches
+ * the current token.
+ */
+ final boolean tokenIs(char c)
+ {
+ return (m_token != null) ? (m_tokenChar == c) : false;
+ }
+
+ /**
+ * Look ahead of the current token in order to
+ * make a branching decision.
+ *
+ * @param c the character to be tested for.
+ * @param n number of tokens to look ahead. Must be
+ * greater than 1.
+ *
+ * @return true if the next token matches the character argument.
+ */
+ final boolean lookahead(char c, int n)
+ {
+
+ int pos = (m_queueMark + n);
+ boolean b;
+
+ if ((pos <= m_ops.getTokenQueueSize()) && (pos > 0)
+ && (m_ops.getTokenQueueSize() != 0))
+ {
+ String tok = ((String) m_ops.m_tokenQueue.elementAt(pos - 1));
+
+ b = (tok.length() == 1) ? (tok.charAt(0) == c) : false;
+ }
+ else
+ {
+ b = false;
+ }
+
+ return b;
+ }
+
+ /**
+ * Look behind the first character of the current token in order to
+ * make a branching decision.
+ *
+ * @param c the character to compare it to.
+ * @param n number of tokens to look behind. Must be
+ * greater than 1. Note that the look behind terminates
+ * at either the beginning of the string or on a '|'
+ * character. Because of this, this method should only
+ * be used for pattern matching.
+ *
+ * @return true if the token behind the current token matches the character
+ * argument.
+ */
+ private final boolean lookbehind(char c, int n)
+ {
+
+ boolean isToken;
+ int lookBehindPos = m_queueMark - (n + 1);
+
+ if (lookBehindPos >= 0)
+ {
+ String lookbehind = (String) m_ops.m_tokenQueue.elementAt(lookBehindPos);
+
+ if (lookbehind.length() == 1)
+ {
+ char c0 = (lookbehind == null) ? '|' : lookbehind.charAt(0);
+
+ isToken = (c0 == '|') ? false : (c0 == c);
+ }
+ else
+ {
+ isToken = false;
+ }
+ }
+ else
+ {
+ isToken = false;
+ }
+
+ return isToken;
+ }
+
+ /**
+ * look behind the current token in order to
+ * see if there is a useable token.
+ *
+ * @param n number of tokens to look behind. Must be
+ * greater than 1. Note that the look behind terminates
+ * at either the beginning of the string or on a '|'
+ * character. Because of this, this method should only
+ * be used for pattern matching.
+ *
+ * @return true if look behind has a token, false otherwise.
+ */
+ private final boolean lookbehindHasToken(int n)
+ {
+
+ boolean hasToken;
+
+ if ((m_queueMark - n) > 0)
+ {
+ String lookbehind = (String) m_ops.m_tokenQueue.elementAt(m_queueMark - (n - 1));
+ char c0 = (lookbehind == null) ? '|' : lookbehind.charAt(0);
+
+ hasToken = (c0 == '|') ? false : true;
+ }
+ else
+ {
+ hasToken = false;
+ }
+
+ return hasToken;
+ }
+
+ /**
+ * Look ahead of the current token in order to
+ * make a branching decision.
+ *
+ * @param s the string to compare it to.
+ * @param n number of tokens to lookahead. Must be
+ * greater than 1.
+ *
+ * @return true if the token behind the current token matches the string
+ * argument.
+ */
+ private final boolean lookahead(String s, int n)
+ {
+
+ boolean isToken;
+
+ if ((m_queueMark + n) <= m_ops.getTokenQueueSize())
+ {
+ String lookahead = (String) m_ops.m_tokenQueue.elementAt(m_queueMark + (n - 1));
+
+ isToken = (lookahead != null) ? lookahead.equals(s) : (s == null);
+ }
+ else
+ {
+ isToken = (null == s);
+ }
+
+ return isToken;
+ }
+
+ /**
+ * Retrieve the next token from the command and
+ * store it in m_token string.
+ */
+ private final void nextToken()
+ {
+
+ if (m_queueMark < m_ops.getTokenQueueSize())
+ {
+ m_token = (String) m_ops.m_tokenQueue.elementAt(m_queueMark++);
+ m_tokenChar = m_token.charAt(0);
+ }
+ else
+ {
+ m_token = null;
+ m_tokenChar = 0;
+ }
+ }
+
+ /**
+ * Retrieve a token relative to the current token.
+ *
+ * @param i Position relative to current token.
+ *
+ * @return The string at the given index, or null if the index is out
+ * of range.
+ */
+ private final String getTokenRelative(int i)
+ {
+
+ String tok;
+ int relative = m_queueMark + i;
+
+ if ((relative > 0) && (relative < m_ops.getTokenQueueSize()))
+ {
+ tok = (String) m_ops.m_tokenQueue.elementAt(relative);
+ }
+ else
+ {
+ tok = null;
+ }
+
+ return tok;
+ }
+
+ /**
+ * Retrieve the previous token from the command and
+ * store it in m_token string.
+ */
+ private final void prevToken()
+ {
+
+ if (m_queueMark > 0)
+ {
+ m_queueMark--;
+
+ m_token = (String) m_ops.m_tokenQueue.elementAt(m_queueMark);
+ m_tokenChar = m_token.charAt(0);
+ }
+ else
+ {
+ m_token = null;
+ m_tokenChar = 0;
+ }
+ }
+
+ /**
+ * Consume an expected token, throwing an exception if it
+ * isn't there.
+ *
+ * @param expected The string to be expected.
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ private final void consumeExpected(String expected)
+ throws javax.xml.transform.TransformerException
+ {
+
+ if (tokenIs(expected))
+ {
+ nextToken();
+ }
+ else
+ {
+ error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND, new Object[]{ expected,
+ m_token }); //"Expected "+expected+", but found: "+m_token);
+
+ // Patch for Christina's gripe. She wants her errorHandler to return from
+ // this error and continue trying to parse, rather than throwing an exception.
+ // Without the patch, that put us into an endless loop.
+ throw new XPathProcessorException(CONTINUE_AFTER_FATAL_ERROR);
+ }
+ }
+
+ /**
+ * Consume an expected token, throwing an exception if it
+ * isn't there.
+ *
+ * @param expected the character to be expected.
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ private final void consumeExpected(char expected)
+ throws javax.xml.transform.TransformerException
+ {
+
+ if (tokenIs(expected))
+ {
+ nextToken();
+ }
+ else
+ {
+ error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND,
+ new Object[]{ String.valueOf(expected),
+ m_token }); //"Expected "+expected+", but found: "+m_token);
+
+ // Patch for Christina's gripe. She wants her errorHandler to return from
+ // this error and continue trying to parse, rather than throwing an exception.
+ // Without the patch, that put us into an endless loop.
+ throw new XPathProcessorException(CONTINUE_AFTER_FATAL_ERROR);
+ }
+ }
+
+ /**
+ * Warn the user of a problem.
+ *
+ * @param msg An error msgkey that corresponds to one of the constants found
+ * in {@link com.sun.org.apache.xpath.internal.res.XPATHErrorResources}, which is
+ * a key for a format string.
+ * @param args An array of arguments represented in the format string, which
+ * may be null.
+ *
+ * @throws TransformerException if the current ErrorListoner determines to
+ * throw an exception.
+ */
+ void warn(String msg, Object[] args) throws TransformerException
+ {
+
+ String fmsg = XSLMessages.createXPATHWarning(msg, args);
+ ErrorListener ehandler = this.getErrorListener();
+
+ if (null != ehandler)
+ {
+ // TO DO: Need to get stylesheet Locator from here.
+ ehandler.warning(new TransformerException(fmsg, m_sourceLocator));
+ }
+ else
+ {
+ // Should never happen.
+ System.err.println(fmsg);
+ }
+ }
+
+ /**
+ * Notify the user of an assertion error, and probably throw an
+ * exception.
+ *
+ * @param b If false, a runtime exception will be thrown.
+ * @param msg The assertion message, which should be informative.
+ *
+ * @throws RuntimeException if the b argument is false.
+ */
+ private void assertion(boolean b, String msg)
+ {
+
+ if (!b)
+ {
+ String fMsg = XSLMessages.createXPATHMessage(
+ XPATHErrorResources.ER_INCORRECT_PROGRAMMER_ASSERTION,
+ new Object[]{ msg });
+
+ throw new RuntimeException(fMsg);
+ }
+ }
+
+ /**
+ * Notify the user of an error, and probably throw an
+ * exception.
+ *
+ * @param msg An error msgkey that corresponds to one of the constants found
+ * in {@link com.sun.org.apache.xpath.internal.res.XPATHErrorResources}, which is
+ * a key for a format string.
+ * @param args An array of arguments represented in the format string, which
+ * may be null.
+ *
+ * @throws TransformerException if the current ErrorListoner determines to
+ * throw an exception.
+ */
+ void error(String msg, Object[] args) throws TransformerException
+ {
+
+ String fmsg = XSLMessages.createXPATHMessage(msg, args);
+ ErrorListener ehandler = this.getErrorListener();
+
+ TransformerException te = new TransformerException(fmsg, m_sourceLocator);
+ if (null != ehandler)
+ {
+ // TO DO: Need to get stylesheet Locator from here.
+ ehandler.fatalError(te);
+ }
+ else
+ {
+ // System.err.println(fmsg);
+ throw te;
+ }
+ }
+
+ /**
+ * This method is added to support DOM 3 XPath API.
+ * <p>
+ * This method is exactly like error(String, Object[]); except that
+ * the underlying TransformerException is
+ * XpathStylesheetDOM3Exception (which extends TransformerException).
+ * <p>
+ * So older XPath code in Xalan is not affected by this. To older XPath code
+ * the behavior of whether error() or errorForDOM3() is called because it is
+ * always catching TransformerException objects and is oblivious to
+ * the new subclass of XPathStylesheetDOM3Exception. Older XPath code
+ * runs as before.
+ * <p>
+ * However, newer DOM3 XPath code upon catching a TransformerException can
+ * can check if the exception is an instance of XPathStylesheetDOM3Exception
+ * and take appropriate action.
+ *
+ * @param msg An error msgkey that corresponds to one of the constants found
+ * in {@link com.sun.org.apache.xpath.internal.res.XPATHErrorResources}, which is
+ * a key for a format string.
+ * @param args An array of arguments represented in the format string, which
+ * may be null.
+ *
+ * @throws TransformerException if the current ErrorListoner determines to
+ * throw an exception.
+ */
+ void errorForDOM3(String msg, Object[] args) throws TransformerException
+ {
+
+ String fmsg = XSLMessages.createXPATHMessage(msg, args);
+ ErrorListener ehandler = this.getErrorListener();
+
+ TransformerException te = new XPathStylesheetDOM3Exception(fmsg, m_sourceLocator);
+ if (null != ehandler)
+ {
+ // TO DO: Need to get stylesheet Locator from here.
+ ehandler.fatalError(te);
+ }
+ else
+ {
+ // System.err.println(fmsg);
+ throw te;
+ }
+ }
+ /**
+ * Dump the remaining token queue.
+ * Thanks to Craig for this.
+ *
+ * @return A dump of the remaining token queue, which may be appended to
+ * an error message.
+ */
+ protected String dumpRemainingTokenQueue()
+ {
+
+ int q = m_queueMark;
+ String returnMsg;
+
+ if (q < m_ops.getTokenQueueSize())
+ {
+ String msg = "\n Remaining tokens: (";
+
+ while (q < m_ops.getTokenQueueSize())
+ {
+ String t = (String) m_ops.m_tokenQueue.elementAt(q++);
+
+ msg += (" '" + t + "'");
+ }
+
+ returnMsg = msg + ")";
+ }
+ else
+ {
+ returnMsg = "";
+ }
+
+ return returnMsg;
+ }
+
+ /**
+ * Given a string, return the corresponding function token.
+ *
+ * @param key A local name of a function.
+ *
+ * @return The function ID, which may correspond to one of the FUNC_XXX
+ * values found in {@link com.sun.org.apache.xpath.internal.compiler.FunctionTable}, but may
+ * be a value installed by an external module.
+ */
+ final int getFunctionToken(String key)
+ {
+
+ int tok;
+
+ Object id;
+
+ try
+ {
+ // These are nodetests, xpathparser treats them as functions when parsing
+ // a FilterExpr.
+ id = Keywords.lookupNodeTest(key);
+ if (null == id) id = m_functionTable.getFunctionID(key);
+ tok = ((Integer) id).intValue();
+ }
+ catch (NullPointerException npe)
+ {
+ tok = -1;
+ }
+ catch (ClassCastException cce)
+ {
+ tok = -1;
+ }
+
+ return tok;
+ }
+
+ /**
+ * Insert room for operation. This will NOT set
+ * the length value of the operation, but will update
+ * the length value for the total expression.
+ *
+ * @param pos The position where the op is to be inserted.
+ * @param length The length of the operation space in the op map.
+ * @param op The op code to the inserted.
+ */
+ void insertOp(int pos, int length, int op)
+ {
+
+ int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+ for (int i = totalLen - 1; i >= pos; i--)
+ {
+ m_ops.setOp(i + length, m_ops.getOp(i));
+ }
+
+ m_ops.setOp(pos,op);
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH,totalLen + length);
+ }
+
+ /**
+ * Insert room for operation. This WILL set
+ * the length value of the operation, and will update
+ * the length value for the total expression.
+ *
+ * @param length The length of the operation.
+ * @param op The op code to the inserted.
+ */
+ void appendOp(int length, int op)
+ {
+
+ int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+ m_ops.setOp(totalLen, op);
+ m_ops.setOp(totalLen + OpMap.MAPINDEX_LENGTH, length);
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH, totalLen + length);
+ }
+
+ // ============= EXPRESSIONS FUNCTIONS =================
+
+ /**
+ *
+ *
+ * Expr ::= OrExpr
+ *
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void Expr() throws javax.xml.transform.TransformerException
+ {
+ OrExpr();
+ }
+
+ /**
+ *
+ *
+ * OrExpr ::= AndExpr
+ * | OrExpr 'or' AndExpr
+ *
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void OrExpr() throws javax.xml.transform.TransformerException
+ {
+
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+ AndExpr();
+
+ if ((null != m_token) && tokenIs("or"))
+ {
+ nextToken();
+ insertOp(opPos, 2, OpCodes.OP_OR);
+ OrExpr();
+
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+ }
+ }
+
+ /**
+ *
+ *
+ * AndExpr ::= EqualityExpr
+ * | AndExpr 'and' EqualityExpr
+ *
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void AndExpr() throws javax.xml.transform.TransformerException
+ {
+
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+ EqualityExpr(-1);
+
+ if ((null != m_token) && tokenIs("and"))
+ {
+ nextToken();
+ insertOp(opPos, 2, OpCodes.OP_AND);
+ AndExpr();
+
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+ }
+ }
+
+ /**
+ *
+ * @returns an Object which is either a String, a Number, a Boolean, or a vector
+ * of nodes.
+ *
+ * EqualityExpr ::= RelationalExpr
+ * | EqualityExpr '=' RelationalExpr
+ *
+ *
+ * @param addPos Position where expression is to be added, or -1 for append.
+ *
+ * @return the position at the end of the equality expression.
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected int EqualityExpr(int addPos) throws javax.xml.transform.TransformerException
+ {
+
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+ if (-1 == addPos)
+ addPos = opPos;
+
+ RelationalExpr(-1);
+
+ if (null != m_token)
+ {
+ if (tokenIs('!') && lookahead('=', 1))
+ {
+ nextToken();
+ nextToken();
+ insertOp(addPos, 2, OpCodes.OP_NOTEQUALS);
+
+ int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
+
+ addPos = EqualityExpr(addPos);
+ m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
+ addPos += 2;
+ }
+ else if (tokenIs('='))
+ {
+ nextToken();
+ insertOp(addPos, 2, OpCodes.OP_EQUALS);
+
+ int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
+
+ addPos = EqualityExpr(addPos);
+ m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
+ addPos += 2;
+ }
+ }
+
+ return addPos;
+ }
+
+ /**
+ * .
+ * @returns an Object which is either a String, a Number, a Boolean, or a vector
+ * of nodes.
+ *
+ * RelationalExpr ::= AdditiveExpr
+ * | RelationalExpr '<' AdditiveExpr
+ * | RelationalExpr '>' AdditiveExpr
+ * | RelationalExpr '<=' AdditiveExpr
+ * | RelationalExpr '>=' AdditiveExpr
+ *
+ *
+ * @param addPos Position where expression is to be added, or -1 for append.
+ *
+ * @return the position at the end of the relational expression.
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected int RelationalExpr(int addPos) throws javax.xml.transform.TransformerException
+ {
+
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+ if (-1 == addPos)
+ addPos = opPos;
+
+ AdditiveExpr(-1);
+
+ if (null != m_token)
+ {
+ if (tokenIs('<'))
+ {
+ nextToken();
+
+ if (tokenIs('='))
+ {
+ nextToken();
+ insertOp(addPos, 2, OpCodes.OP_LTE);
+ }
+ else
+ {
+ insertOp(addPos, 2, OpCodes.OP_LT);
+ }
+
+ int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
+
+ addPos = RelationalExpr(addPos);
+ m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
+ addPos += 2;
+ }
+ else if (tokenIs('>'))
+ {
+ nextToken();
+
+ if (tokenIs('='))
+ {
+ nextToken();
+ insertOp(addPos, 2, OpCodes.OP_GTE);
+ }
+ else
+ {
+ insertOp(addPos, 2, OpCodes.OP_GT);
+ }
+
+ int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
+
+ addPos = RelationalExpr(addPos);
+ m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
+ addPos += 2;
+ }
+ }
+
+ return addPos;
+ }
+
+ /**
+ * This has to handle construction of the operations so that they are evaluated
+ * in pre-fix order. So, for 9+7-6, instead of |+|9|-|7|6|, this needs to be
+ * evaluated as |-|+|9|7|6|.
+ *
+ * AdditiveExpr ::= MultiplicativeExpr
+ * | AdditiveExpr '+' MultiplicativeExpr
+ * | AdditiveExpr '-' MultiplicativeExpr
+ *
+ *
+ * @param addPos Position where expression is to be added, or -1 for append.
+ *
+ * @return the position at the end of the equality expression.
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected int AdditiveExpr(int addPos) throws javax.xml.transform.TransformerException
+ {
+
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+ if (-1 == addPos)
+ addPos = opPos;
+
+ MultiplicativeExpr(-1);
+
+ if (null != m_token)
+ {
+ if (tokenIs('+'))
+ {
+ nextToken();
+ insertOp(addPos, 2, OpCodes.OP_PLUS);
+
+ int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
+
+ addPos = AdditiveExpr(addPos);
+ m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
+ addPos += 2;
+ }
+ else if (tokenIs('-'))
+ {
+ nextToken();
+ insertOp(addPos, 2, OpCodes.OP_MINUS);
+
+ int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
+
+ addPos = AdditiveExpr(addPos);
+ m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
+ addPos += 2;
+ }
+ }
+
+ return addPos;
+ }
+
+ /**
+ * This has to handle construction of the operations so that they are evaluated
+ * in pre-fix order. So, for 9+7-6, instead of |+|9|-|7|6|, this needs to be
+ * evaluated as |-|+|9|7|6|.
+ *
+ * MultiplicativeExpr ::= UnaryExpr
+ * | MultiplicativeExpr MultiplyOperator UnaryExpr
+ * | MultiplicativeExpr 'div' UnaryExpr
+ * | MultiplicativeExpr 'mod' UnaryExpr
+ * | MultiplicativeExpr 'quo' UnaryExpr
+ *
+ * @param addPos Position where expression is to be added, or -1 for append.
+ *
+ * @return the position at the end of the equality expression.
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected int MultiplicativeExpr(int addPos) throws javax.xml.transform.TransformerException
+ {
+
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+ if (-1 == addPos)
+ addPos = opPos;
+
+ UnaryExpr();
+
+ if (null != m_token)
+ {
+ if (tokenIs('*'))
+ {
+ nextToken();
+ insertOp(addPos, 2, OpCodes.OP_MULT);
+
+ int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
+
+ addPos = MultiplicativeExpr(addPos);
+ m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
+ addPos += 2;
+ }
+ else if (tokenIs("div"))
+ {
+ nextToken();
+ insertOp(addPos, 2, OpCodes.OP_DIV);
+
+ int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
+
+ addPos = MultiplicativeExpr(addPos);
+ m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
+ addPos += 2;
+ }
+ else if (tokenIs("mod"))
+ {
+ nextToken();
+ insertOp(addPos, 2, OpCodes.OP_MOD);
+
+ int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
+
+ addPos = MultiplicativeExpr(addPos);
+ m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
+ addPos += 2;
+ }
+ else if (tokenIs("quo"))
+ {
+ nextToken();
+ insertOp(addPos, 2, OpCodes.OP_QUO);
+
+ int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
+
+ addPos = MultiplicativeExpr(addPos);
+ m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
+ addPos += 2;
+ }
+ }
+
+ return addPos;
+ }
+
+ /**
+ *
+ * UnaryExpr ::= UnionExpr
+ * | '-' UnaryExpr
+ *
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void UnaryExpr() throws javax.xml.transform.TransformerException
+ {
+
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+ boolean isNeg = false;
+
+ if (m_tokenChar == '-')
+ {
+ nextToken();
+ appendOp(2, OpCodes.OP_NEG);
+
+ isNeg = true;
+ }
+
+ UnionExpr();
+
+ if (isNeg)
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+ }
+
+ /**
+ *
+ * StringExpr ::= Expr
+ *
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void StringExpr() throws javax.xml.transform.TransformerException
+ {
+
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+ appendOp(2, OpCodes.OP_STRING);
+ Expr();
+
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+ }
+
+ /**
+ *
+ *
+ * StringExpr ::= Expr
+ *
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void BooleanExpr() throws javax.xml.transform.TransformerException
+ {
+
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+ appendOp(2, OpCodes.OP_BOOL);
+ Expr();
+
+ int opLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos;
+
+ if (opLen == 2)
+ {
+ error(XPATHErrorResources.ER_BOOLEAN_ARG_NO_LONGER_OPTIONAL, null); //"boolean(...) argument is no longer optional with 19990709 XPath draft.");
+ }
+
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, opLen);
+ }
+
+ /**
+ *
+ *
+ * NumberExpr ::= Expr
+ *
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void NumberExpr() throws javax.xml.transform.TransformerException
+ {
+
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+ appendOp(2, OpCodes.OP_NUMBER);
+ Expr();
+
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+ }
+
+ /**
+ * The context of the right hand side expressions is the context of the
+ * left hand side expression. The results of the right hand side expressions
+ * are node sets. The result of the left hand side UnionExpr is the union
+ * of the results of the right hand side expressions.
+ *
+ *
+ * UnionExpr ::= PathExpr
+ * | UnionExpr '|' PathExpr
+ *
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void UnionExpr() throws javax.xml.transform.TransformerException
+ {
+
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+ boolean continueOrLoop = true;
+ boolean foundUnion = false;
+
+ do
+ {
+ PathExpr();
+
+ if (tokenIs('|'))
+ {
+ if (false == foundUnion)
+ {
+ foundUnion = true;
+
+ insertOp(opPos, 2, OpCodes.OP_UNION);
+ }
+
+ nextToken();
+ }
+ else
+ {
+ break;
+ }
+
+ // this.m_testForDocOrder = true;
+ }
+ while (continueOrLoop);
+
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+ }
+
+ /**
+ * PathExpr ::= LocationPath
+ * | FilterExpr
+ * | FilterExpr '/' RelativeLocationPath
+ * | FilterExpr '//' RelativeLocationPath
+ *
+ * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
+ * the error condition is severe enough to halt processing.
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void PathExpr() throws javax.xml.transform.TransformerException
+ {
+
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+ int filterExprMatch = FilterExpr();
+
+ if (filterExprMatch != FILTER_MATCH_FAILED)
+ {
+ // If FilterExpr had Predicates, a OP_LOCATIONPATH opcode would already
+ // have been inserted.
+ boolean locationPathStarted = (filterExprMatch==FILTER_MATCH_PREDICATES);
+
+ if (tokenIs('/'))
+ {
+ nextToken();
+
+ if (!locationPathStarted)
+ {
+ // int locationPathOpPos = opPos;
+ insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH);
+
+ locationPathStarted = true;
+ }
+
+ if (!RelativeLocationPath())
+ {
+ // "Relative location path expected following '/' or '//'"
+ error(XPATHErrorResources.ER_EXPECTED_REL_LOC_PATH, null);
+ }
+
+ }
+
+ // Terminate for safety.
+ if (locationPathStarted)
+ {
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+ }
+ }
+ else
+ {
+ LocationPath();
+ }
+ }
+
+ /**
+ *
+ *
+ * FilterExpr ::= PrimaryExpr
+ * | FilterExpr Predicate
+ *
+ * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
+ * the error condition is severe enough to halt processing.
+ *
+ * @return FILTER_MATCH_PREDICATES, if this method successfully matched a
+ * FilterExpr with one or more Predicates;
+ * FILTER_MATCH_PRIMARY, if this method successfully matched a
+ * FilterExpr that was just a PrimaryExpr; or
+ * FILTER_MATCH_FAILED, if this method did not match a FilterExpr
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected int FilterExpr() throws javax.xml.transform.TransformerException
+ {
+
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+ int filterMatch;
+
+ if (PrimaryExpr())
+ {
+ if (tokenIs('['))
+ {
+
+ // int locationPathOpPos = opPos;
+ insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH);
+
+ while (tokenIs('['))
+ {
+ Predicate();
+ }
+
+ filterMatch = FILTER_MATCH_PREDICATES;
+ }
+ else
+ {
+ filterMatch = FILTER_MATCH_PRIMARY;
+ }
+ }
+ else
+ {
+ filterMatch = FILTER_MATCH_FAILED;
+ }
+
+ return filterMatch;
+
+ /*
+ * if(tokenIs('['))
+ * {
+ * Predicate();
+ * m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos;
+ * }
+ */
+ }
+
+ /**
+ *
+ * PrimaryExpr ::= VariableReference
+ * | '(' Expr ')'
+ * | Literal
+ * | Number
+ * | FunctionCall
+ *
+ * @return true if this method successfully matched a PrimaryExpr
+ *
+ * @throws javax.xml.transform.TransformerException
+ *
+ */
+ protected boolean PrimaryExpr() throws javax.xml.transform.TransformerException
+ {
+
+ boolean matchFound;
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+ if ((m_tokenChar == '\'') || (m_tokenChar == '"'))
+ {
+ appendOp(2, OpCodes.OP_LITERAL);
+ Literal();
+
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+
+ matchFound = true;
+ }
+ else if (m_tokenChar == '$')
+ {
+ nextToken(); // consume '$'
+ appendOp(2, OpCodes.OP_VARIABLE);
+ QName();
+
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+
+ matchFound = true;
+ }
+ else if (m_tokenChar == '(')
+ {
+ nextToken();
+ appendOp(2, OpCodes.OP_GROUP);
+ Expr();
+ consumeExpected(')');
+
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+
+ matchFound = true;
+ }
+ else if ((null != m_token) && ((('.' == m_tokenChar) && (m_token.length() > 1) && Character.isDigit(
+ m_token.charAt(1))) || Character.isDigit(m_tokenChar)))
+ {
+ appendOp(2, OpCodes.OP_NUMBERLIT);
+ Number();
+
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+
+ matchFound = true;
+ }
+ else if (lookahead('(', 1) || (lookahead(':', 1) && lookahead('(', 3)))
+ {
+ matchFound = FunctionCall();
+ }
+ else
+ {
+ matchFound = false;
+ }
+
+ return matchFound;
+ }
+
+ /**
+ *
+ * Argument ::= Expr
+ *
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void Argument() throws javax.xml.transform.TransformerException
+ {
+
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+ appendOp(2, OpCodes.OP_ARGUMENT);
+ Expr();
+
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+ }
+
+ /**
+ *
+ * FunctionCall ::= FunctionName '(' ( Argument ( ',' Argument)*)? ')'
+ *
+ * @return true if, and only if, a FunctionCall was matched
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected boolean FunctionCall() throws javax.xml.transform.TransformerException
+ {
+
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+ if (lookahead(':', 1))
+ {
+ appendOp(4, OpCodes.OP_EXTFUNCTION);
+
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, m_queueMark - 1);
+
+ nextToken();
+ consumeExpected(':');
+
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 2, m_queueMark - 1);
+
+ nextToken();
+ }
+ else
+ {
+ int funcTok = getFunctionToken(m_token);
+
+ if (-1 == funcTok)
+ {
+ error(XPATHErrorResources.ER_COULDNOT_FIND_FUNCTION,
+ new Object[]{ m_token }); //"Could not find function: "+m_token+"()");
+ }
+
+ switch (funcTok)
+ {
+ case OpCodes.NODETYPE_PI :
+ case OpCodes.NODETYPE_COMMENT :
+ case OpCodes.NODETYPE_TEXT :
+ case OpCodes.NODETYPE_NODE :
+ // Node type tests look like function calls, but they're not
+ return false;
+ default :
+ appendOp(3, OpCodes.OP_FUNCTION);
+
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, funcTok);
+ }
+
+ nextToken();
+ }
+
+ consumeExpected('(');
+
+ while (!tokenIs(')') && m_token != null)
+ {
+ if (tokenIs(','))
+ {
+ error(XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_PRECEDING_ARG, null); //"Found ',' but no preceding argument!");
+ }
+
+ Argument();
+
+ if (!tokenIs(')'))
+ {
+ consumeExpected(',');
+
+ if (tokenIs(')'))
+ {
+ error(XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_FOLLOWING_ARG,
+ null); //"Found ',' but no following argument!");
+ }
+ }
+ }
+
+ consumeExpected(')');
+
+ // Terminate for safety.
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+
+ return true;
+ }
+
+ // ============= GRAMMAR FUNCTIONS =================
+
+ /**
+ *
+ * LocationPath ::= RelativeLocationPath
+ * | AbsoluteLocationPath
+ *
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void LocationPath() throws javax.xml.transform.TransformerException
+ {
+
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+ // int locationPathOpPos = opPos;
+ appendOp(2, OpCodes.OP_LOCATIONPATH);
+
+ boolean seenSlash = tokenIs('/');
+
+ if (seenSlash)
+ {
+ appendOp(4, OpCodes.FROM_ROOT);
+
+ // Tell how long the step is without the predicate
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_ROOT);
+
+ nextToken();
+ } else if (m_token == null) {
+ error(XPATHErrorResources.ER_EXPECTED_LOC_PATH_AT_END_EXPR, null);
+ }
+
+ if (m_token != null)
+ {
+ if (!RelativeLocationPath() && !seenSlash)
+ {
+ // Neither a '/' nor a RelativeLocationPath - i.e., matched nothing
+ // "Location path expected, but found "+m_token+" was encountered."
+ error(XPATHErrorResources.ER_EXPECTED_LOC_PATH,
+ new Object [] {m_token});
+ }
+ }
+
+ // Terminate for safety.
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+ }
+
+ /**
+ *
+ * RelativeLocationPath ::= Step
+ * | RelativeLocationPath '/' Step
+ * | AbbreviatedRelativeLocationPath
+ *
+ * @returns true if, and only if, a RelativeLocationPath was matched
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected boolean RelativeLocationPath()
+ throws javax.xml.transform.TransformerException
+ {
+ if (!Step())
+ {
+ return false;
+ }
+
+ while (tokenIs('/'))
+ {
+ nextToken();
+
+ if (!Step())
+ {
+ // RelativeLocationPath can't end with a trailing '/'
+ // "Location step expected following '/' or '//'"
+ error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ *
+ * Step ::= Basis Predicate
+ * | AbbreviatedStep
+ *
+ * @returns false if step was empty (or only a '/'); true, otherwise
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected boolean Step() throws javax.xml.transform.TransformerException
+ {
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+ boolean doubleSlash = tokenIs('/');
+
+ // At most a single '/' before each Step is consumed by caller; if the
+ // first thing is a '/', that means we had '//' and the Step must not
+ // be empty.
+ if (doubleSlash)
+ {
+ nextToken();
+
+ appendOp(2, OpCodes.FROM_DESCENDANTS_OR_SELF);
+
+ // Have to fix up for patterns such as '//@foo' or '//attribute::foo',
+ // which translate to 'descendant-or-self::node()/attribute::foo'.
+ // notice I leave the '/' on the queue, so the next will be processed
+ // by a regular step pattern.
+
+ // Make room for telling how long the step is without the predicate
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.NODETYPE_NODE);
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+ // Tell how long the step is without the predicate
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+
+ // Tell how long the step is with the predicate
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+
+ opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+ }
+
+ if (tokenIs("."))
+ {
+ nextToken();
+
+ if (tokenIs('['))
+ {
+ error(XPATHErrorResources.ER_PREDICATE_ILLEGAL_SYNTAX, null); //"'..[predicate]' or '.[predicate]' is illegal syntax. Use 'self::node()[predicate]' instead.");
+ }
+
+ appendOp(4, OpCodes.FROM_SELF);
+
+ // Tell how long the step is without the predicate
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2,4);
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_NODE);
+ }
+ else if (tokenIs(".."))
+ {
+ nextToken();
+ appendOp(4, OpCodes.FROM_PARENT);
+
+ // Tell how long the step is without the predicate
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2,4);
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_NODE);
+ }
+
+ // There is probably a better way to test for this
+ // transition... but it gets real hairy if you try
+ // to do it in basis().
+ else if (tokenIs('*') || tokenIs('@') || tokenIs('_')
+ || (m_token!= null && Character.isLetter(m_token.charAt(0))))
+ {
+ Basis();
+
+ while (tokenIs('['))
+ {
+ Predicate();
+ }
+
+ // Tell how long the entire step is.
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+ }
+ else
+ {
+ // No Step matched - that's an error if previous thing was a '//'
+ if (doubleSlash)
+ {
+ // "Location step expected following '/' or '//'"
+ error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null);
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ *
+ * Basis ::= AxisName '::' NodeTest
+ * | AbbreviatedBasis
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void Basis() throws javax.xml.transform.TransformerException
+ {
+
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+ int axesType;
+
+ // The next blocks guarantee that a FROM_XXX will be added.
+ if (lookahead("::", 1))
+ {
+ axesType = AxisName();
+
+ nextToken();
+ nextToken();
+ }
+ else if (tokenIs('@'))
+ {
+ axesType = OpCodes.FROM_ATTRIBUTES;
+
+ appendOp(2, axesType);
+ nextToken();
+ }
+ else
+ {
+ axesType = OpCodes.FROM_CHILDREN;
+
+ appendOp(2, axesType);
+ }
+
+ // Make room for telling how long the step is without the predicate
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+ NodeTest(axesType);
+
+ // Tell how long the step is without the predicate
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+ }
+
+ /**
+ *
+ * Basis ::= AxisName '::' NodeTest
+ * | AbbreviatedBasis
+ *
+ * @return FROM_XXX axes type, found in {@link com.sun.org.apache.xpath.internal.compiler.Keywords}.
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected int AxisName() throws javax.xml.transform.TransformerException
+ {
+
+ Object val = Keywords.getAxisName(m_token);
+
+ if (null == val)
+ {
+ error(XPATHErrorResources.ER_ILLEGAL_AXIS_NAME,
+ new Object[]{ m_token }); //"illegal axis name: "+m_token);
+ }
+
+ int axesType = ((Integer) val).intValue();
+
+ appendOp(2, axesType);
+
+ return axesType;
+ }
+
+ /**
+ *
+ * NodeTest ::= WildcardName
+ * | NodeType '(' ')'
+ * | 'processing-instruction' '(' Literal ')'
+ *
+ * @param axesType FROM_XXX axes type, found in {@link com.sun.org.apache.xpath.internal.compiler.Keywords}.
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void NodeTest(int axesType) throws javax.xml.transform.TransformerException
+ {
+
+ if (lookahead('(', 1))
+ {
+ Object nodeTestOp = Keywords.getNodeType(m_token);
+
+ if (null == nodeTestOp)
+ {
+ error(XPATHErrorResources.ER_UNKNOWN_NODETYPE,
+ new Object[]{ m_token }); //"Unknown nodetype: "+m_token);
+ }
+ else
+ {
+ nextToken();
+
+ int nt = ((Integer) nodeTestOp).intValue();
+
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), nt);
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+ consumeExpected('(');
+
+ if (OpCodes.NODETYPE_PI == nt)
+ {
+ if (!tokenIs(')'))
+ {
+ Literal();
+ }
+ }
+
+ consumeExpected(')');
+ }
+ }
+ else
+ {
+
+ // Assume name of attribute or element.
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.NODENAME);
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+ if (lookahead(':', 1))
+ {
+ if (tokenIs('*'))
+ {
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ELEMWILDCARD);
+ }
+ else
+ {
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
+
+ // Minimalist check for an NCName - just check first character
+ // to distinguish from other possible tokens
+ if (!Character.isLetter(m_tokenChar) && !tokenIs('_'))
+ {
+ // "Node test that matches either NCName:* or QName was expected."
+ error(XPATHErrorResources.ER_EXPECTED_NODE_TEST, null);
+ }
+ }
+
+ nextToken();
+ consumeExpected(':');
+ }
+ else
+ {
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.EMPTY);
+ }
+
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+ if (tokenIs('*'))
+ {
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ELEMWILDCARD);
+ }
+ else
+ {
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
+
+ // Minimalist check for an NCName - just check first character
+ // to distinguish from other possible tokens
+ if (!Character.isLetter(m_tokenChar) && !tokenIs('_'))
+ {
+ // "Node test that matches either NCName:* or QName was expected."
+ error(XPATHErrorResources.ER_EXPECTED_NODE_TEST, null);
+ }
+ }
+
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+ nextToken();
+ }
+ }
+
+ /**
+ *
+ * Predicate ::= '[' PredicateExpr ']'
+ *
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void Predicate() throws javax.xml.transform.TransformerException
+ {
+
+ if (tokenIs('['))
+ {
+ nextToken();
+ PredicateExpr();
+ consumeExpected(']');
+ }
+ }
+
+ /**
+ *
+ * PredicateExpr ::= Expr
+ *
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void PredicateExpr() throws javax.xml.transform.TransformerException
+ {
+
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+ appendOp(2, OpCodes.OP_PREDICATE);
+ Expr();
+
+ // Terminate for safety.
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+ }
+
+ /**
+ * QName ::= (Prefix ':')? LocalPart
+ * Prefix ::= NCName
+ * LocalPart ::= NCName
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void QName() throws javax.xml.transform.TransformerException
+ {
+ // Namespace
+ if(lookahead(':', 1))
+ {
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+ nextToken();
+ consumeExpected(':');
+ }
+ else
+ {
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.EMPTY);
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+ }
+
+ // Local name
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+ nextToken();
+ }
+
+ /**
+ * NCName ::= (Letter | '_') (NCNameChar)
+ * NCNameChar ::= Letter | Digit | '.' | '-' | '_' | CombiningChar | Extender
+ */
+ protected void NCName()
+ {
+
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+ nextToken();
+ }
+
+ /**
+ * The value of the Literal is the sequence of characters inside
+ * the " or ' characters>.
+ *
+ * Literal ::= '"' [^"]* '"'
+ * | "'" [^']* "'"
+ *
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void Literal() throws javax.xml.transform.TransformerException
+ {
+
+ int last = m_token.length() - 1;
+ char c0 = m_tokenChar;
+ char cX = m_token.charAt(last);
+
+ if (((c0 == '\"') && (cX == '\"')) || ((c0 == '\'') && (cX == '\'')))
+ {
+
+ // Mutate the token to remove the quotes and have the XString object
+ // already made.
+ int tokenQueuePos = m_queueMark - 1;
+
+ m_ops.m_tokenQueue.setElementAt(null,tokenQueuePos);
+
+ Object obj = new XString(m_token.substring(1, last));
+
+ m_ops.m_tokenQueue.setElementAt(obj,tokenQueuePos);
+
+ // lit = m_token.substring(1, last);
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), tokenQueuePos);
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+ nextToken();
+ }
+ else
+ {
+ error(XPATHErrorResources.ER_PATTERN_LITERAL_NEEDS_BE_QUOTED,
+ new Object[]{ m_token }); //"Pattern literal ("+m_token+") needs to be quoted!");
+ }
+ }
+
+ /**
+ *
+ * Number ::= [0-9]+('.'[0-9]+)? | '.'[0-9]+
+ *
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void Number() throws javax.xml.transform.TransformerException
+ {
+
+ if (null != m_token)
+ {
+
+ // Mutate the token to remove the quotes and have the XNumber object
+ // already made.
+ double num;
+
+ try
+ {
+ // XPath 1.0 does not support number in exp notation
+ if ((m_token.indexOf('e') > -1)||(m_token.indexOf('E') > -1))
+ throw new NumberFormatException();
+ num = Double.valueOf(m_token).doubleValue();
+ }
+ catch (NumberFormatException nfe)
+ {
+ num = 0.0; // to shut up compiler.
+
+ error(XPATHErrorResources.ER_COULDNOT_BE_FORMATTED_TO_NUMBER,
+ new Object[]{ m_token }); //m_token+" could not be formatted to a number!");
+ }
+
+ m_ops.m_tokenQueue.setElementAt(new XNumber(num),m_queueMark - 1);
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+ nextToken();
+ }
+ }
+
+ // ============= PATTERN FUNCTIONS =================
+
+ /**
+ *
+ * Pattern ::= LocationPathPattern
+ * | Pattern '|' LocationPathPattern
+ *
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void Pattern() throws javax.xml.transform.TransformerException
+ {
+
+ while (true)
+ {
+ LocationPathPattern();
+
+ if (tokenIs('|'))
+ {
+ nextToken();
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ /**
+ *
+ *
+ * LocationPathPattern ::= '/' RelativePathPattern?
+ * | IdKeyPattern (('/' | '//') RelativePathPattern)?
+ * | '//'? RelativePathPattern
+ *
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void LocationPathPattern() throws javax.xml.transform.TransformerException
+ {
+
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+ final int RELATIVE_PATH_NOT_PERMITTED = 0;
+ final int RELATIVE_PATH_PERMITTED = 1;
+ final int RELATIVE_PATH_REQUIRED = 2;
+
+ int relativePathStatus = RELATIVE_PATH_NOT_PERMITTED;
+
+ appendOp(2, OpCodes.OP_LOCATIONPATHPATTERN);
+
+ if (lookahead('(', 1)
+ && (tokenIs(Keywords.FUNC_ID_STRING)
+ || tokenIs(Keywords.FUNC_KEY_STRING)))
+ {
+ IdKeyPattern();
+
+ if (tokenIs('/'))
+ {
+ nextToken();
+
+ if (tokenIs('/'))
+ {
+ appendOp(4, OpCodes.MATCH_ANY_ANCESTOR);
+
+ nextToken();
+ }
+ else
+ {
+ appendOp(4, OpCodes.MATCH_IMMEDIATE_ANCESTOR);
+ }
+
+ // Tell how long the step is without the predicate
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_FUNCTEST);
+
+ relativePathStatus = RELATIVE_PATH_REQUIRED;
+ }
+ }
+ else if (tokenIs('/'))
+ {
+ if (lookahead('/', 1))
+ {
+ appendOp(4, OpCodes.MATCH_ANY_ANCESTOR);
+
+ // Added this to fix bug reported by Myriam for match="//x/a"
+ // patterns. If you don't do this, the 'x' step will think it's part
+ // of a '//' pattern, and so will cause 'a' to be matched when it has
+ // any ancestor that is 'x'.
+ nextToken();
+
+ relativePathStatus = RELATIVE_PATH_REQUIRED;
+ }
+ else
+ {
+ appendOp(4, OpCodes.FROM_ROOT);
+
+ relativePathStatus = RELATIVE_PATH_PERMITTED;
+ }
+
+
+ // Tell how long the step is without the predicate
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_ROOT);
+
+ nextToken();
+ }
+ else
+ {
+ relativePathStatus = RELATIVE_PATH_REQUIRED;
+ }
+
+ if (relativePathStatus != RELATIVE_PATH_NOT_PERMITTED)
+ {
+ if (!tokenIs('|') && (null != m_token))
+ {
+ RelativePathPattern();
+ }
+ else if (relativePathStatus == RELATIVE_PATH_REQUIRED)
+ {
+ // "A relative path pattern was expected."
+ error(XPATHErrorResources.ER_EXPECTED_REL_PATH_PATTERN, null);
+ }
+ }
+
+ // Terminate for safety.
+ m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+ }
+
+ /**
+ *
+ * IdKeyPattern ::= 'id' '(' Literal ')'
+ * | 'key' '(' Literal ',' Literal ')'
+ * (Also handle doc())
+ *
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void IdKeyPattern() throws javax.xml.transform.TransformerException
+ {
+ FunctionCall();
+ }
+
+ /**
+ *
+ * RelativePathPattern ::= StepPattern
+ * | RelativePathPattern '/' StepPattern
+ * | RelativePathPattern '//' StepPattern
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected void RelativePathPattern()
+ throws javax.xml.transform.TransformerException
+ {
+
+ // Caller will have consumed any '/' or '//' preceding the
+ // RelativePathPattern, so let StepPattern know it can't begin with a '/'
+ boolean trailingSlashConsumed = StepPattern(false);
+
+ while (tokenIs('/'))
+ {
+ nextToken();
+
+ // StepPattern() may consume first slash of pair in "a//b" while
+ // processing StepPattern "a". On next iteration, let StepPattern know
+ // that happened, so it doesn't match ill-formed patterns like "a///b".
+ trailingSlashConsumed = StepPattern(!trailingSlashConsumed);
+ }
+ }
+
+ /**
+ *
+ * StepPattern ::= AbbreviatedNodeTestStep
+ *
+ * @param isLeadingSlashPermitted a boolean indicating whether a slash can
+ * appear at the start of this step
+ *
+ * @return boolean indicating whether a slash following the step was consumed
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected boolean StepPattern(boolean isLeadingSlashPermitted)
+ throws javax.xml.transform.TransformerException
+ {
+ return AbbreviatedNodeTestStep(isLeadingSlashPermitted);
+ }
+
+ /**
+ *
+ * AbbreviatedNodeTestStep ::= '@'? NodeTest Predicate
+ *
+ * @param isLeadingSlashPermitted a boolean indicating whether a slash can
+ * appear at the start of this step
+ *
+ * @return boolean indicating whether a slash following the step was consumed
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ protected boolean AbbreviatedNodeTestStep(boolean isLeadingSlashPermitted)
+ throws javax.xml.transform.TransformerException
+ {
+
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+ int axesType;
+
+ // The next blocks guarantee that a MATCH_XXX will be added.
+ int matchTypePos = -1;
+
+ if (tokenIs('@'))
+ {
+ axesType = OpCodes.MATCH_ATTRIBUTE;
+
+ appendOp(2, axesType);
+ nextToken();
+ }
+ else if (this.lookahead("::", 1))
+ {
+ if (tokenIs("attribute"))
+ {
+ axesType = OpCodes.MATCH_ATTRIBUTE;
+
+ appendOp(2, axesType);
+ }
+ else if (tokenIs("child"))
+ {
+ matchTypePos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+ axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR;
+
+ appendOp(2, axesType);
+ }
+ else
+ {
+ axesType = -1;
+
+ this.error(XPATHErrorResources.ER_AXES_NOT_ALLOWED,
+ new Object[]{ this.m_token });
+ }
+
+ nextToken();
+ nextToken();
+ }
+ else if (tokenIs('/'))
+ {
+ if (!isLeadingSlashPermitted)
+ {
+ // "A step was expected in the pattern, but '/' was encountered."
+ error(XPATHErrorResources.ER_EXPECTED_STEP_PATTERN, null);
+ }
+ axesType = OpCodes.MATCH_ANY_ANCESTOR;
+
+ appendOp(2, axesType);
+ nextToken();
+ }
+ else
+ {
+ matchTypePos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+ axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR;
+
+ appendOp(2, axesType);
+ }
+
+ // Make room for telling how long the step is without the predicate
+ m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
+
+ NodeTest(axesType);
+
+ // Tell how long the step is without the predicate
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+
+ while (tokenIs('['))
+ {
+ Predicate();
+ }
+
+ boolean trailingSlashConsumed;
+
+ // For "a//b", where "a" is current step, we need to mark operation of
+ // current step as "MATCH_ANY_ANCESTOR". Then we'll consume the first
+ // slash and subsequent step will be treated as a MATCH_IMMEDIATE_ANCESTOR
+ // (unless it too is followed by '//'.)
+ //
+ // %REVIEW% Following is what happens today, but I'm not sure that's
+ // %REVIEW% correct behaviour. Perhaps no valid case could be constructed
+ // %REVIEW% where it would matter?
+ //
+ // If current step is on the attribute axis (e.g., "@x//b"), we won't
+ // change the current step, and let following step be marked as
+ // MATCH_ANY_ANCESTOR on next call instead.
+ if ((matchTypePos > -1) && tokenIs('/') && lookahead('/', 1))
+ {
+ m_ops.setOp(matchTypePos, OpCodes.MATCH_ANY_ANCESTOR);
+
+ nextToken();
+
+ trailingSlashConsumed = true;
+ }
+ else
+ {
+ trailingSlashConsumed = false;
+ }
+
+ // Tell how long the entire step is.
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+
+ return trailingSlashConsumed;
+ }
+}