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