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 */
21package org.apache.xpath.compiler;
22
23import javax.xml.transform.ErrorListener;
24import javax.xml.transform.TransformerException;
25
26import org.apache.xalan.res.XSLMessages;
27import org.apache.xml.utils.PrefixResolver;
28import org.apache.xpath.XPathProcessorException;
29import org.apache.xpath.domapi.XPathStylesheetDOM3Exception;
30import org.apache.xpath.objects.XNumber;
31import org.apache.xpath.objects.XString;
32import 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 */
39public 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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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