• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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