2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
19 * $Id: XPathParser.java 468655 2006-10-28 07:12:06Z minchau $
21 package org.apache.xpath.compiler;
23 import javax.xml.transform.ErrorListener;
24 import javax.xml.transform.TransformerException;
26 import org.apache.xalan.res.XSLMessages;
27 import org.apache.xml.utils.PrefixResolver;
28 import org.apache.xpath.XPathProcessorException;
29 import org.apache.xpath.domapi.XPathStylesheetDOM3Exception;
30 import org.apache.xpath.objects.XNumber;
31 import org.apache.xpath.objects.XString;
32 import org.apache.xpath.res.XPATHErrorResources;
35 * Tokenizes and parses XPath expressions. This should really be named
36 * XPathParserImpl, and may be renamed in the future.
39 public class XPathParser
41 // %REVIEW% Is there a better way of doing this?
42 // Upside is minimum object churn. Downside is that we don't have a useful
43 // backtrace in the exception itself -- but we don't expect to need one.
44 static public final String CONTINUE_AFTER_FATAL_ERROR="CONTINUE_AFTER_FATAL_ERROR";
47 * The XPath to be processed.
52 * The next token in the pattern.
54 transient String m_token;
57 * The first char in m_token, the theory being that this
58 * is an optimization because we won't have to do charAt(0) as
61 transient char m_tokenChar = 0;
64 * The position in the token queue is tracked by m_queueMark.
69 * Results from checking FilterExpr syntax
71 protected final static int FILTER_MATCH_FAILED = 0;
72 protected final static int FILTER_MATCH_PRIMARY = 1;
73 protected final static int FILTER_MATCH_PREDICATES = 2;
76 * The parser constructor.
78 public XPathParser(ErrorListener errorListener, javax.xml.transform.SourceLocator sourceLocator)
80 m_errorListener = errorListener;
81 m_sourceLocator = sourceLocator;
85 * The prefix resolver to map prefixes to namespaces in the OpMap.
87 PrefixResolver m_namespaceContext;
90 * Given an string, init an XPath object for selections,
91 * in order that a parse doesn't
92 * have to be done each time the expression is evaluated.
94 * @param compiler The compiler object.
95 * @param expression A string conforming to the XPath grammar.
96 * @param namespaceContext An object that is able to resolve prefixes in
97 * the XPath to namespaces.
99 * @throws javax.xml.transform.TransformerException
101 public void initXPath(
102 Compiler compiler, String expression, PrefixResolver namespaceContext)
103 throws javax.xml.transform.TransformerException
107 m_namespaceContext = namespaceContext;
108 m_functionTable = compiler.getFunctionTable();
110 Lexer lexer = new Lexer(compiler, namespaceContext, this);
112 lexer.tokenize(expression);
114 m_ops.setOp(0,OpCodes.OP_XPATH);
115 m_ops.setOp(OpMap.MAPINDEX_LENGTH,2);
118 // Patch for Christine's gripe. She wants her errorHandler to return from
119 // a fatal error and continue trying to parse, rather than throwing an exception.
120 // Without the patch, that put us into an endless loop.
122 // %REVIEW% Is there a better way of doing this?
123 // %REVIEW% Are there any other cases which need the safety net?
124 // (and if so do we care right now, or should we rewrite the XPath
125 // grammar engine and can fix it at that time?)
133 String extraTokens = "";
135 while (null != m_token)
137 extraTokens += "'" + m_token + "'";
145 error(XPATHErrorResources.ER_EXTRA_ILLEGAL_TOKENS,
146 new Object[]{ extraTokens }); //"Extra illegal tokens: "+extraTokens);
150 catch (org.apache.xpath.XPathProcessorException e)
152 if(CONTINUE_AFTER_FATAL_ERROR.equals(e.getMessage()))
154 // What I _want_ to do is null out this XPath.
155 // I doubt this has the desired effect, but I'm not sure what else to do.
157 initXPath(compiler, "/..", namespaceContext);
167 * Given an string, init an XPath object for pattern matches,
168 * in order that a parse doesn't
169 * have to be done each time the expression is evaluated.
170 * @param compiler The XPath object to be initialized.
171 * @param expression A String representing the XPath.
172 * @param namespaceContext An object that is able to resolve prefixes in
173 * the XPath to namespaces.
175 * @throws javax.xml.transform.TransformerException
177 public void initMatchPattern(
178 Compiler compiler, String expression, PrefixResolver namespaceContext)
179 throws javax.xml.transform.TransformerException
183 m_namespaceContext = namespaceContext;
184 m_functionTable = compiler.getFunctionTable();
186 Lexer lexer = new Lexer(compiler, namespaceContext, this);
188 lexer.tokenize(expression);
190 m_ops.setOp(0, OpCodes.OP_MATCHPATTERN);
191 m_ops.setOp(OpMap.MAPINDEX_LENGTH, 2);
198 String extraTokens = "";
200 while (null != m_token)
202 extraTokens += "'" + m_token + "'";
210 error(XPATHErrorResources.ER_EXTRA_ILLEGAL_TOKENS,
211 new Object[]{ extraTokens }); //"Extra illegal tokens: "+extraTokens);
214 // Terminate for safety.
215 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
216 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH)+1);
221 /** The error listener where syntax errors are to be sent.
223 private ErrorListener m_errorListener;
225 /** The source location of the XPath. */
226 javax.xml.transform.SourceLocator m_sourceLocator;
228 /** The table contains build-in functions and customized functions */
229 private FunctionTable m_functionTable;
232 * Allow an application to register an error event handler, where syntax
233 * errors will be sent. If the error listener is not set, syntax errors
234 * will be sent to System.err.
236 * @param handler Reference to error listener where syntax errors will be
239 public void setErrorHandler(ErrorListener handler)
241 m_errorListener = handler;
245 * Return the current error listener.
247 * @return The error listener, which should not normally be null, but may be.
249 public ErrorListener getErrorListener()
251 return m_errorListener;
255 * Check whether m_token matches the target string.
257 * @param s A string reference or null.
259 * @return If m_token is null, returns false (or true if s is also null), or
260 * return true if the current token matches the string, else false.
262 final boolean tokenIs(String s)
264 return (m_token != null) ? (m_token.equals(s)) : (s == null);
268 * Check whether m_tokenChar==c.
270 * @param c A character to be tested.
272 * @return If m_token is null, returns false, or return true if c matches
275 final boolean tokenIs(char c)
277 return (m_token != null) ? (m_tokenChar == c) : false;
281 * Look ahead of the current token in order to
282 * make a branching decision.
284 * @param c the character to be tested for.
285 * @param n number of tokens to look ahead. Must be
288 * @return true if the next token matches the character argument.
290 final boolean lookahead(char c, int n)
293 int pos = (m_queueMark + n);
296 if ((pos <= m_ops.getTokenQueueSize()) && (pos > 0)
297 && (m_ops.getTokenQueueSize() != 0))
299 String tok = ((String) m_ops.m_tokenQueue.elementAt(pos - 1));
301 b = (tok.length() == 1) ? (tok.charAt(0) == c) : false;
312 * Look behind the first character of the current token in order to
313 * make a branching decision.
315 * @param c the character to compare it to.
316 * @param n number of tokens to look behind. Must be
317 * greater than 1. Note that the look behind terminates
318 * at either the beginning of the string or on a '|'
319 * character. Because of this, this method should only
320 * be used for pattern matching.
322 * @return true if the token behind the current token matches the character
325 private final boolean lookbehind(char c, int n)
329 int lookBehindPos = m_queueMark - (n + 1);
331 if (lookBehindPos >= 0)
333 String lookbehind = (String) m_ops.m_tokenQueue.elementAt(lookBehindPos);
335 if (lookbehind.length() == 1)
337 char c0 = (lookbehind == null) ? '|' : lookbehind.charAt(0);
339 isToken = (c0 == '|') ? false : (c0 == c);
355 * look behind the current token in order to
356 * see if there is a useable token.
358 * @param n number of tokens to look behind. Must be
359 * greater than 1. Note that the look behind terminates
360 * at either the beginning of the string or on a '|'
361 * character. Because of this, this method should only
362 * be used for pattern matching.
364 * @return true if look behind has a token, false otherwise.
366 private final boolean lookbehindHasToken(int n)
371 if ((m_queueMark - n) > 0)
373 String lookbehind = (String) m_ops.m_tokenQueue.elementAt(m_queueMark - (n - 1));
374 char c0 = (lookbehind == null) ? '|' : lookbehind.charAt(0);
376 hasToken = (c0 == '|') ? false : true;
387 * Look ahead of the current token in order to
388 * make a branching decision.
390 * @param s the string to compare it to.
391 * @param n number of tokens to lookahead. Must be
394 * @return true if the token behind the current token matches the string
397 private final boolean lookahead(String s, int n)
402 if ((m_queueMark + n) <= m_ops.getTokenQueueSize())
404 String lookahead = (String) m_ops.m_tokenQueue.elementAt(m_queueMark + (n - 1));
406 isToken = (lookahead != null) ? lookahead.equals(s) : (s == null);
410 isToken = (null == s);
417 * Retrieve the next token from the command and
418 * store it in m_token string.
420 private final void nextToken()
423 if (m_queueMark < m_ops.getTokenQueueSize())
425 m_token = (String) m_ops.m_tokenQueue.elementAt(m_queueMark++);
426 m_tokenChar = m_token.charAt(0);
436 * Retrieve a token relative to the current token.
438 * @param i Position relative to current token.
440 * @return The string at the given index, or null if the index is out
443 private final String getTokenRelative(int i)
447 int relative = m_queueMark + i;
449 if ((relative > 0) && (relative < m_ops.getTokenQueueSize()))
451 tok = (String) m_ops.m_tokenQueue.elementAt(relative);
462 * Retrieve the previous token from the command and
463 * store it in m_token string.
465 private final void prevToken()
472 m_token = (String) m_ops.m_tokenQueue.elementAt(m_queueMark);
473 m_tokenChar = m_token.charAt(0);
483 * Consume an expected token, throwing an exception if it
486 * @param expected The string to be expected.
488 * @throws javax.xml.transform.TransformerException
490 private final void consumeExpected(String expected)
491 throws javax.xml.transform.TransformerException
494 if (tokenIs(expected))
500 error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND, new Object[]{ expected,
501 m_token }); //"Expected "+expected+", but found: "+m_token);
503 // Patch for Christina's gripe. She wants her errorHandler to return from
504 // this error and continue trying to parse, rather than throwing an exception.
505 // Without the patch, that put us into an endless loop.
506 throw new XPathProcessorException(CONTINUE_AFTER_FATAL_ERROR);
511 * Consume an expected token, throwing an exception if it
514 * @param expected the character to be expected.
516 * @throws javax.xml.transform.TransformerException
518 private final void consumeExpected(char expected)
519 throws javax.xml.transform.TransformerException
522 if (tokenIs(expected))
528 error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND,
529 new Object[]{ String.valueOf(expected),
530 m_token }); //"Expected "+expected+", but found: "+m_token);
532 // Patch for Christina's gripe. She wants her errorHandler to return from
533 // this error and continue trying to parse, rather than throwing an exception.
534 // Without the patch, that put us into an endless loop.
535 throw new XPathProcessorException(CONTINUE_AFTER_FATAL_ERROR);
540 * Warn the user of a problem.
542 * @param msg An error msgkey that corresponds to one of the constants found
543 * in {@link org.apache.xpath.res.XPATHErrorResources}, which is
544 * a key for a format string.
545 * @param args An array of arguments represented in the format string, which
548 * @throws TransformerException if the current ErrorListoner determines to
549 * throw an exception.
551 void warn(String msg, Object[] args) throws TransformerException
554 String fmsg = XSLMessages.createXPATHWarning(msg, args);
555 ErrorListener ehandler = this.getErrorListener();
557 if (null != ehandler)
559 // TO DO: Need to get stylesheet Locator from here.
560 ehandler.warning(new TransformerException(fmsg, m_sourceLocator));
564 // Should never happen.
565 System.err.println(fmsg);
570 * Notify the user of an assertion error, and probably throw an
573 * @param b If false, a runtime exception will be thrown.
574 * @param msg The assertion message, which should be informative.
576 * @throws RuntimeException if the b argument is false.
578 private void assertion(boolean b, String msg)
583 String fMsg = XSLMessages.createXPATHMessage(
584 XPATHErrorResources.ER_INCORRECT_PROGRAMMER_ASSERTION,
585 new Object[]{ msg });
587 throw new RuntimeException(fMsg);
592 * Notify the user of an error, and probably throw an
595 * @param msg An error msgkey that corresponds to one of the constants found
596 * in {@link org.apache.xpath.res.XPATHErrorResources}, which is
597 * a key for a format string.
598 * @param args An array of arguments represented in the format string, which
601 * @throws TransformerException if the current ErrorListoner determines to
602 * throw an exception.
604 void error(String msg, Object[] args) throws TransformerException
607 String fmsg = XSLMessages.createXPATHMessage(msg, args);
608 ErrorListener ehandler = this.getErrorListener();
610 TransformerException te = new TransformerException(fmsg, m_sourceLocator);
611 if (null != ehandler)
613 // TO DO: Need to get stylesheet Locator from here.
614 ehandler.fatalError(te);
618 // System.err.println(fmsg);
624 * This method is added to support DOM 3 XPath API.
626 * This method is exactly like error(String, Object[]); except that
627 * the underlying TransformerException is
628 * XpathStylesheetDOM3Exception (which extends TransformerException).
630 * So older XPath code in Xalan is not affected by this. To older XPath code
631 * the behavior of whether error() or errorForDOM3() is called because it is
632 * always catching TransformerException objects and is oblivious to
633 * the new subclass of XPathStylesheetDOM3Exception. Older XPath code
636 * However, newer DOM3 XPath code upon catching a TransformerException can
637 * can check if the exception is an instance of XPathStylesheetDOM3Exception
638 * and take appropriate action.
640 * @param msg An error msgkey that corresponds to one of the constants found
641 * in {@link org.apache.xpath.res.XPATHErrorResources}, which is
642 * a key for a format string.
643 * @param args An array of arguments represented in the format string, which
646 * @throws TransformerException if the current ErrorListoner determines to
647 * throw an exception.
649 void errorForDOM3(String msg, Object[] args) throws TransformerException
652 String fmsg = XSLMessages.createXPATHMessage(msg, args);
653 ErrorListener ehandler = this.getErrorListener();
655 TransformerException te = new XPathStylesheetDOM3Exception(fmsg, m_sourceLocator);
656 if (null != ehandler)
658 // TO DO: Need to get stylesheet Locator from here.
659 ehandler.fatalError(te);
663 // System.err.println(fmsg);
668 * Dump the remaining token queue.
669 * Thanks to Craig for this.
671 * @return A dump of the remaining token queue, which may be appended to
674 protected String dumpRemainingTokenQueue()
680 if (q < m_ops.getTokenQueueSize())
682 String msg = "\n Remaining tokens: (";
684 while (q < m_ops.getTokenQueueSize())
686 String t = (String) m_ops.m_tokenQueue.elementAt(q++);
688 msg += (" '" + t + "'");
691 returnMsg = msg + ")";
702 * Given a string, return the corresponding function token.
704 * @param key A local name of a function.
706 * @return The function ID, which may correspond to one of the FUNC_XXX
707 * values found in {@link org.apache.xpath.compiler.FunctionTable}, but may
708 * be a value installed by an external module.
710 final int getFunctionToken(String key)
719 // These are nodetests, xpathparser treats them as functions when parsing
721 id = Keywords.lookupNodeTest(key);
722 if (null == id) id = m_functionTable.getFunctionID(key);
723 tok = ((Integer) id).intValue();
725 catch (NullPointerException npe)
729 catch (ClassCastException cce)
738 * Insert room for operation. This will NOT set
739 * the length value of the operation, but will update
740 * the length value for the total expression.
742 * @param pos The position where the op is to be inserted.
743 * @param length The length of the operation space in the op map.
744 * @param op The op code to the inserted.
746 void insertOp(int pos, int length, int op)
749 int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
751 for (int i = totalLen - 1; i >= pos; i--)
753 m_ops.setOp(i + length, m_ops.getOp(i));
757 m_ops.setOp(OpMap.MAPINDEX_LENGTH,totalLen + length);
761 * Insert room for operation. This WILL set
762 * the length value of the operation, and will update
763 * the length value for the total expression.
765 * @param length The length of the operation.
766 * @param op The op code to the inserted.
768 void appendOp(int length, int op)
771 int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
773 m_ops.setOp(totalLen, op);
774 m_ops.setOp(totalLen + OpMap.MAPINDEX_LENGTH, length);
775 m_ops.setOp(OpMap.MAPINDEX_LENGTH, totalLen + length);
778 // ============= EXPRESSIONS FUNCTIONS =================
786 * @throws javax.xml.transform.TransformerException
788 protected void Expr() throws javax.xml.transform.TransformerException
797 * | OrExpr 'or' AndExpr
800 * @throws javax.xml.transform.TransformerException
802 protected void OrExpr() throws javax.xml.transform.TransformerException
805 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
809 if ((null != m_token) && tokenIs("or"))
812 insertOp(opPos, 2, OpCodes.OP_OR);
815 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
816 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
823 * AndExpr ::= EqualityExpr
824 * | AndExpr 'and' EqualityExpr
827 * @throws javax.xml.transform.TransformerException
829 protected void AndExpr() throws javax.xml.transform.TransformerException
832 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
836 if ((null != m_token) && tokenIs("and"))
839 insertOp(opPos, 2, OpCodes.OP_AND);
842 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
843 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
849 * @returns an Object which is either a String, a Number, a Boolean, or a vector
852 * EqualityExpr ::= RelationalExpr
853 * | EqualityExpr '=' RelationalExpr
856 * @param addPos Position where expression is to be added, or -1 for append.
858 * @return the position at the end of the equality expression.
860 * @throws javax.xml.transform.TransformerException
862 protected int EqualityExpr(int addPos) throws javax.xml.transform.TransformerException
865 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
874 if (tokenIs('!') && lookahead('=', 1))
878 insertOp(addPos, 2, OpCodes.OP_NOTEQUALS);
880 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
882 addPos = EqualityExpr(addPos);
883 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
884 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
887 else if (tokenIs('='))
890 insertOp(addPos, 2, OpCodes.OP_EQUALS);
892 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
894 addPos = EqualityExpr(addPos);
895 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
896 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
906 * @returns an Object which is either a String, a Number, a Boolean, or a vector
909 * RelationalExpr ::= AdditiveExpr
910 * | RelationalExpr '<' AdditiveExpr
911 * | RelationalExpr '>' AdditiveExpr
912 * | RelationalExpr '<=' AdditiveExpr
913 * | RelationalExpr '>=' AdditiveExpr
916 * @param addPos Position where expression is to be added, or -1 for append.
918 * @return the position at the end of the relational expression.
920 * @throws javax.xml.transform.TransformerException
922 protected int RelationalExpr(int addPos) throws javax.xml.transform.TransformerException
925 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
941 insertOp(addPos, 2, OpCodes.OP_LTE);
945 insertOp(addPos, 2, OpCodes.OP_LT);
948 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
950 addPos = RelationalExpr(addPos);
951 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
952 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
955 else if (tokenIs('>'))
962 insertOp(addPos, 2, OpCodes.OP_GTE);
966 insertOp(addPos, 2, OpCodes.OP_GT);
969 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
971 addPos = RelationalExpr(addPos);
972 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
973 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
982 * This has to handle construction of the operations so that they are evaluated
983 * in pre-fix order. So, for 9+7-6, instead of |+|9|-|7|6|, this needs to be
984 * evaluated as |-|+|9|7|6|.
986 * AdditiveExpr ::= MultiplicativeExpr
987 * | AdditiveExpr '+' MultiplicativeExpr
988 * | AdditiveExpr '-' MultiplicativeExpr
991 * @param addPos Position where expression is to be added, or -1 for append.
993 * @return the position at the end of the equality expression.
995 * @throws javax.xml.transform.TransformerException
997 protected int AdditiveExpr(int addPos) throws javax.xml.transform.TransformerException
1000 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1005 MultiplicativeExpr(-1);
1007 if (null != m_token)
1012 insertOp(addPos, 2, OpCodes.OP_PLUS);
1014 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1016 addPos = AdditiveExpr(addPos);
1017 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1018 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1021 else if (tokenIs('-'))
1024 insertOp(addPos, 2, OpCodes.OP_MINUS);
1026 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1028 addPos = AdditiveExpr(addPos);
1029 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1030 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1039 * This has to handle construction of the operations so that they are evaluated
1040 * in pre-fix order. So, for 9+7-6, instead of |+|9|-|7|6|, this needs to be
1041 * evaluated as |-|+|9|7|6|.
1043 * MultiplicativeExpr ::= UnaryExpr
1044 * | MultiplicativeExpr MultiplyOperator UnaryExpr
1045 * | MultiplicativeExpr 'div' UnaryExpr
1046 * | MultiplicativeExpr 'mod' UnaryExpr
1047 * | MultiplicativeExpr 'quo' UnaryExpr
1049 * @param addPos Position where expression is to be added, or -1 for append.
1051 * @return the position at the end of the equality expression.
1053 * @throws javax.xml.transform.TransformerException
1055 protected int MultiplicativeExpr(int addPos) throws javax.xml.transform.TransformerException
1058 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1065 if (null != m_token)
1070 insertOp(addPos, 2, OpCodes.OP_MULT);
1072 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1074 addPos = MultiplicativeExpr(addPos);
1075 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1076 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1079 else if (tokenIs("div"))
1082 insertOp(addPos, 2, OpCodes.OP_DIV);
1084 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1086 addPos = MultiplicativeExpr(addPos);
1087 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1088 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1091 else if (tokenIs("mod"))
1094 insertOp(addPos, 2, OpCodes.OP_MOD);
1096 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1098 addPos = MultiplicativeExpr(addPos);
1099 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1100 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1103 else if (tokenIs("quo"))
1106 insertOp(addPos, 2, OpCodes.OP_QUO);
1108 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1110 addPos = MultiplicativeExpr(addPos);
1111 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1112 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1122 * UnaryExpr ::= UnionExpr
1126 * @throws javax.xml.transform.TransformerException
1128 protected void UnaryExpr() throws javax.xml.transform.TransformerException
1131 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1132 boolean isNeg = false;
1134 if (m_tokenChar == '-')
1137 appendOp(2, OpCodes.OP_NEG);
1145 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1146 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1151 * StringExpr ::= Expr
1154 * @throws javax.xml.transform.TransformerException
1156 protected void StringExpr() throws javax.xml.transform.TransformerException
1159 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1161 appendOp(2, OpCodes.OP_STRING);
1164 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1165 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1171 * StringExpr ::= Expr
1174 * @throws javax.xml.transform.TransformerException
1176 protected void BooleanExpr() throws javax.xml.transform.TransformerException
1179 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1181 appendOp(2, OpCodes.OP_BOOL);
1184 int opLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos;
1188 error(XPATHErrorResources.ER_BOOLEAN_ARG_NO_LONGER_OPTIONAL, null); //"boolean(...) argument is no longer optional with 19990709 XPath draft.");
1191 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, opLen);
1197 * NumberExpr ::= Expr
1200 * @throws javax.xml.transform.TransformerException
1202 protected void NumberExpr() throws javax.xml.transform.TransformerException
1205 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1207 appendOp(2, OpCodes.OP_NUMBER);
1210 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1211 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1215 * The context of the right hand side expressions is the context of the
1216 * left hand side expression. The results of the right hand side expressions
1217 * are node sets. The result of the left hand side UnionExpr is the union
1218 * of the results of the right hand side expressions.
1221 * UnionExpr ::= PathExpr
1222 * | UnionExpr '|' PathExpr
1225 * @throws javax.xml.transform.TransformerException
1227 protected void UnionExpr() throws javax.xml.transform.TransformerException
1230 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1231 boolean continueOrLoop = true;
1232 boolean foundUnion = false;
1240 if (false == foundUnion)
1244 insertOp(opPos, 2, OpCodes.OP_UNION);
1254 // this.m_testForDocOrder = true;
1256 while (continueOrLoop);
1258 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1259 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1263 * PathExpr ::= LocationPath
1265 * | FilterExpr '/' RelativeLocationPath
1266 * | FilterExpr '//' RelativeLocationPath
1268 * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
1269 * the error condition is severe enough to halt processing.
1271 * @throws javax.xml.transform.TransformerException
1273 protected void PathExpr() throws javax.xml.transform.TransformerException
1276 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1278 int filterExprMatch = FilterExpr();
1280 if (filterExprMatch != FILTER_MATCH_FAILED)
1282 // If FilterExpr had Predicates, a OP_LOCATIONPATH opcode would already
1283 // have been inserted.
1284 boolean locationPathStarted = (filterExprMatch==FILTER_MATCH_PREDICATES);
1290 if (!locationPathStarted)
1292 // int locationPathOpPos = opPos;
1293 insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH);
1295 locationPathStarted = true;
1298 if (!RelativeLocationPath())
1300 // "Relative location path expected following '/' or '//'"
1301 error(XPATHErrorResources.ER_EXPECTED_REL_LOC_PATH, null);
1306 // Terminate for safety.
1307 if (locationPathStarted)
1309 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1310 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1311 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1312 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1324 * FilterExpr ::= PrimaryExpr
1325 * | FilterExpr Predicate
1327 * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
1328 * the error condition is severe enough to halt processing.
1330 * @return FILTER_MATCH_PREDICATES, if this method successfully matched a
1331 * FilterExpr with one or more Predicates;
1332 * FILTER_MATCH_PRIMARY, if this method successfully matched a
1333 * FilterExpr that was just a PrimaryExpr; or
1334 * FILTER_MATCH_FAILED, if this method did not match a FilterExpr
1336 * @throws javax.xml.transform.TransformerException
1338 protected int FilterExpr() throws javax.xml.transform.TransformerException
1341 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1350 // int locationPathOpPos = opPos;
1351 insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH);
1353 while (tokenIs('['))
1358 filterMatch = FILTER_MATCH_PREDICATES;
1362 filterMatch = FILTER_MATCH_PRIMARY;
1367 filterMatch = FILTER_MATCH_FAILED;
1376 * m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos;
1383 * PrimaryExpr ::= VariableReference
1389 * @return true if this method successfully matched a PrimaryExpr
1391 * @throws javax.xml.transform.TransformerException
1394 protected boolean PrimaryExpr() throws javax.xml.transform.TransformerException
1398 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1400 if ((m_tokenChar == '\'') || (m_tokenChar == '"'))
1402 appendOp(2, OpCodes.OP_LITERAL);
1405 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1406 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1410 else if (m_tokenChar == '$')
1412 nextToken(); // consume '$'
1413 appendOp(2, OpCodes.OP_VARIABLE);
1416 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1417 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1421 else if (m_tokenChar == '(')
1424 appendOp(2, OpCodes.OP_GROUP);
1426 consumeExpected(')');
1428 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1429 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1433 else if ((null != m_token) && ((('.' == m_tokenChar) && (m_token.length() > 1) && Character.isDigit(
1434 m_token.charAt(1))) || Character.isDigit(m_tokenChar)))
1436 appendOp(2, OpCodes.OP_NUMBERLIT);
1439 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1440 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1444 else if (lookahead('(', 1) || (lookahead(':', 1) && lookahead('(', 3)))
1446 matchFound = FunctionCall();
1461 * @throws javax.xml.transform.TransformerException
1463 protected void Argument() throws javax.xml.transform.TransformerException
1466 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1468 appendOp(2, OpCodes.OP_ARGUMENT);
1471 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1472 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1477 * FunctionCall ::= FunctionName '(' ( Argument ( ',' Argument)*)? ')'
1479 * @return true if, and only if, a FunctionCall was matched
1481 * @throws javax.xml.transform.TransformerException
1483 protected boolean FunctionCall() throws javax.xml.transform.TransformerException
1486 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1488 if (lookahead(':', 1))
1490 appendOp(4, OpCodes.OP_EXTFUNCTION);
1492 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, m_queueMark - 1);
1495 consumeExpected(':');
1497 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 2, m_queueMark - 1);
1503 int funcTok = getFunctionToken(m_token);
1507 error(XPATHErrorResources.ER_COULDNOT_FIND_FUNCTION,
1508 new Object[]{ m_token }); //"Could not find function: "+m_token+"()");
1513 case OpCodes.NODETYPE_PI :
1514 case OpCodes.NODETYPE_COMMENT :
1515 case OpCodes.NODETYPE_TEXT :
1516 case OpCodes.NODETYPE_NODE :
1517 // Node type tests look like function calls, but they're not
1520 appendOp(3, OpCodes.OP_FUNCTION);
1522 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, funcTok);
1528 consumeExpected('(');
1530 while (!tokenIs(')') && m_token != null)
1534 error(XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_PRECEDING_ARG, null); //"Found ',' but no preceding argument!");
1541 consumeExpected(',');
1545 error(XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_FOLLOWING_ARG,
1546 null); //"Found ',' but no following argument!");
1551 consumeExpected(')');
1553 // Terminate for safety.
1554 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1555 m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1556 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1557 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1562 // ============= GRAMMAR FUNCTIONS =================
1566 * LocationPath ::= RelativeLocationPath
1567 * | AbsoluteLocationPath
1570 * @throws javax.xml.transform.TransformerException
1572 protected void LocationPath() throws javax.xml.transform.TransformerException
1575 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1577 // int locationPathOpPos = opPos;
1578 appendOp(2, OpCodes.OP_LOCATIONPATH);
1580 boolean seenSlash = tokenIs('/');
1584 appendOp(4, OpCodes.FROM_ROOT);
1586 // Tell how long the step is without the predicate
1587 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
1588 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_ROOT);
1591 } else if (m_token == null) {
1592 error(XPATHErrorResources.ER_EXPECTED_LOC_PATH_AT_END_EXPR, null);
1595 if (m_token != null)
1597 if (!RelativeLocationPath() && !seenSlash)
1599 // Neither a '/' nor a RelativeLocationPath - i.e., matched nothing
1600 // "Location path expected, but found "+m_token+" was encountered."
1601 error(XPATHErrorResources.ER_EXPECTED_LOC_PATH,
1602 new Object [] {m_token});
1606 // Terminate for safety.
1607 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1608 m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1609 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1610 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1615 * RelativeLocationPath ::= Step
1616 * | RelativeLocationPath '/' Step
1617 * | AbbreviatedRelativeLocationPath
1619 * @returns true if, and only if, a RelativeLocationPath was matched
1621 * @throws javax.xml.transform.TransformerException
1623 protected boolean RelativeLocationPath()
1624 throws javax.xml.transform.TransformerException
1631 while (tokenIs('/'))
1637 // RelativeLocationPath can't end with a trailing '/'
1638 // "Location step expected following '/' or '//'"
1639 error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null);
1648 * Step ::= Basis Predicate
1651 * @returns false if step was empty (or only a '/'); true, otherwise
1653 * @throws javax.xml.transform.TransformerException
1655 protected boolean Step() throws javax.xml.transform.TransformerException
1657 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1659 boolean doubleSlash = tokenIs('/');
1661 // At most a single '/' before each Step is consumed by caller; if the
1662 // first thing is a '/', that means we had '//' and the Step must not
1668 appendOp(2, OpCodes.FROM_DESCENDANTS_OR_SELF);
1670 // Have to fix up for patterns such as '//@foo' or '//attribute::foo',
1671 // which translate to 'descendant-or-self::node()/attribute::foo'.
1672 // notice I leave the '/' on the queue, so the next will be processed
1673 // by a regular step pattern.
1675 // Make room for telling how long the step is without the predicate
1676 m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1677 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.NODETYPE_NODE);
1678 m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1680 // Tell how long the step is without the predicate
1681 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
1682 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1684 // Tell how long the step is with the predicate
1685 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1686 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1688 opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1697 error(XPATHErrorResources.ER_PREDICATE_ILLEGAL_SYNTAX, null); //"'..[predicate]' or '.[predicate]' is illegal syntax. Use 'self::node()[predicate]' instead.");
1700 appendOp(4, OpCodes.FROM_SELF);
1702 // Tell how long the step is without the predicate
1703 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2,4);
1704 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_NODE);
1706 else if (tokenIs(".."))
1709 appendOp(4, OpCodes.FROM_PARENT);
1711 // Tell how long the step is without the predicate
1712 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2,4);
1713 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_NODE);
1716 // There is probably a better way to test for this
1717 // transition... but it gets real hairy if you try
1718 // to do it in basis().
1719 else if (tokenIs('*') || tokenIs('@') || tokenIs('_')
1720 || (m_token!= null && Character.isLetter(m_token.charAt(0))))
1724 while (tokenIs('['))
1729 // Tell how long the entire step is.
1730 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1731 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1735 // No Step matched - that's an error if previous thing was a '//'
1738 // "Location step expected following '/' or '//'"
1739 error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null);
1750 * Basis ::= AxisName '::' NodeTest
1751 * | AbbreviatedBasis
1753 * @throws javax.xml.transform.TransformerException
1755 protected void Basis() throws javax.xml.transform.TransformerException
1758 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1761 // The next blocks guarantee that a FROM_XXX will be added.
1762 if (lookahead("::", 1))
1764 axesType = AxisName();
1769 else if (tokenIs('@'))
1771 axesType = OpCodes.FROM_ATTRIBUTES;
1773 appendOp(2, axesType);
1778 axesType = OpCodes.FROM_CHILDREN;
1780 appendOp(2, axesType);
1783 // Make room for telling how long the step is without the predicate
1784 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1788 // Tell how long the step is without the predicate
1789 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
1790 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1795 * Basis ::= AxisName '::' NodeTest
1796 * | AbbreviatedBasis
1798 * @return FROM_XXX axes type, found in {@link org.apache.xpath.compiler.Keywords}.
1800 * @throws javax.xml.transform.TransformerException
1802 protected int AxisName() throws javax.xml.transform.TransformerException
1805 Object val = Keywords.getAxisName(m_token);
1809 error(XPATHErrorResources.ER_ILLEGAL_AXIS_NAME,
1810 new Object[]{ m_token }); //"illegal axis name: "+m_token);
1813 int axesType = ((Integer) val).intValue();
1815 appendOp(2, axesType);
1822 * NodeTest ::= WildcardName
1823 * | NodeType '(' ')'
1824 * | 'processing-instruction' '(' Literal ')'
1826 * @param axesType FROM_XXX axes type, found in {@link org.apache.xpath.compiler.Keywords}.
1828 * @throws javax.xml.transform.TransformerException
1830 protected void NodeTest(int axesType) throws javax.xml.transform.TransformerException
1833 if (lookahead('(', 1))
1835 Object nodeTestOp = Keywords.getNodeType(m_token);
1837 if (null == nodeTestOp)
1839 error(XPATHErrorResources.ER_UNKNOWN_NODETYPE,
1840 new Object[]{ m_token }); //"Unknown nodetype: "+m_token);
1846 int nt = ((Integer) nodeTestOp).intValue();
1848 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), nt);
1849 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1851 consumeExpected('(');
1853 if (OpCodes.NODETYPE_PI == nt)
1861 consumeExpected(')');
1867 // Assume name of attribute or element.
1868 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.NODENAME);
1869 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1871 if (lookahead(':', 1))
1875 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ELEMWILDCARD);
1879 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1881 // Minimalist check for an NCName - just check first character
1882 // to distinguish from other possible tokens
1883 if (!Character.isLetter(m_tokenChar) && !tokenIs('_'))
1885 // "Node test that matches either NCName:* or QName was expected."
1886 error(XPATHErrorResources.ER_EXPECTED_NODE_TEST, null);
1891 consumeExpected(':');
1895 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.EMPTY);
1898 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1902 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ELEMWILDCARD);
1906 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1908 // Minimalist check for an NCName - just check first character
1909 // to distinguish from other possible tokens
1910 if (!Character.isLetter(m_tokenChar) && !tokenIs('_'))
1912 // "Node test that matches either NCName:* or QName was expected."
1913 error(XPATHErrorResources.ER_EXPECTED_NODE_TEST, null);
1917 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1925 * Predicate ::= '[' PredicateExpr ']'
1928 * @throws javax.xml.transform.TransformerException
1930 protected void Predicate() throws javax.xml.transform.TransformerException
1937 consumeExpected(']');
1943 * PredicateExpr ::= Expr
1946 * @throws javax.xml.transform.TransformerException
1948 protected void PredicateExpr() throws javax.xml.transform.TransformerException
1951 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1953 appendOp(2, OpCodes.OP_PREDICATE);
1956 // Terminate for safety.
1957 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1958 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1959 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1960 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1964 * QName ::= (Prefix ':')? LocalPart
1966 * LocalPart ::= NCName
1968 * @throws javax.xml.transform.TransformerException
1970 protected void QName() throws javax.xml.transform.TransformerException
1973 if(lookahead(':', 1))
1975 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1976 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1979 consumeExpected(':');
1983 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.EMPTY);
1984 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1988 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1989 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1995 * NCName ::= (Letter | '_') (NCNameChar)
1996 * NCNameChar ::= Letter | Digit | '.' | '-' | '_' | CombiningChar | Extender
1998 protected void NCName()
2001 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
2002 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
2008 * The value of the Literal is the sequence of characters inside
2009 * the " or ' characters>.
2011 * Literal ::= '"' [^"]* '"'
2015 * @throws javax.xml.transform.TransformerException
2017 protected void Literal() throws javax.xml.transform.TransformerException
2020 int last = m_token.length() - 1;
2021 char c0 = m_tokenChar;
2022 char cX = m_token.charAt(last);
2024 if (((c0 == '\"') && (cX == '\"')) || ((c0 == '\'') && (cX == '\'')))
2027 // Mutate the token to remove the quotes and have the XString object
2029 int tokenQueuePos = m_queueMark - 1;
2031 m_ops.m_tokenQueue.setElementAt(null,tokenQueuePos);
2033 Object obj = new XString(m_token.substring(1, last));
2035 m_ops.m_tokenQueue.setElementAt(obj,tokenQueuePos);
2037 // lit = m_token.substring(1, last);
2038 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), tokenQueuePos);
2039 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
2045 error(XPATHErrorResources.ER_PATTERN_LITERAL_NEEDS_BE_QUOTED,
2046 new Object[]{ m_token }); //"Pattern literal ("+m_token+") needs to be quoted!");
2052 * Number ::= [0-9]+('.'[0-9]+)? | '.'[0-9]+
2055 * @throws javax.xml.transform.TransformerException
2057 protected void Number() throws javax.xml.transform.TransformerException
2060 if (null != m_token)
2063 // Mutate the token to remove the quotes and have the XNumber object
2069 // XPath 1.0 does not support number in exp notation
2070 if ((m_token.indexOf('e') > -1)||(m_token.indexOf('E') > -1))
2071 throw new NumberFormatException();
2072 num = Double.valueOf(m_token).doubleValue();
2074 catch (NumberFormatException nfe)
2076 num = 0.0; // to shut up compiler.
2078 error(XPATHErrorResources.ER_COULDNOT_BE_FORMATTED_TO_NUMBER,
2079 new Object[]{ m_token }); //m_token+" could not be formatted to a number!");
2082 m_ops.m_tokenQueue.setElementAt(new XNumber(num),m_queueMark - 1);
2083 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
2084 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
2090 // ============= PATTERN FUNCTIONS =================
2094 * Pattern ::= LocationPathPattern
2095 * | Pattern '|' LocationPathPattern
2098 * @throws javax.xml.transform.TransformerException
2100 protected void Pattern() throws javax.xml.transform.TransformerException
2105 LocationPathPattern();
2121 * LocationPathPattern ::= '/' RelativePathPattern?
2122 * | IdKeyPattern (('/' | '//') RelativePathPattern)?
2123 * | '//'? RelativePathPattern
2126 * @throws javax.xml.transform.TransformerException
2128 protected void LocationPathPattern() throws javax.xml.transform.TransformerException
2131 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2133 final int RELATIVE_PATH_NOT_PERMITTED = 0;
2134 final int RELATIVE_PATH_PERMITTED = 1;
2135 final int RELATIVE_PATH_REQUIRED = 2;
2137 int relativePathStatus = RELATIVE_PATH_NOT_PERMITTED;
2139 appendOp(2, OpCodes.OP_LOCATIONPATHPATTERN);
2141 if (lookahead('(', 1)
2142 && (tokenIs(Keywords.FUNC_ID_STRING)
2143 || tokenIs(Keywords.FUNC_KEY_STRING)))
2153 appendOp(4, OpCodes.MATCH_ANY_ANCESTOR);
2159 appendOp(4, OpCodes.MATCH_IMMEDIATE_ANCESTOR);
2162 // Tell how long the step is without the predicate
2163 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
2164 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_FUNCTEST);
2166 relativePathStatus = RELATIVE_PATH_REQUIRED;
2169 else if (tokenIs('/'))
2171 if (lookahead('/', 1))
2173 appendOp(4, OpCodes.MATCH_ANY_ANCESTOR);
2175 // Added this to fix bug reported by Myriam for match="//x/a"
2176 // patterns. If you don't do this, the 'x' step will think it's part
2177 // of a '//' pattern, and so will cause 'a' to be matched when it has
2178 // any ancestor that is 'x'.
2181 relativePathStatus = RELATIVE_PATH_REQUIRED;
2185 appendOp(4, OpCodes.FROM_ROOT);
2187 relativePathStatus = RELATIVE_PATH_PERMITTED;
2191 // Tell how long the step is without the predicate
2192 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
2193 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_ROOT);
2199 relativePathStatus = RELATIVE_PATH_REQUIRED;
2202 if (relativePathStatus != RELATIVE_PATH_NOT_PERMITTED)
2204 if (!tokenIs('|') && (null != m_token))
2206 RelativePathPattern();
2208 else if (relativePathStatus == RELATIVE_PATH_REQUIRED)
2210 // "A relative path pattern was expected."
2211 error(XPATHErrorResources.ER_EXPECTED_REL_PATH_PATTERN, null);
2215 // Terminate for safety.
2216 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
2217 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
2218 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
2219 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
2224 * IdKeyPattern ::= 'id' '(' Literal ')'
2225 * | 'key' '(' Literal ',' Literal ')'
2226 * (Also handle doc())
2229 * @throws javax.xml.transform.TransformerException
2231 protected void IdKeyPattern() throws javax.xml.transform.TransformerException
2238 * RelativePathPattern ::= StepPattern
2239 * | RelativePathPattern '/' StepPattern
2240 * | RelativePathPattern '//' StepPattern
2242 * @throws javax.xml.transform.TransformerException
2244 protected void RelativePathPattern()
2245 throws javax.xml.transform.TransformerException
2248 // Caller will have consumed any '/' or '//' preceding the
2249 // RelativePathPattern, so let StepPattern know it can't begin with a '/'
2250 boolean trailingSlashConsumed = StepPattern(false);
2252 while (tokenIs('/'))
2256 // StepPattern() may consume first slash of pair in "a//b" while
2257 // processing StepPattern "a". On next iteration, let StepPattern know
2258 // that happened, so it doesn't match ill-formed patterns like "a///b".
2259 trailingSlashConsumed = StepPattern(!trailingSlashConsumed);
2265 * StepPattern ::= AbbreviatedNodeTestStep
2267 * @param isLeadingSlashPermitted a boolean indicating whether a slash can
2268 * appear at the start of this step
2270 * @return boolean indicating whether a slash following the step was consumed
2272 * @throws javax.xml.transform.TransformerException
2274 protected boolean StepPattern(boolean isLeadingSlashPermitted)
2275 throws javax.xml.transform.TransformerException
2277 return AbbreviatedNodeTestStep(isLeadingSlashPermitted);
2282 * AbbreviatedNodeTestStep ::= '@'? NodeTest Predicate
2284 * @param isLeadingSlashPermitted a boolean indicating whether a slash can
2285 * appear at the start of this step
2287 * @return boolean indicating whether a slash following the step was consumed
2289 * @throws javax.xml.transform.TransformerException
2291 protected boolean AbbreviatedNodeTestStep(boolean isLeadingSlashPermitted)
2292 throws javax.xml.transform.TransformerException
2295 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2298 // The next blocks guarantee that a MATCH_XXX will be added.
2299 int matchTypePos = -1;
2303 axesType = OpCodes.MATCH_ATTRIBUTE;
2305 appendOp(2, axesType);
2308 else if (this.lookahead("::", 1))
2310 if (tokenIs("attribute"))
2312 axesType = OpCodes.MATCH_ATTRIBUTE;
2314 appendOp(2, axesType);
2316 else if (tokenIs("child"))
2318 matchTypePos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2319 axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR;
2321 appendOp(2, axesType);
2327 this.error(XPATHErrorResources.ER_AXES_NOT_ALLOWED,
2328 new Object[]{ this.m_token });
2334 else if (tokenIs('/'))
2336 if (!isLeadingSlashPermitted)
2338 // "A step was expected in the pattern, but '/' was encountered."
2339 error(XPATHErrorResources.ER_EXPECTED_STEP_PATTERN, null);
2341 axesType = OpCodes.MATCH_ANY_ANCESTOR;
2343 appendOp(2, axesType);
2348 matchTypePos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2349 axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR;
2351 appendOp(2, axesType);
2354 // Make room for telling how long the step is without the predicate
2355 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
2359 // Tell how long the step is without the predicate
2360 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
2361 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
2363 while (tokenIs('['))
2368 boolean trailingSlashConsumed;
2370 // For "a//b", where "a" is current step, we need to mark operation of
2371 // current step as "MATCH_ANY_ANCESTOR". Then we'll consume the first
2372 // slash and subsequent step will be treated as a MATCH_IMMEDIATE_ANCESTOR
2373 // (unless it too is followed by '//'.)
2375 // %REVIEW% Following is what happens today, but I'm not sure that's
2376 // %REVIEW% correct behaviour. Perhaps no valid case could be constructed
2377 // %REVIEW% where it would matter?
2379 // If current step is on the attribute axis (e.g., "@x//b"), we won't
2380 // change the current step, and let following step be marked as
2381 // MATCH_ANY_ANCESTOR on next call instead.
2382 if ((matchTypePos > -1) && tokenIs('/') && lookahead('/', 1))
2384 m_ops.setOp(matchTypePos, OpCodes.MATCH_ANY_ANCESTOR);
2388 trailingSlashConsumed = true;
2392 trailingSlashConsumed = false;
2395 // Tell how long the entire step is.
2396 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
2397 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
2399 return trailingSlashConsumed;