1 /*
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
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
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.
17  */
18 /*
19  * $Id: XPathParser.java 468655 2006-10-28 07:12:06Z minchau $
20  */
21 package org.apache.xpath.compiler;
22 
23 import javax.xml.transform.ErrorListener;
24 import javax.xml.transform.TransformerException;
25 
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;
33 
34 /**
35  * Tokenizes and parses XPath expressions. This should really be named
36  * XPathParserImpl, and may be renamed in the future.
37  * @xsl.usage general
38  */
39 public class XPathParser
40 {
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";
45 
46   /**
47    * The XPath to be processed.
48    */
49   private OpMap m_ops;
50 
51   /**
52    * The next token in the pattern.
53    */
54   transient String m_token;
55 
56   /**
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
59    * often.
60    */
61   transient char m_tokenChar = 0;
62 
63   /**
64    * The position in the token queue is tracked by m_queueMark.
65    */
66   int m_queueMark = 0;
67 
68   /**
69    * Results from checking FilterExpr syntax
70    */
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;
74 
75   /**
76    * The parser constructor.
77    */
XPathParser(ErrorListener errorListener, javax.xml.transform.SourceLocator sourceLocator)78   public XPathParser(ErrorListener errorListener, javax.xml.transform.SourceLocator sourceLocator)
79   {
80     m_errorListener = errorListener;
81     m_sourceLocator = sourceLocator;
82   }
83 
84   /**
85    * The prefix resolver to map prefixes to namespaces in the OpMap.
86    */
87   PrefixResolver m_namespaceContext;
88 
89   /**
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.
93    *
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.
98    *
99    * @throws javax.xml.transform.TransformerException
100    */
initXPath( Compiler compiler, String expression, PrefixResolver namespaceContext)101   public void initXPath(
102           Compiler compiler, String expression, PrefixResolver namespaceContext)
103             throws javax.xml.transform.TransformerException
104   {
105 
106     m_ops = compiler;
107     m_namespaceContext = namespaceContext;
108     m_functionTable = compiler.getFunctionTable();
109 
110     Lexer lexer = new Lexer(compiler, namespaceContext, this);
111 
112     lexer.tokenize(expression);
113 
114     m_ops.setOp(0,OpCodes.OP_XPATH);
115     m_ops.setOp(OpMap.MAPINDEX_LENGTH,2);
116 
117 
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.
121 	//
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?)
126 	try {
127 
128       nextToken();
129       Expr();
130 
131       if (null != m_token)
132       {
133         String extraTokens = "";
134 
135         while (null != m_token)
136         {
137           extraTokens += "'" + m_token + "'";
138 
139           nextToken();
140 
141           if (null != m_token)
142             extraTokens += ", ";
143         }
144 
145         error(XPATHErrorResources.ER_EXTRA_ILLEGAL_TOKENS,
146               new Object[]{ extraTokens });  //"Extra illegal tokens: "+extraTokens);
147       }
148 
149     }
150     catch (org.apache.xpath.XPathProcessorException e)
151     {
152 	  if(CONTINUE_AFTER_FATAL_ERROR.equals(e.getMessage()))
153 	  {
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.
156 		// %REVIEW%!!!
157 		initXPath(compiler, "/..",  namespaceContext);
158 	  }
159 	  else
160 		throw e;
161     }
162 
163     compiler.shrink();
164   }
165 
166   /**
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.
174    *
175    * @throws javax.xml.transform.TransformerException
176    */
initMatchPattern( Compiler compiler, String expression, PrefixResolver namespaceContext)177   public void initMatchPattern(
178           Compiler compiler, String expression, PrefixResolver namespaceContext)
179             throws javax.xml.transform.TransformerException
180   {
181 
182     m_ops = compiler;
183     m_namespaceContext = namespaceContext;
184     m_functionTable = compiler.getFunctionTable();
185 
186     Lexer lexer = new Lexer(compiler, namespaceContext, this);
187 
188     lexer.tokenize(expression);
189 
190     m_ops.setOp(0, OpCodes.OP_MATCHPATTERN);
191     m_ops.setOp(OpMap.MAPINDEX_LENGTH, 2);
192 
193     nextToken();
194     Pattern();
195 
196     if (null != m_token)
197     {
198       String extraTokens = "";
199 
200       while (null != m_token)
201       {
202         extraTokens += "'" + m_token + "'";
203 
204         nextToken();
205 
206         if (null != m_token)
207           extraTokens += ", ";
208       }
209 
210       error(XPATHErrorResources.ER_EXTRA_ILLEGAL_TOKENS,
211             new Object[]{ extraTokens });  //"Extra illegal tokens: "+extraTokens);
212     }
213 
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);
217 
218     m_ops.shrink();
219   }
220 
221   /** The error listener where syntax errors are to be sent.
222    */
223   private ErrorListener m_errorListener;
224 
225   /** The source location of the XPath. */
226   javax.xml.transform.SourceLocator m_sourceLocator;
227 
228   /** The table contains build-in functions and customized functions */
229   private FunctionTable m_functionTable;
230 
231   /**
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.
235    *
236    * @param handler Reference to error listener where syntax errors will be
237    *                sent.
238    */
setErrorHandler(ErrorListener handler)239   public void setErrorHandler(ErrorListener handler)
240   {
241     m_errorListener = handler;
242   }
243 
244   /**
245    * Return the current error listener.
246    *
247    * @return The error listener, which should not normally be null, but may be.
248    */
getErrorListener()249   public ErrorListener getErrorListener()
250   {
251     return m_errorListener;
252   }
253 
254   /**
255    * Check whether m_token matches the target string.
256    *
257    * @param s A string reference or null.
258    *
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.
261    */
tokenIs(String s)262   final boolean tokenIs(String s)
263   {
264     return (m_token != null) ? (m_token.equals(s)) : (s == null);
265   }
266 
267   /**
268    * Check whether m_tokenChar==c.
269    *
270    * @param c A character to be tested.
271    *
272    * @return If m_token is null, returns false, or return true if c matches
273    *         the current token.
274    */
tokenIs(char c)275   final boolean tokenIs(char c)
276   {
277     return (m_token != null) ? (m_tokenChar == c) : false;
278   }
279 
280   /**
281    * Look ahead of the current token in order to
282    * make a branching decision.
283    *
284    * @param c the character to be tested for.
285    * @param n number of tokens to look ahead.  Must be
286    * greater than 1.
287    *
288    * @return true if the next token matches the character argument.
289    */
lookahead(char c, int n)290   final boolean lookahead(char c, int n)
291   {
292 
293     int pos = (m_queueMark + n);
294     boolean b;
295 
296     if ((pos <= m_ops.getTokenQueueSize()) && (pos > 0)
297             && (m_ops.getTokenQueueSize() != 0))
298     {
299       String tok = ((String) m_ops.m_tokenQueue.elementAt(pos - 1));
300 
301       b = (tok.length() == 1) ? (tok.charAt(0) == c) : false;
302     }
303     else
304     {
305       b = false;
306     }
307 
308     return b;
309   }
310 
311   /**
312    * Look behind the first character of the current token in order to
313    * make a branching decision.
314    *
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.
321    *
322    * @return true if the token behind the current token matches the character
323    *         argument.
324    */
lookbehind(char c, int n)325   private final boolean lookbehind(char c, int n)
326   {
327 
328     boolean isToken;
329     int lookBehindPos = m_queueMark - (n + 1);
330 
331     if (lookBehindPos >= 0)
332     {
333       String lookbehind = (String) m_ops.m_tokenQueue.elementAt(lookBehindPos);
334 
335       if (lookbehind.length() == 1)
336       {
337         char c0 = (lookbehind == null) ? '|' : lookbehind.charAt(0);
338 
339         isToken = (c0 == '|') ? false : (c0 == c);
340       }
341       else
342       {
343         isToken = false;
344       }
345     }
346     else
347     {
348       isToken = false;
349     }
350 
351     return isToken;
352   }
353 
354   /**
355    * look behind the current token in order to
356    * see if there is a useable token.
357    *
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.
363    *
364    * @return true if look behind has a token, false otherwise.
365    */
lookbehindHasToken(int n)366   private final boolean lookbehindHasToken(int n)
367   {
368 
369     boolean hasToken;
370 
371     if ((m_queueMark - n) > 0)
372     {
373       String lookbehind = (String) m_ops.m_tokenQueue.elementAt(m_queueMark - (n - 1));
374       char c0 = (lookbehind == null) ? '|' : lookbehind.charAt(0);
375 
376       hasToken = (c0 == '|') ? false : true;
377     }
378     else
379     {
380       hasToken = false;
381     }
382 
383     return hasToken;
384   }
385 
386   /**
387    * Look ahead of the current token in order to
388    * make a branching decision.
389    *
390    * @param s the string to compare it to.
391    * @param n number of tokens to lookahead.  Must be
392    * greater than 1.
393    *
394    * @return true if the token behind the current token matches the string
395    *         argument.
396    */
lookahead(String s, int n)397   private final boolean lookahead(String s, int n)
398   {
399 
400     boolean isToken;
401 
402     if ((m_queueMark + n) <= m_ops.getTokenQueueSize())
403     {
404       String lookahead = (String) m_ops.m_tokenQueue.elementAt(m_queueMark + (n - 1));
405 
406       isToken = (lookahead != null) ? lookahead.equals(s) : (s == null);
407     }
408     else
409     {
410       isToken = (null == s);
411     }
412 
413     return isToken;
414   }
415 
416   /**
417    * Retrieve the next token from the command and
418    * store it in m_token string.
419    */
nextToken()420   private final void nextToken()
421   {
422 
423     if (m_queueMark < m_ops.getTokenQueueSize())
424     {
425       m_token = (String) m_ops.m_tokenQueue.elementAt(m_queueMark++);
426       m_tokenChar = m_token.charAt(0);
427     }
428     else
429     {
430       m_token = null;
431       m_tokenChar = 0;
432     }
433   }
434 
435   /**
436    * Retrieve a token relative to the current token.
437    *
438    * @param i Position relative to current token.
439    *
440    * @return The string at the given index, or null if the index is out
441    *         of range.
442    */
getTokenRelative(int i)443   private final String getTokenRelative(int i)
444   {
445 
446     String tok;
447     int relative = m_queueMark + i;
448 
449     if ((relative > 0) && (relative < m_ops.getTokenQueueSize()))
450     {
451       tok = (String) m_ops.m_tokenQueue.elementAt(relative);
452     }
453     else
454     {
455       tok = null;
456     }
457 
458     return tok;
459   }
460 
461   /**
462    * Retrieve the previous token from the command and
463    * store it in m_token string.
464    */
prevToken()465   private final void prevToken()
466   {
467 
468     if (m_queueMark > 0)
469     {
470       m_queueMark--;
471 
472       m_token = (String) m_ops.m_tokenQueue.elementAt(m_queueMark);
473       m_tokenChar = m_token.charAt(0);
474     }
475     else
476     {
477       m_token = null;
478       m_tokenChar = 0;
479     }
480   }
481 
482   /**
483    * Consume an expected token, throwing an exception if it
484    * isn't there.
485    *
486    * @param expected The string to be expected.
487    *
488    * @throws javax.xml.transform.TransformerException
489    */
consumeExpected(String expected)490   private final void consumeExpected(String expected)
491           throws javax.xml.transform.TransformerException
492   {
493 
494     if (tokenIs(expected))
495     {
496       nextToken();
497     }
498     else
499     {
500       error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND, new Object[]{ expected,
501                                                                      m_token });  //"Expected "+expected+", but found: "+m_token);
502 
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);
507 	}
508   }
509 
510   /**
511    * Consume an expected token, throwing an exception if it
512    * isn't there.
513    *
514    * @param expected the character to be expected.
515    *
516    * @throws javax.xml.transform.TransformerException
517    */
consumeExpected(char expected)518   private final void consumeExpected(char expected)
519           throws javax.xml.transform.TransformerException
520   {
521 
522     if (tokenIs(expected))
523     {
524       nextToken();
525     }
526     else
527     {
528       error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND,
529             new Object[]{ String.valueOf(expected),
530                           m_token });  //"Expected "+expected+", but found: "+m_token);
531 
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);
536     }
537   }
538 
539   /**
540    * Warn the user of a problem.
541    *
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
546    *             may be null.
547    *
548    * @throws TransformerException if the current ErrorListoner determines to
549    *                              throw an exception.
550    */
warn(String msg, Object[] args)551   void warn(String msg, Object[] args) throws TransformerException
552   {
553 
554     String fmsg = XSLMessages.createXPATHWarning(msg, args);
555     ErrorListener ehandler = this.getErrorListener();
556 
557     if (null != ehandler)
558     {
559       // TO DO: Need to get stylesheet Locator from here.
560       ehandler.warning(new TransformerException(fmsg, m_sourceLocator));
561     }
562     else
563     {
564       // Should never happen.
565       System.err.println(fmsg);
566     }
567   }
568 
569   /**
570    * Notify the user of an assertion error, and probably throw an
571    * exception.
572    *
573    * @param b  If false, a runtime exception will be thrown.
574    * @param msg The assertion message, which should be informative.
575    *
576    * @throws RuntimeException if the b argument is false.
577    */
assertion(boolean b, String msg)578   private void assertion(boolean b, String msg)
579   {
580 
581     if (!b)
582     {
583       String fMsg = XSLMessages.createXPATHMessage(
584         XPATHErrorResources.ER_INCORRECT_PROGRAMMER_ASSERTION,
585         new Object[]{ msg });
586 
587       throw new RuntimeException(fMsg);
588     }
589   }
590 
591   /**
592    * Notify the user of an error, and probably throw an
593    * exception.
594    *
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
599    *             may be null.
600    *
601    * @throws TransformerException if the current ErrorListoner determines to
602    *                              throw an exception.
603    */
error(String msg, Object[] args)604   void error(String msg, Object[] args) throws TransformerException
605   {
606 
607     String fmsg = XSLMessages.createXPATHMessage(msg, args);
608     ErrorListener ehandler = this.getErrorListener();
609 
610     TransformerException te = new TransformerException(fmsg, m_sourceLocator);
611     if (null != ehandler)
612     {
613       // TO DO: Need to get stylesheet Locator from here.
614       ehandler.fatalError(te);
615     }
616     else
617     {
618       // System.err.println(fmsg);
619       throw te;
620     }
621   }
622 
623   /**
624    * This method is added to support DOM 3 XPath API.
625    * <p>
626    * This method is exactly like error(String, Object[]); except that
627    * the underlying TransformerException is
628    * XpathStylesheetDOM3Exception (which extends TransformerException).
629    * <p>
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
634    * runs as before.
635    * <p>
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.
639    *
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
644    *             may be null.
645    *
646    * @throws TransformerException if the current ErrorListoner determines to
647    *                              throw an exception.
648    */
errorForDOM3(String msg, Object[] args)649   void errorForDOM3(String msg, Object[] args) throws TransformerException
650   {
651 
652 	String fmsg = XSLMessages.createXPATHMessage(msg, args);
653 	ErrorListener ehandler = this.getErrorListener();
654 
655 	TransformerException te = new XPathStylesheetDOM3Exception(fmsg, m_sourceLocator);
656 	if (null != ehandler)
657 	{
658 	  // TO DO: Need to get stylesheet Locator from here.
659 	  ehandler.fatalError(te);
660 	}
661 	else
662 	{
663 	  // System.err.println(fmsg);
664 	  throw te;
665 	}
666   }
667   /**
668    * Dump the remaining token queue.
669    * Thanks to Craig for this.
670    *
671    * @return A dump of the remaining token queue, which may be appended to
672    *         an error message.
673    */
dumpRemainingTokenQueue()674   protected String dumpRemainingTokenQueue()
675   {
676 
677     int q = m_queueMark;
678     String returnMsg;
679 
680     if (q < m_ops.getTokenQueueSize())
681     {
682       String msg = "\n Remaining tokens: (";
683 
684       while (q < m_ops.getTokenQueueSize())
685       {
686         String t = (String) m_ops.m_tokenQueue.elementAt(q++);
687 
688         msg += (" '" + t + "'");
689       }
690 
691       returnMsg = msg + ")";
692     }
693     else
694     {
695       returnMsg = "";
696     }
697 
698     return returnMsg;
699   }
700 
701   /**
702    * Given a string, return the corresponding function token.
703    *
704    * @param key A local name of a function.
705    *
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.
709    */
getFunctionToken(String key)710   final int getFunctionToken(String key)
711   {
712 
713     int tok;
714 
715     Object id;
716 
717     try
718     {
719       // These are nodetests, xpathparser treats them as functions when parsing
720       // a FilterExpr.
721       id = Keywords.lookupNodeTest(key);
722       if (null == id) id = m_functionTable.getFunctionID(key);
723       tok = ((Integer) id).intValue();
724     }
725     catch (NullPointerException npe)
726     {
727       tok = -1;
728     }
729     catch (ClassCastException cce)
730     {
731       tok = -1;
732     }
733 
734     return tok;
735   }
736 
737   /**
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.
741    *
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.
745    */
insertOp(int pos, int length, int op)746   void insertOp(int pos, int length, int op)
747   {
748 
749     int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
750 
751     for (int i = totalLen - 1; i >= pos; i--)
752     {
753       m_ops.setOp(i + length, m_ops.getOp(i));
754     }
755 
756     m_ops.setOp(pos,op);
757     m_ops.setOp(OpMap.MAPINDEX_LENGTH,totalLen + length);
758   }
759 
760   /**
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.
764    *
765    * @param length The length of the operation.
766    * @param op The op code to the inserted.
767    */
appendOp(int length, int op)768   void appendOp(int length, int op)
769   {
770 
771     int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
772 
773     m_ops.setOp(totalLen, op);
774     m_ops.setOp(totalLen + OpMap.MAPINDEX_LENGTH, length);
775     m_ops.setOp(OpMap.MAPINDEX_LENGTH, totalLen + length);
776   }
777 
778   // ============= EXPRESSIONS FUNCTIONS =================
779 
780   /**
781    *
782    *
783    * Expr  ::=  OrExpr
784    *
785    *
786    * @throws javax.xml.transform.TransformerException
787    */
Expr()788   protected void Expr() throws javax.xml.transform.TransformerException
789   {
790     OrExpr();
791   }
792 
793   /**
794    *
795    *
796    * OrExpr  ::=  AndExpr
797    * | OrExpr 'or' AndExpr
798    *
799    *
800    * @throws javax.xml.transform.TransformerException
801    */
OrExpr()802   protected void OrExpr() throws javax.xml.transform.TransformerException
803   {
804 
805     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
806 
807     AndExpr();
808 
809     if ((null != m_token) && tokenIs("or"))
810     {
811       nextToken();
812       insertOp(opPos, 2, OpCodes.OP_OR);
813       OrExpr();
814 
815       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
816         m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
817     }
818   }
819 
820   /**
821    *
822    *
823    * AndExpr  ::=  EqualityExpr
824    * | AndExpr 'and' EqualityExpr
825    *
826    *
827    * @throws javax.xml.transform.TransformerException
828    */
AndExpr()829   protected void AndExpr() throws javax.xml.transform.TransformerException
830   {
831 
832     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
833 
834     EqualityExpr(-1);
835 
836     if ((null != m_token) && tokenIs("and"))
837     {
838       nextToken();
839       insertOp(opPos, 2, OpCodes.OP_AND);
840       AndExpr();
841 
842       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
843         m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
844     }
845   }
846 
847   /**
848    *
849    * @returns an Object which is either a String, a Number, a Boolean, or a vector
850    * of nodes.
851    *
852    * EqualityExpr  ::=  RelationalExpr
853    * | EqualityExpr '=' RelationalExpr
854    *
855    *
856    * @param addPos Position where expression is to be added, or -1 for append.
857    *
858    * @return the position at the end of the equality expression.
859    *
860    * @throws javax.xml.transform.TransformerException
861    */
EqualityExpr(int addPos)862   protected int EqualityExpr(int addPos) throws javax.xml.transform.TransformerException
863   {
864 
865     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
866 
867     if (-1 == addPos)
868       addPos = opPos;
869 
870     RelationalExpr(-1);
871 
872     if (null != m_token)
873     {
874       if (tokenIs('!') && lookahead('=', 1))
875       {
876         nextToken();
877         nextToken();
878         insertOp(addPos, 2, OpCodes.OP_NOTEQUALS);
879 
880         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
881 
882         addPos = EqualityExpr(addPos);
883         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
884           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
885         addPos += 2;
886       }
887       else if (tokenIs('='))
888       {
889         nextToken();
890         insertOp(addPos, 2, OpCodes.OP_EQUALS);
891 
892         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
893 
894         addPos = EqualityExpr(addPos);
895         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
896           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
897         addPos += 2;
898       }
899     }
900 
901     return addPos;
902   }
903 
904   /**
905    * .
906    * @returns an Object which is either a String, a Number, a Boolean, or a vector
907    * of nodes.
908    *
909    * RelationalExpr  ::=  AdditiveExpr
910    * | RelationalExpr '<' AdditiveExpr
911    * | RelationalExpr '>' AdditiveExpr
912    * | RelationalExpr '<=' AdditiveExpr
913    * | RelationalExpr '>=' AdditiveExpr
914    *
915    *
916    * @param addPos Position where expression is to be added, or -1 for append.
917    *
918    * @return the position at the end of the relational expression.
919    *
920    * @throws javax.xml.transform.TransformerException
921    */
RelationalExpr(int addPos)922   protected int RelationalExpr(int addPos) throws javax.xml.transform.TransformerException
923   {
924 
925     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
926 
927     if (-1 == addPos)
928       addPos = opPos;
929 
930     AdditiveExpr(-1);
931 
932     if (null != m_token)
933     {
934       if (tokenIs('<'))
935       {
936         nextToken();
937 
938         if (tokenIs('='))
939         {
940           nextToken();
941           insertOp(addPos, 2, OpCodes.OP_LTE);
942         }
943         else
944         {
945           insertOp(addPos, 2, OpCodes.OP_LT);
946         }
947 
948         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
949 
950         addPos = RelationalExpr(addPos);
951         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
952           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
953         addPos += 2;
954       }
955       else if (tokenIs('>'))
956       {
957         nextToken();
958 
959         if (tokenIs('='))
960         {
961           nextToken();
962           insertOp(addPos, 2, OpCodes.OP_GTE);
963         }
964         else
965         {
966           insertOp(addPos, 2, OpCodes.OP_GT);
967         }
968 
969         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
970 
971         addPos = RelationalExpr(addPos);
972         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
973           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
974         addPos += 2;
975       }
976     }
977 
978     return addPos;
979   }
980 
981   /**
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|.
985    *
986    * AdditiveExpr  ::=  MultiplicativeExpr
987    * | AdditiveExpr '+' MultiplicativeExpr
988    * | AdditiveExpr '-' MultiplicativeExpr
989    *
990    *
991    * @param addPos Position where expression is to be added, or -1 for append.
992    *
993    * @return the position at the end of the equality expression.
994    *
995    * @throws javax.xml.transform.TransformerException
996    */
AdditiveExpr(int addPos)997   protected int AdditiveExpr(int addPos) throws javax.xml.transform.TransformerException
998   {
999 
1000     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1001 
1002     if (-1 == addPos)
1003       addPos = opPos;
1004 
1005     MultiplicativeExpr(-1);
1006 
1007     if (null != m_token)
1008     {
1009       if (tokenIs('+'))
1010       {
1011         nextToken();
1012         insertOp(addPos, 2, OpCodes.OP_PLUS);
1013 
1014         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1015 
1016         addPos = AdditiveExpr(addPos);
1017         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1018           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1019         addPos += 2;
1020       }
1021       else if (tokenIs('-'))
1022       {
1023         nextToken();
1024         insertOp(addPos, 2, OpCodes.OP_MINUS);
1025 
1026         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1027 
1028         addPos = AdditiveExpr(addPos);
1029         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1030           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1031         addPos += 2;
1032       }
1033     }
1034 
1035     return addPos;
1036   }
1037 
1038   /**
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|.
1042    *
1043    * MultiplicativeExpr  ::=  UnaryExpr
1044    * | MultiplicativeExpr MultiplyOperator UnaryExpr
1045    * | MultiplicativeExpr 'div' UnaryExpr
1046    * | MultiplicativeExpr 'mod' UnaryExpr
1047    * | MultiplicativeExpr 'quo' UnaryExpr
1048    *
1049    * @param addPos Position where expression is to be added, or -1 for append.
1050    *
1051    * @return the position at the end of the equality expression.
1052    *
1053    * @throws javax.xml.transform.TransformerException
1054    */
MultiplicativeExpr(int addPos)1055   protected int MultiplicativeExpr(int addPos) throws javax.xml.transform.TransformerException
1056   {
1057 
1058     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1059 
1060     if (-1 == addPos)
1061       addPos = opPos;
1062 
1063     UnaryExpr();
1064 
1065     if (null != m_token)
1066     {
1067       if (tokenIs('*'))
1068       {
1069         nextToken();
1070         insertOp(addPos, 2, OpCodes.OP_MULT);
1071 
1072         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1073 
1074         addPos = MultiplicativeExpr(addPos);
1075         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1076           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1077         addPos += 2;
1078       }
1079       else if (tokenIs("div"))
1080       {
1081         nextToken();
1082         insertOp(addPos, 2, OpCodes.OP_DIV);
1083 
1084         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1085 
1086         addPos = MultiplicativeExpr(addPos);
1087         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1088           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1089         addPos += 2;
1090       }
1091       else if (tokenIs("mod"))
1092       {
1093         nextToken();
1094         insertOp(addPos, 2, OpCodes.OP_MOD);
1095 
1096         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1097 
1098         addPos = MultiplicativeExpr(addPos);
1099         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1100           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1101         addPos += 2;
1102       }
1103       else if (tokenIs("quo"))
1104       {
1105         nextToken();
1106         insertOp(addPos, 2, OpCodes.OP_QUO);
1107 
1108         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1109 
1110         addPos = MultiplicativeExpr(addPos);
1111         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1112           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1113         addPos += 2;
1114       }
1115     }
1116 
1117     return addPos;
1118   }
1119 
1120   /**
1121    *
1122    * UnaryExpr  ::=  UnionExpr
1123    * | '-' UnaryExpr
1124    *
1125    *
1126    * @throws javax.xml.transform.TransformerException
1127    */
UnaryExpr()1128   protected void UnaryExpr() throws javax.xml.transform.TransformerException
1129   {
1130 
1131     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1132     boolean isNeg = false;
1133 
1134     if (m_tokenChar == '-')
1135     {
1136       nextToken();
1137       appendOp(2, OpCodes.OP_NEG);
1138 
1139       isNeg = true;
1140     }
1141 
1142     UnionExpr();
1143 
1144     if (isNeg)
1145       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1146         m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1147   }
1148 
1149   /**
1150    *
1151    * StringExpr  ::=  Expr
1152    *
1153    *
1154    * @throws javax.xml.transform.TransformerException
1155    */
StringExpr()1156   protected void StringExpr() throws javax.xml.transform.TransformerException
1157   {
1158 
1159     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1160 
1161     appendOp(2, OpCodes.OP_STRING);
1162     Expr();
1163 
1164     m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1165       m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1166   }
1167 
1168   /**
1169    *
1170    *
1171    * StringExpr  ::=  Expr
1172    *
1173    *
1174    * @throws javax.xml.transform.TransformerException
1175    */
BooleanExpr()1176   protected void BooleanExpr() throws javax.xml.transform.TransformerException
1177   {
1178 
1179     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1180 
1181     appendOp(2, OpCodes.OP_BOOL);
1182     Expr();
1183 
1184     int opLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos;
1185 
1186     if (opLen == 2)
1187     {
1188       error(XPATHErrorResources.ER_BOOLEAN_ARG_NO_LONGER_OPTIONAL, null);  //"boolean(...) argument is no longer optional with 19990709 XPath draft.");
1189     }
1190 
1191     m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, opLen);
1192   }
1193 
1194   /**
1195    *
1196    *
1197    * NumberExpr  ::=  Expr
1198    *
1199    *
1200    * @throws javax.xml.transform.TransformerException
1201    */
NumberExpr()1202   protected void NumberExpr() throws javax.xml.transform.TransformerException
1203   {
1204 
1205     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1206 
1207     appendOp(2, OpCodes.OP_NUMBER);
1208     Expr();
1209 
1210     m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1211       m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1212   }
1213 
1214   /**
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.
1219    *
1220    *
1221    * UnionExpr    ::=    PathExpr
1222    * | UnionExpr '|' PathExpr
1223    *
1224    *
1225    * @throws javax.xml.transform.TransformerException
1226    */
UnionExpr()1227   protected void UnionExpr() throws javax.xml.transform.TransformerException
1228   {
1229 
1230     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1231     boolean continueOrLoop = true;
1232     boolean foundUnion = false;
1233 
1234     do
1235     {
1236       PathExpr();
1237 
1238       if (tokenIs('|'))
1239       {
1240         if (false == foundUnion)
1241         {
1242           foundUnion = true;
1243 
1244           insertOp(opPos, 2, OpCodes.OP_UNION);
1245         }
1246 
1247         nextToken();
1248       }
1249       else
1250       {
1251         break;
1252       }
1253 
1254       // this.m_testForDocOrder = true;
1255     }
1256     while (continueOrLoop);
1257 
1258     m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1259           m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1260   }
1261 
1262   /**
1263    * PathExpr  ::=  LocationPath
1264    * | FilterExpr
1265    * | FilterExpr '/' RelativeLocationPath
1266    * | FilterExpr '//' RelativeLocationPath
1267    *
1268    * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
1269    * the error condition is severe enough to halt processing.
1270    *
1271    * @throws javax.xml.transform.TransformerException
1272    */
PathExpr()1273   protected void PathExpr() throws javax.xml.transform.TransformerException
1274   {
1275 
1276     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1277 
1278     int filterExprMatch = FilterExpr();
1279 
1280     if (filterExprMatch != FILTER_MATCH_FAILED)
1281     {
1282       // If FilterExpr had Predicates, a OP_LOCATIONPATH opcode would already
1283       // have been inserted.
1284       boolean locationPathStarted = (filterExprMatch==FILTER_MATCH_PREDICATES);
1285 
1286       if (tokenIs('/'))
1287       {
1288         nextToken();
1289 
1290         if (!locationPathStarted)
1291         {
1292           // int locationPathOpPos = opPos;
1293           insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH);
1294 
1295           locationPathStarted = true;
1296         }
1297 
1298         if (!RelativeLocationPath())
1299         {
1300           // "Relative location path expected following '/' or '//'"
1301           error(XPATHErrorResources.ER_EXPECTED_REL_LOC_PATH, null);
1302         }
1303 
1304       }
1305 
1306       // Terminate for safety.
1307       if (locationPathStarted)
1308       {
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);
1313       }
1314     }
1315     else
1316     {
1317       LocationPath();
1318     }
1319   }
1320 
1321   /**
1322    *
1323    *
1324    * FilterExpr  ::=  PrimaryExpr
1325    * | FilterExpr Predicate
1326    *
1327    * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
1328    * the error condition is severe enough to halt processing.
1329    *
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
1335    *
1336    * @throws javax.xml.transform.TransformerException
1337    */
FilterExpr()1338   protected int FilterExpr() throws javax.xml.transform.TransformerException
1339   {
1340 
1341     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1342 
1343     int filterMatch;
1344 
1345     if (PrimaryExpr())
1346     {
1347       if (tokenIs('['))
1348       {
1349 
1350         // int locationPathOpPos = opPos;
1351         insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH);
1352 
1353         while (tokenIs('['))
1354         {
1355           Predicate();
1356         }
1357 
1358         filterMatch = FILTER_MATCH_PREDICATES;
1359       }
1360       else
1361       {
1362         filterMatch = FILTER_MATCH_PRIMARY;
1363       }
1364     }
1365     else
1366     {
1367       filterMatch = FILTER_MATCH_FAILED;
1368     }
1369 
1370     return filterMatch;
1371 
1372     /*
1373      * if(tokenIs('['))
1374      * {
1375      *   Predicate();
1376      *   m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos;
1377      * }
1378      */
1379   }
1380 
1381   /**
1382    *
1383    * PrimaryExpr  ::=  VariableReference
1384    * | '(' Expr ')'
1385    * | Literal
1386    * | Number
1387    * | FunctionCall
1388    *
1389    * @return true if this method successfully matched a PrimaryExpr
1390    *
1391    * @throws javax.xml.transform.TransformerException
1392    *
1393    */
PrimaryExpr()1394   protected boolean PrimaryExpr() throws javax.xml.transform.TransformerException
1395   {
1396 
1397     boolean matchFound;
1398     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1399 
1400     if ((m_tokenChar == '\'') || (m_tokenChar == '"'))
1401     {
1402       appendOp(2, OpCodes.OP_LITERAL);
1403       Literal();
1404 
1405       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1406         m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1407 
1408       matchFound = true;
1409     }
1410     else if (m_tokenChar == '$')
1411     {
1412       nextToken();  // consume '$'
1413       appendOp(2, OpCodes.OP_VARIABLE);
1414       QName();
1415 
1416       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1417         m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1418 
1419       matchFound = true;
1420     }
1421     else if (m_tokenChar == '(')
1422     {
1423       nextToken();
1424       appendOp(2, OpCodes.OP_GROUP);
1425       Expr();
1426       consumeExpected(')');
1427 
1428       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1429         m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1430 
1431       matchFound = true;
1432     }
1433     else if ((null != m_token) && ((('.' == m_tokenChar) && (m_token.length() > 1) && Character.isDigit(
1434             m_token.charAt(1))) || Character.isDigit(m_tokenChar)))
1435     {
1436       appendOp(2, OpCodes.OP_NUMBERLIT);
1437       Number();
1438 
1439       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1440         m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1441 
1442       matchFound = true;
1443     }
1444     else if (lookahead('(', 1) || (lookahead(':', 1) && lookahead('(', 3)))
1445     {
1446       matchFound = FunctionCall();
1447     }
1448     else
1449     {
1450       matchFound = false;
1451     }
1452 
1453     return matchFound;
1454   }
1455 
1456   /**
1457    *
1458    * Argument    ::=    Expr
1459    *
1460    *
1461    * @throws javax.xml.transform.TransformerException
1462    */
Argument()1463   protected void Argument() throws javax.xml.transform.TransformerException
1464   {
1465 
1466     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1467 
1468     appendOp(2, OpCodes.OP_ARGUMENT);
1469     Expr();
1470 
1471     m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1472       m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1473   }
1474 
1475   /**
1476    *
1477    * FunctionCall    ::=    FunctionName '(' ( Argument ( ',' Argument)*)? ')'
1478    *
1479    * @return true if, and only if, a FunctionCall was matched
1480    *
1481    * @throws javax.xml.transform.TransformerException
1482    */
FunctionCall()1483   protected boolean FunctionCall() throws javax.xml.transform.TransformerException
1484   {
1485 
1486     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1487 
1488     if (lookahead(':', 1))
1489     {
1490       appendOp(4, OpCodes.OP_EXTFUNCTION);
1491 
1492       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, m_queueMark - 1);
1493 
1494       nextToken();
1495       consumeExpected(':');
1496 
1497       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 2, m_queueMark - 1);
1498 
1499       nextToken();
1500     }
1501     else
1502     {
1503       int funcTok = getFunctionToken(m_token);
1504 
1505       if (-1 == funcTok)
1506       {
1507         error(XPATHErrorResources.ER_COULDNOT_FIND_FUNCTION,
1508               new Object[]{ m_token });  //"Could not find function: "+m_token+"()");
1509       }
1510 
1511       switch (funcTok)
1512       {
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
1518         return false;
1519       default :
1520         appendOp(3, OpCodes.OP_FUNCTION);
1521 
1522         m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, funcTok);
1523       }
1524 
1525       nextToken();
1526     }
1527 
1528     consumeExpected('(');
1529 
1530     while (!tokenIs(')') && m_token != null)
1531     {
1532       if (tokenIs(','))
1533       {
1534         error(XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_PRECEDING_ARG, null);  //"Found ',' but no preceding argument!");
1535       }
1536 
1537       Argument();
1538 
1539       if (!tokenIs(')'))
1540       {
1541         consumeExpected(',');
1542 
1543         if (tokenIs(')'))
1544         {
1545           error(XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_FOLLOWING_ARG,
1546                 null);  //"Found ',' but no following argument!");
1547         }
1548       }
1549     }
1550 
1551     consumeExpected(')');
1552 
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);
1558 
1559     return true;
1560   }
1561 
1562   // ============= GRAMMAR FUNCTIONS =================
1563 
1564   /**
1565    *
1566    * LocationPath ::= RelativeLocationPath
1567    * | AbsoluteLocationPath
1568    *
1569    *
1570    * @throws javax.xml.transform.TransformerException
1571    */
LocationPath()1572   protected void LocationPath() throws javax.xml.transform.TransformerException
1573   {
1574 
1575     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1576 
1577     // int locationPathOpPos = opPos;
1578     appendOp(2, OpCodes.OP_LOCATIONPATH);
1579 
1580     boolean seenSlash = tokenIs('/');
1581 
1582     if (seenSlash)
1583     {
1584       appendOp(4, OpCodes.FROM_ROOT);
1585 
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);
1589 
1590       nextToken();
1591     } else if (m_token == null) {
1592       error(XPATHErrorResources.ER_EXPECTED_LOC_PATH_AT_END_EXPR, null);
1593     }
1594 
1595     if (m_token != null)
1596     {
1597       if (!RelativeLocationPath() && !seenSlash)
1598       {
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});
1603       }
1604     }
1605 
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);
1611   }
1612 
1613   /**
1614    *
1615    * RelativeLocationPath ::= Step
1616    * | RelativeLocationPath '/' Step
1617    * | AbbreviatedRelativeLocationPath
1618    *
1619    * @returns true if, and only if, a RelativeLocationPath was matched
1620    *
1621    * @throws javax.xml.transform.TransformerException
1622    */
RelativeLocationPath()1623   protected boolean RelativeLocationPath()
1624                throws javax.xml.transform.TransformerException
1625   {
1626     if (!Step())
1627     {
1628       return false;
1629     }
1630 
1631     while (tokenIs('/'))
1632     {
1633       nextToken();
1634 
1635       if (!Step())
1636       {
1637         // RelativeLocationPath can't end with a trailing '/'
1638         // "Location step expected following '/' or '//'"
1639         error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null);
1640       }
1641     }
1642 
1643     return true;
1644   }
1645 
1646   /**
1647    *
1648    * Step    ::=    Basis Predicate
1649    * | AbbreviatedStep
1650    *
1651    * @returns false if step was empty (or only a '/'); true, otherwise
1652    *
1653    * @throws javax.xml.transform.TransformerException
1654    */
Step()1655   protected boolean Step() throws javax.xml.transform.TransformerException
1656   {
1657     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1658 
1659     boolean doubleSlash = tokenIs('/');
1660 
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
1663     // be empty.
1664     if (doubleSlash)
1665     {
1666       nextToken();
1667 
1668       appendOp(2, OpCodes.FROM_DESCENDANTS_OR_SELF);
1669 
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.
1674 
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);
1679 
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);
1683 
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);
1687 
1688       opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1689     }
1690 
1691     if (tokenIs("."))
1692     {
1693       nextToken();
1694 
1695       if (tokenIs('['))
1696       {
1697         error(XPATHErrorResources.ER_PREDICATE_ILLEGAL_SYNTAX, null);  //"'..[predicate]' or '.[predicate]' is illegal syntax.  Use 'self::node()[predicate]' instead.");
1698       }
1699 
1700       appendOp(4, OpCodes.FROM_SELF);
1701 
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);
1705     }
1706     else if (tokenIs(".."))
1707     {
1708       nextToken();
1709       appendOp(4, OpCodes.FROM_PARENT);
1710 
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);
1714     }
1715 
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))))
1721     {
1722       Basis();
1723 
1724       while (tokenIs('['))
1725       {
1726         Predicate();
1727       }
1728 
1729       // Tell how long the entire step is.
1730       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1731         m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1732     }
1733     else
1734     {
1735       // No Step matched - that's an error if previous thing was a '//'
1736       if (doubleSlash)
1737       {
1738         // "Location step expected following '/' or '//'"
1739         error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null);
1740       }
1741 
1742       return false;
1743     }
1744 
1745     return true;
1746   }
1747 
1748   /**
1749    *
1750    * Basis    ::=    AxisName '::' NodeTest
1751    * | AbbreviatedBasis
1752    *
1753    * @throws javax.xml.transform.TransformerException
1754    */
Basis()1755   protected void Basis() throws javax.xml.transform.TransformerException
1756   {
1757 
1758     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1759     int axesType;
1760 
1761     // The next blocks guarantee that a FROM_XXX will be added.
1762     if (lookahead("::", 1))
1763     {
1764       axesType = AxisName();
1765 
1766       nextToken();
1767       nextToken();
1768     }
1769     else if (tokenIs('@'))
1770     {
1771       axesType = OpCodes.FROM_ATTRIBUTES;
1772 
1773       appendOp(2, axesType);
1774       nextToken();
1775     }
1776     else
1777     {
1778       axesType = OpCodes.FROM_CHILDREN;
1779 
1780       appendOp(2, axesType);
1781     }
1782 
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);
1785 
1786     NodeTest(axesType);
1787 
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);
1791    }
1792 
1793   /**
1794    *
1795    * Basis    ::=    AxisName '::' NodeTest
1796    * | AbbreviatedBasis
1797    *
1798    * @return FROM_XXX axes type, found in {@link org.apache.xpath.compiler.Keywords}.
1799    *
1800    * @throws javax.xml.transform.TransformerException
1801    */
AxisName()1802   protected int AxisName() throws javax.xml.transform.TransformerException
1803   {
1804 
1805     Object val = Keywords.getAxisName(m_token);
1806 
1807     if (null == val)
1808     {
1809       error(XPATHErrorResources.ER_ILLEGAL_AXIS_NAME,
1810             new Object[]{ m_token });  //"illegal axis name: "+m_token);
1811     }
1812 
1813     int axesType = ((Integer) val).intValue();
1814 
1815     appendOp(2, axesType);
1816 
1817     return axesType;
1818   }
1819 
1820   /**
1821    *
1822    * NodeTest    ::=    WildcardName
1823    * | NodeType '(' ')'
1824    * | 'processing-instruction' '(' Literal ')'
1825    *
1826    * @param axesType FROM_XXX axes type, found in {@link org.apache.xpath.compiler.Keywords}.
1827    *
1828    * @throws javax.xml.transform.TransformerException
1829    */
NodeTest(int axesType)1830   protected void NodeTest(int axesType) throws javax.xml.transform.TransformerException
1831   {
1832 
1833     if (lookahead('(', 1))
1834     {
1835       Object nodeTestOp = Keywords.getNodeType(m_token);
1836 
1837       if (null == nodeTestOp)
1838       {
1839         error(XPATHErrorResources.ER_UNKNOWN_NODETYPE,
1840               new Object[]{ m_token });  //"Unknown nodetype: "+m_token);
1841       }
1842       else
1843       {
1844         nextToken();
1845 
1846         int nt = ((Integer) nodeTestOp).intValue();
1847 
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);
1850 
1851         consumeExpected('(');
1852 
1853         if (OpCodes.NODETYPE_PI == nt)
1854         {
1855           if (!tokenIs(')'))
1856           {
1857             Literal();
1858           }
1859         }
1860 
1861         consumeExpected(')');
1862       }
1863     }
1864     else
1865     {
1866 
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);
1870 
1871       if (lookahead(':', 1))
1872       {
1873         if (tokenIs('*'))
1874         {
1875           m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ELEMWILDCARD);
1876         }
1877         else
1878         {
1879           m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1880 
1881           // Minimalist check for an NCName - just check first character
1882           // to distinguish from other possible tokens
1883           if (!Character.isLetter(m_tokenChar) && !tokenIs('_'))
1884           {
1885             // "Node test that matches either NCName:* or QName was expected."
1886             error(XPATHErrorResources.ER_EXPECTED_NODE_TEST, null);
1887           }
1888         }
1889 
1890         nextToken();
1891         consumeExpected(':');
1892       }
1893       else
1894       {
1895         m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.EMPTY);
1896       }
1897 
1898       m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1899 
1900       if (tokenIs('*'))
1901       {
1902         m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ELEMWILDCARD);
1903       }
1904       else
1905       {
1906         m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1907 
1908         // Minimalist check for an NCName - just check first character
1909         // to distinguish from other possible tokens
1910         if (!Character.isLetter(m_tokenChar) && !tokenIs('_'))
1911         {
1912           // "Node test that matches either NCName:* or QName was expected."
1913           error(XPATHErrorResources.ER_EXPECTED_NODE_TEST, null);
1914         }
1915       }
1916 
1917       m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1918 
1919       nextToken();
1920     }
1921   }
1922 
1923   /**
1924    *
1925    * Predicate ::= '[' PredicateExpr ']'
1926    *
1927    *
1928    * @throws javax.xml.transform.TransformerException
1929    */
Predicate()1930   protected void Predicate() throws javax.xml.transform.TransformerException
1931   {
1932 
1933     if (tokenIs('['))
1934     {
1935       nextToken();
1936       PredicateExpr();
1937       consumeExpected(']');
1938     }
1939   }
1940 
1941   /**
1942    *
1943    * PredicateExpr ::= Expr
1944    *
1945    *
1946    * @throws javax.xml.transform.TransformerException
1947    */
PredicateExpr()1948   protected void PredicateExpr() throws javax.xml.transform.TransformerException
1949   {
1950 
1951     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1952 
1953     appendOp(2, OpCodes.OP_PREDICATE);
1954     Expr();
1955 
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);
1961   }
1962 
1963   /**
1964    * QName ::=  (Prefix ':')? LocalPart
1965    * Prefix ::=  NCName
1966    * LocalPart ::=  NCName
1967    *
1968    * @throws javax.xml.transform.TransformerException
1969    */
QName()1970   protected void QName() throws javax.xml.transform.TransformerException
1971   {
1972     // Namespace
1973     if(lookahead(':', 1))
1974     {
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);
1977 
1978       nextToken();
1979       consumeExpected(':');
1980     }
1981     else
1982     {
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);
1985     }
1986 
1987     // Local name
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);
1990 
1991     nextToken();
1992   }
1993 
1994   /**
1995    * NCName ::=  (Letter | '_') (NCNameChar)
1996    * NCNameChar ::=  Letter | Digit | '.' | '-' | '_' | CombiningChar | Extender
1997    */
NCName()1998   protected void NCName()
1999   {
2000 
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);
2003 
2004     nextToken();
2005   }
2006 
2007   /**
2008    * The value of the Literal is the sequence of characters inside
2009    * the " or ' characters>.
2010    *
2011    * Literal  ::=  '"' [^"]* '"'
2012    * | "'" [^']* "'"
2013    *
2014    *
2015    * @throws javax.xml.transform.TransformerException
2016    */
Literal()2017   protected void Literal() throws javax.xml.transform.TransformerException
2018   {
2019 
2020     int last = m_token.length() - 1;
2021     char c0 = m_tokenChar;
2022     char cX = m_token.charAt(last);
2023 
2024     if (((c0 == '\"') && (cX == '\"')) || ((c0 == '\'') && (cX == '\'')))
2025     {
2026 
2027       // Mutate the token to remove the quotes and have the XString object
2028       // already made.
2029       int tokenQueuePos = m_queueMark - 1;
2030 
2031       m_ops.m_tokenQueue.setElementAt(null,tokenQueuePos);
2032 
2033       Object obj = new XString(m_token.substring(1, last));
2034 
2035       m_ops.m_tokenQueue.setElementAt(obj,tokenQueuePos);
2036 
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);
2040 
2041       nextToken();
2042     }
2043     else
2044     {
2045       error(XPATHErrorResources.ER_PATTERN_LITERAL_NEEDS_BE_QUOTED,
2046             new Object[]{ m_token });  //"Pattern literal ("+m_token+") needs to be quoted!");
2047     }
2048   }
2049 
2050   /**
2051    *
2052    * Number ::= [0-9]+('.'[0-9]+)? | '.'[0-9]+
2053    *
2054    *
2055    * @throws javax.xml.transform.TransformerException
2056    */
Number()2057   protected void Number() throws javax.xml.transform.TransformerException
2058   {
2059 
2060     if (null != m_token)
2061     {
2062 
2063       // Mutate the token to remove the quotes and have the XNumber object
2064       // already made.
2065       double num;
2066 
2067       try
2068       {
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();
2073       }
2074       catch (NumberFormatException nfe)
2075       {
2076         num = 0.0;  // to shut up compiler.
2077 
2078         error(XPATHErrorResources.ER_COULDNOT_BE_FORMATTED_TO_NUMBER,
2079               new Object[]{ m_token });  //m_token+" could not be formatted to a number!");
2080       }
2081 
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);
2085 
2086       nextToken();
2087     }
2088   }
2089 
2090   // ============= PATTERN FUNCTIONS =================
2091 
2092   /**
2093    *
2094    * Pattern  ::=  LocationPathPattern
2095    * | Pattern '|' LocationPathPattern
2096    *
2097    *
2098    * @throws javax.xml.transform.TransformerException
2099    */
Pattern()2100   protected void Pattern() throws javax.xml.transform.TransformerException
2101   {
2102 
2103     while (true)
2104     {
2105       LocationPathPattern();
2106 
2107       if (tokenIs('|'))
2108       {
2109         nextToken();
2110       }
2111       else
2112       {
2113         break;
2114       }
2115     }
2116   }
2117 
2118   /**
2119    *
2120    *
2121    * LocationPathPattern  ::=  '/' RelativePathPattern?
2122    * | IdKeyPattern (('/' | '//') RelativePathPattern)?
2123    * | '//'? RelativePathPattern
2124    *
2125    *
2126    * @throws javax.xml.transform.TransformerException
2127    */
LocationPathPattern()2128   protected void LocationPathPattern() throws javax.xml.transform.TransformerException
2129   {
2130 
2131     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2132 
2133     final int RELATIVE_PATH_NOT_PERMITTED = 0;
2134     final int RELATIVE_PATH_PERMITTED     = 1;
2135     final int RELATIVE_PATH_REQUIRED      = 2;
2136 
2137     int relativePathStatus = RELATIVE_PATH_NOT_PERMITTED;
2138 
2139     appendOp(2, OpCodes.OP_LOCATIONPATHPATTERN);
2140 
2141     if (lookahead('(', 1)
2142             && (tokenIs(Keywords.FUNC_ID_STRING)
2143                 || tokenIs(Keywords.FUNC_KEY_STRING)))
2144     {
2145       IdKeyPattern();
2146 
2147       if (tokenIs('/'))
2148       {
2149         nextToken();
2150 
2151         if (tokenIs('/'))
2152         {
2153           appendOp(4, OpCodes.MATCH_ANY_ANCESTOR);
2154 
2155           nextToken();
2156         }
2157         else
2158         {
2159           appendOp(4, OpCodes.MATCH_IMMEDIATE_ANCESTOR);
2160         }
2161 
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);
2165 
2166         relativePathStatus = RELATIVE_PATH_REQUIRED;
2167       }
2168     }
2169     else if (tokenIs('/'))
2170     {
2171       if (lookahead('/', 1))
2172       {
2173         appendOp(4, OpCodes.MATCH_ANY_ANCESTOR);
2174 
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'.
2179         nextToken();
2180 
2181         relativePathStatus = RELATIVE_PATH_REQUIRED;
2182       }
2183       else
2184       {
2185         appendOp(4, OpCodes.FROM_ROOT);
2186 
2187         relativePathStatus = RELATIVE_PATH_PERMITTED;
2188       }
2189 
2190 
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);
2194 
2195       nextToken();
2196     }
2197     else
2198     {
2199       relativePathStatus = RELATIVE_PATH_REQUIRED;
2200     }
2201 
2202     if (relativePathStatus != RELATIVE_PATH_NOT_PERMITTED)
2203     {
2204       if (!tokenIs('|') && (null != m_token))
2205       {
2206         RelativePathPattern();
2207       }
2208       else if (relativePathStatus == RELATIVE_PATH_REQUIRED)
2209       {
2210         // "A relative path pattern was expected."
2211         error(XPATHErrorResources.ER_EXPECTED_REL_PATH_PATTERN, null);
2212       }
2213     }
2214 
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);
2220   }
2221 
2222   /**
2223    *
2224    * IdKeyPattern  ::=  'id' '(' Literal ')'
2225    * | 'key' '(' Literal ',' Literal ')'
2226    * (Also handle doc())
2227    *
2228    *
2229    * @throws javax.xml.transform.TransformerException
2230    */
IdKeyPattern()2231   protected void IdKeyPattern() throws javax.xml.transform.TransformerException
2232   {
2233     FunctionCall();
2234   }
2235 
2236   /**
2237    *
2238    * RelativePathPattern  ::=  StepPattern
2239    * | RelativePathPattern '/' StepPattern
2240    * | RelativePathPattern '//' StepPattern
2241    *
2242    * @throws javax.xml.transform.TransformerException
2243    */
RelativePathPattern()2244   protected void RelativePathPattern()
2245               throws javax.xml.transform.TransformerException
2246   {
2247 
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);
2251 
2252     while (tokenIs('/'))
2253     {
2254       nextToken();
2255 
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);
2260     }
2261   }
2262 
2263   /**
2264    *
2265    * StepPattern  ::=  AbbreviatedNodeTestStep
2266    *
2267    * @param isLeadingSlashPermitted a boolean indicating whether a slash can
2268    *        appear at the start of this step
2269    *
2270    * @return boolean indicating whether a slash following the step was consumed
2271    *
2272    * @throws javax.xml.transform.TransformerException
2273    */
StepPattern(boolean isLeadingSlashPermitted)2274   protected boolean StepPattern(boolean isLeadingSlashPermitted)
2275             throws javax.xml.transform.TransformerException
2276   {
2277     return AbbreviatedNodeTestStep(isLeadingSlashPermitted);
2278   }
2279 
2280   /**
2281    *
2282    * AbbreviatedNodeTestStep    ::=    '@'? NodeTest Predicate
2283    *
2284    * @param isLeadingSlashPermitted a boolean indicating whether a slash can
2285    *        appear at the start of this step
2286    *
2287    * @return boolean indicating whether a slash following the step was consumed
2288    *
2289    * @throws javax.xml.transform.TransformerException
2290    */
AbbreviatedNodeTestStep(boolean isLeadingSlashPermitted)2291   protected boolean AbbreviatedNodeTestStep(boolean isLeadingSlashPermitted)
2292             throws javax.xml.transform.TransformerException
2293   {
2294 
2295     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2296     int axesType;
2297 
2298     // The next blocks guarantee that a MATCH_XXX will be added.
2299     int matchTypePos = -1;
2300 
2301     if (tokenIs('@'))
2302     {
2303       axesType = OpCodes.MATCH_ATTRIBUTE;
2304 
2305       appendOp(2, axesType);
2306       nextToken();
2307     }
2308     else if (this.lookahead("::", 1))
2309     {
2310       if (tokenIs("attribute"))
2311       {
2312         axesType = OpCodes.MATCH_ATTRIBUTE;
2313 
2314         appendOp(2, axesType);
2315       }
2316       else if (tokenIs("child"))
2317       {
2318         matchTypePos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2319         axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR;
2320 
2321         appendOp(2, axesType);
2322       }
2323       else
2324       {
2325         axesType = -1;
2326 
2327         this.error(XPATHErrorResources.ER_AXES_NOT_ALLOWED,
2328                    new Object[]{ this.m_token });
2329       }
2330 
2331       nextToken();
2332       nextToken();
2333     }
2334     else if (tokenIs('/'))
2335     {
2336       if (!isLeadingSlashPermitted)
2337       {
2338         // "A step was expected in the pattern, but '/' was encountered."
2339         error(XPATHErrorResources.ER_EXPECTED_STEP_PATTERN, null);
2340       }
2341       axesType = OpCodes.MATCH_ANY_ANCESTOR;
2342 
2343       appendOp(2, axesType);
2344       nextToken();
2345     }
2346     else
2347     {
2348       matchTypePos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2349       axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR;
2350 
2351       appendOp(2, axesType);
2352     }
2353 
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);
2356 
2357     NodeTest(axesType);
2358 
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);
2362 
2363     while (tokenIs('['))
2364     {
2365       Predicate();
2366     }
2367 
2368     boolean trailingSlashConsumed;
2369 
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 '//'.)
2374     //
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?
2378     //
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))
2383     {
2384       m_ops.setOp(matchTypePos, OpCodes.MATCH_ANY_ANCESTOR);
2385 
2386       nextToken();
2387 
2388       trailingSlashConsumed = true;
2389     }
2390     else
2391     {
2392       trailingSlashConsumed = false;
2393     }
2394 
2395     // Tell how long the entire step is.
2396     m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
2397       m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
2398 
2399     return trailingSlashConsumed;
2400   }
2401 }
2402