1/*
2 * reserved comment block
3 * DO NOT REMOVE OR ALTER!
4 */
5/*
6 * Licensed to the Apache Software Foundation (ASF) under one or more
7 * contributor license agreements.  See the NOTICE file distributed with
8 * this work for additional information regarding copyright ownership.
9 * The ASF licenses this file to You under the Apache License, Version 2.0
10 * (the "License"); you may not use this file except in compliance with
11 * the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 */
21
22package com.sun.org.apache.xpath.internal.compiler;
23
24import javax.xml.transform.ErrorListener;
25import javax.xml.transform.TransformerException;
26
27import com.sun.org.apache.xalan.internal.res.XSLMessages;
28import com.sun.org.apache.xml.internal.utils.PrefixResolver;
29import com.sun.org.apache.xpath.internal.XPathProcessorException;
30import com.sun.org.apache.xpath.internal.objects.XNumber;
31import com.sun.org.apache.xpath.internal.objects.XString;
32import com.sun.org.apache.xpath.internal.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 (com.sun.org.apache.xpath.internal.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 com.sun.org.apache.xpath.internal.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 com.sun.org.apache.xpath.internal.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   * Dump the remaining token queue.
625   * Thanks to Craig for this.
626   *
627   * @return A dump of the remaining token queue, which may be appended to
628   *         an error message.
629   */
630  protected String dumpRemainingTokenQueue()
631  {
632
633    int q = m_queueMark;
634    String returnMsg;
635
636    if (q < m_ops.getTokenQueueSize())
637    {
638      String msg = "\n Remaining tokens: (";
639
640      while (q < m_ops.getTokenQueueSize())
641      {
642        String t = (String) m_ops.m_tokenQueue.elementAt(q++);
643
644        msg += (" '" + t + "'");
645      }
646
647      returnMsg = msg + ")";
648    }
649    else
650    {
651      returnMsg = "";
652    }
653
654    return returnMsg;
655  }
656
657  /**
658   * Given a string, return the corresponding function token.
659   *
660   * @param key A local name of a function.
661   *
662   * @return   The function ID, which may correspond to one of the FUNC_XXX
663   *    values found in {@link com.sun.org.apache.xpath.internal.compiler.FunctionTable}, but may
664   *    be a value installed by an external module.
665   */
666  final int getFunctionToken(String key)
667  {
668
669    int tok;
670
671    Object id;
672
673    try
674    {
675      // These are nodetests, xpathparser treats them as functions when parsing
676      // a FilterExpr.
677      id = Keywords.lookupNodeTest(key);
678      if (null == id) id = m_functionTable.getFunctionID(key);
679      tok = ((Integer) id).intValue();
680    }
681    catch (NullPointerException npe)
682    {
683      tok = -1;
684    }
685    catch (ClassCastException cce)
686    {
687      tok = -1;
688    }
689
690    return tok;
691  }
692
693  /**
694   * Insert room for operation.  This will NOT set
695   * the length value of the operation, but will update
696   * the length value for the total expression.
697   *
698   * @param pos The position where the op is to be inserted.
699   * @param length The length of the operation space in the op map.
700   * @param op The op code to the inserted.
701   */
702  void insertOp(int pos, int length, int op)
703  {
704
705    int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
706
707    for (int i = totalLen - 1; i >= pos; i--)
708    {
709      m_ops.setOp(i + length, m_ops.getOp(i));
710    }
711
712    m_ops.setOp(pos,op);
713    m_ops.setOp(OpMap.MAPINDEX_LENGTH,totalLen + length);
714  }
715
716  /**
717   * Insert room for operation.  This WILL set
718   * the length value of the operation, and will update
719   * the length value for the total expression.
720   *
721   * @param length The length of the operation.
722   * @param op The op code to the inserted.
723   */
724  void appendOp(int length, int op)
725  {
726
727    int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
728
729    m_ops.setOp(totalLen, op);
730    m_ops.setOp(totalLen + OpMap.MAPINDEX_LENGTH, length);
731    m_ops.setOp(OpMap.MAPINDEX_LENGTH, totalLen + length);
732  }
733
734  // ============= EXPRESSIONS FUNCTIONS =================
735
736  /**
737   *
738   *
739   * Expr  ::=  OrExpr
740   *
741   *
742   * @throws javax.xml.transform.TransformerException
743   */
744  protected void Expr() throws javax.xml.transform.TransformerException
745  {
746    OrExpr();
747  }
748
749  /**
750   *
751   *
752   * OrExpr  ::=  AndExpr
753   * | OrExpr 'or' AndExpr
754   *
755   *
756   * @throws javax.xml.transform.TransformerException
757   */
758  protected void OrExpr() throws javax.xml.transform.TransformerException
759  {
760
761    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
762
763    AndExpr();
764
765    if ((null != m_token) && tokenIs("or"))
766    {
767      nextToken();
768      insertOp(opPos, 2, OpCodes.OP_OR);
769      OrExpr();
770
771      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
772        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
773    }
774  }
775
776  /**
777   *
778   *
779   * AndExpr  ::=  EqualityExpr
780   * | AndExpr 'and' EqualityExpr
781   *
782   *
783   * @throws javax.xml.transform.TransformerException
784   */
785  protected void AndExpr() throws javax.xml.transform.TransformerException
786  {
787
788    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
789
790    EqualityExpr(-1);
791
792    if ((null != m_token) && tokenIs("and"))
793    {
794      nextToken();
795      insertOp(opPos, 2, OpCodes.OP_AND);
796      AndExpr();
797
798      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
799        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
800    }
801  }
802
803  /**
804   *
805   * @returns an Object which is either a String, a Number, a Boolean, or a vector
806   * of nodes.
807   *
808   * EqualityExpr  ::=  RelationalExpr
809   * | EqualityExpr '=' RelationalExpr
810   *
811   *
812   * @param addPos Position where expression is to be added, or -1 for append.
813   *
814   * @return the position at the end of the equality expression.
815   *
816   * @throws javax.xml.transform.TransformerException
817   */
818  protected int EqualityExpr(int addPos) throws javax.xml.transform.TransformerException
819  {
820
821    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
822
823    if (-1 == addPos)
824      addPos = opPos;
825
826    RelationalExpr(-1);
827
828    if (null != m_token)
829    {
830      if (tokenIs('!') && lookahead('=', 1))
831      {
832        nextToken();
833        nextToken();
834        insertOp(addPos, 2, OpCodes.OP_NOTEQUALS);
835
836        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
837
838        addPos = EqualityExpr(addPos);
839        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
840          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
841        addPos += 2;
842      }
843      else if (tokenIs('='))
844      {
845        nextToken();
846        insertOp(addPos, 2, OpCodes.OP_EQUALS);
847
848        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
849
850        addPos = EqualityExpr(addPos);
851        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
852          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
853        addPos += 2;
854      }
855    }
856
857    return addPos;
858  }
859
860  /**
861   * .
862   * @returns an Object which is either a String, a Number, a Boolean, or a vector
863   * of nodes.
864   *
865   * RelationalExpr  ::=  AdditiveExpr
866   * | RelationalExpr '<' AdditiveExpr
867   * | RelationalExpr '>' AdditiveExpr
868   * | RelationalExpr '<=' AdditiveExpr
869   * | RelationalExpr '>=' AdditiveExpr
870   *
871   *
872   * @param addPos Position where expression is to be added, or -1 for append.
873   *
874   * @return the position at the end of the relational expression.
875   *
876   * @throws javax.xml.transform.TransformerException
877   */
878  protected int RelationalExpr(int addPos) throws javax.xml.transform.TransformerException
879  {
880
881    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
882
883    if (-1 == addPos)
884      addPos = opPos;
885
886    AdditiveExpr(-1);
887
888    if (null != m_token)
889    {
890      if (tokenIs('<'))
891      {
892        nextToken();
893
894        if (tokenIs('='))
895        {
896          nextToken();
897          insertOp(addPos, 2, OpCodes.OP_LTE);
898        }
899        else
900        {
901          insertOp(addPos, 2, OpCodes.OP_LT);
902        }
903
904        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
905
906        addPos = RelationalExpr(addPos);
907        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
908          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
909        addPos += 2;
910      }
911      else if (tokenIs('>'))
912      {
913        nextToken();
914
915        if (tokenIs('='))
916        {
917          nextToken();
918          insertOp(addPos, 2, OpCodes.OP_GTE);
919        }
920        else
921        {
922          insertOp(addPos, 2, OpCodes.OP_GT);
923        }
924
925        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
926
927        addPos = RelationalExpr(addPos);
928        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
929          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
930        addPos += 2;
931      }
932    }
933
934    return addPos;
935  }
936
937  /**
938   * This has to handle construction of the operations so that they are evaluated
939   * in pre-fix order.  So, for 9+7-6, instead of |+|9|-|7|6|, this needs to be
940   * evaluated as |-|+|9|7|6|.
941   *
942   * AdditiveExpr  ::=  MultiplicativeExpr
943   * | AdditiveExpr '+' MultiplicativeExpr
944   * | AdditiveExpr '-' MultiplicativeExpr
945   *
946   *
947   * @param addPos Position where expression is to be added, or -1 for append.
948   *
949   * @return the position at the end of the equality expression.
950   *
951   * @throws javax.xml.transform.TransformerException
952   */
953  protected int AdditiveExpr(int addPos) throws javax.xml.transform.TransformerException
954  {
955
956    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
957
958    if (-1 == addPos)
959      addPos = opPos;
960
961    MultiplicativeExpr(-1);
962
963    if (null != m_token)
964    {
965      if (tokenIs('+'))
966      {
967        nextToken();
968        insertOp(addPos, 2, OpCodes.OP_PLUS);
969
970        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
971
972        addPos = AdditiveExpr(addPos);
973        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
974          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
975        addPos += 2;
976      }
977      else if (tokenIs('-'))
978      {
979        nextToken();
980        insertOp(addPos, 2, OpCodes.OP_MINUS);
981
982        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
983
984        addPos = AdditiveExpr(addPos);
985        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
986          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
987        addPos += 2;
988      }
989    }
990
991    return addPos;
992  }
993
994  /**
995   * This has to handle construction of the operations so that they are evaluated
996   * in pre-fix order.  So, for 9+7-6, instead of |+|9|-|7|6|, this needs to be
997   * evaluated as |-|+|9|7|6|.
998   *
999   * MultiplicativeExpr  ::=  UnaryExpr
1000   * | MultiplicativeExpr MultiplyOperator UnaryExpr
1001   * | MultiplicativeExpr 'div' UnaryExpr
1002   * | MultiplicativeExpr 'mod' UnaryExpr
1003   * | MultiplicativeExpr 'quo' UnaryExpr
1004   *
1005   * @param addPos Position where expression is to be added, or -1 for append.
1006   *
1007   * @return the position at the end of the equality expression.
1008   *
1009   * @throws javax.xml.transform.TransformerException
1010   */
1011  protected int MultiplicativeExpr(int addPos) throws javax.xml.transform.TransformerException
1012  {
1013
1014    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1015
1016    if (-1 == addPos)
1017      addPos = opPos;
1018
1019    UnaryExpr();
1020
1021    if (null != m_token)
1022    {
1023      if (tokenIs('*'))
1024      {
1025        nextToken();
1026        insertOp(addPos, 2, OpCodes.OP_MULT);
1027
1028        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1029
1030        addPos = MultiplicativeExpr(addPos);
1031        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1032          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1033        addPos += 2;
1034      }
1035      else if (tokenIs("div"))
1036      {
1037        nextToken();
1038        insertOp(addPos, 2, OpCodes.OP_DIV);
1039
1040        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1041
1042        addPos = MultiplicativeExpr(addPos);
1043        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1044          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1045        addPos += 2;
1046      }
1047      else if (tokenIs("mod"))
1048      {
1049        nextToken();
1050        insertOp(addPos, 2, OpCodes.OP_MOD);
1051
1052        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1053
1054        addPos = MultiplicativeExpr(addPos);
1055        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1056          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1057        addPos += 2;
1058      }
1059      else if (tokenIs("quo"))
1060      {
1061        nextToken();
1062        insertOp(addPos, 2, OpCodes.OP_QUO);
1063
1064        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1065
1066        addPos = MultiplicativeExpr(addPos);
1067        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1068          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1069        addPos += 2;
1070      }
1071    }
1072
1073    return addPos;
1074  }
1075
1076  /**
1077   *
1078   * UnaryExpr  ::=  UnionExpr
1079   * | '-' UnaryExpr
1080   *
1081   *
1082   * @throws javax.xml.transform.TransformerException
1083   */
1084  protected void UnaryExpr() throws javax.xml.transform.TransformerException
1085  {
1086
1087    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1088    boolean isNeg = false;
1089
1090    if (m_tokenChar == '-')
1091    {
1092      nextToken();
1093      appendOp(2, OpCodes.OP_NEG);
1094
1095      isNeg = true;
1096    }
1097
1098    UnionExpr();
1099
1100    if (isNeg)
1101      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1102        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1103  }
1104
1105  /**
1106   *
1107   * StringExpr  ::=  Expr
1108   *
1109   *
1110   * @throws javax.xml.transform.TransformerException
1111   */
1112  protected void StringExpr() throws javax.xml.transform.TransformerException
1113  {
1114
1115    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1116
1117    appendOp(2, OpCodes.OP_STRING);
1118    Expr();
1119
1120    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1121      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1122  }
1123
1124  /**
1125   *
1126   *
1127   * StringExpr  ::=  Expr
1128   *
1129   *
1130   * @throws javax.xml.transform.TransformerException
1131   */
1132  protected void BooleanExpr() throws javax.xml.transform.TransformerException
1133  {
1134
1135    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1136
1137    appendOp(2, OpCodes.OP_BOOL);
1138    Expr();
1139
1140    int opLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos;
1141
1142    if (opLen == 2)
1143    {
1144      error(XPATHErrorResources.ER_BOOLEAN_ARG_NO_LONGER_OPTIONAL, null);  //"boolean(...) argument is no longer optional with 19990709 XPath draft.");
1145    }
1146
1147    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, opLen);
1148  }
1149
1150  /**
1151   *
1152   *
1153   * NumberExpr  ::=  Expr
1154   *
1155   *
1156   * @throws javax.xml.transform.TransformerException
1157   */
1158  protected void NumberExpr() throws javax.xml.transform.TransformerException
1159  {
1160
1161    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1162
1163    appendOp(2, OpCodes.OP_NUMBER);
1164    Expr();
1165
1166    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1167      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1168  }
1169
1170  /**
1171   * The context of the right hand side expressions is the context of the
1172   * left hand side expression. The results of the right hand side expressions
1173   * are node sets. The result of the left hand side UnionExpr is the union
1174   * of the results of the right hand side expressions.
1175   *
1176   *
1177   * UnionExpr    ::=    PathExpr
1178   * | UnionExpr '|' PathExpr
1179   *
1180   *
1181   * @throws javax.xml.transform.TransformerException
1182   */
1183  protected void UnionExpr() throws javax.xml.transform.TransformerException
1184  {
1185
1186    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1187    boolean continueOrLoop = true;
1188    boolean foundUnion = false;
1189
1190    do
1191    {
1192      PathExpr();
1193
1194      if (tokenIs('|'))
1195      {
1196        if (false == foundUnion)
1197        {
1198          foundUnion = true;
1199
1200          insertOp(opPos, 2, OpCodes.OP_UNION);
1201        }
1202
1203        nextToken();
1204      }
1205      else
1206      {
1207        break;
1208      }
1209
1210      // this.m_testForDocOrder = true;
1211    }
1212    while (continueOrLoop);
1213
1214    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1215          m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1216  }
1217
1218  /**
1219   * PathExpr  ::=  LocationPath
1220   * | FilterExpr
1221   * | FilterExpr '/' RelativeLocationPath
1222   * | FilterExpr '//' RelativeLocationPath
1223   *
1224   * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
1225   * the error condition is severe enough to halt processing.
1226   *
1227   * @throws javax.xml.transform.TransformerException
1228   */
1229  protected void PathExpr() throws javax.xml.transform.TransformerException
1230  {
1231
1232    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1233
1234    int filterExprMatch = FilterExpr();
1235
1236    if (filterExprMatch != FILTER_MATCH_FAILED)
1237    {
1238      // If FilterExpr had Predicates, a OP_LOCATIONPATH opcode would already
1239      // have been inserted.
1240      boolean locationPathStarted = (filterExprMatch==FILTER_MATCH_PREDICATES);
1241
1242      if (tokenIs('/'))
1243      {
1244        nextToken();
1245
1246        if (!locationPathStarted)
1247        {
1248          // int locationPathOpPos = opPos;
1249          insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH);
1250
1251          locationPathStarted = true;
1252        }
1253
1254        if (!RelativeLocationPath())
1255        {
1256          // "Relative location path expected following '/' or '//'"
1257          error(XPATHErrorResources.ER_EXPECTED_REL_LOC_PATH, null);
1258        }
1259
1260      }
1261
1262      // Terminate for safety.
1263      if (locationPathStarted)
1264      {
1265        m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1266        m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1267        m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1268          m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1269      }
1270    }
1271    else
1272    {
1273      LocationPath();
1274    }
1275  }
1276
1277  /**
1278   *
1279   *
1280   * FilterExpr  ::=  PrimaryExpr
1281   * | FilterExpr Predicate
1282   *
1283   * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
1284   * the error condition is severe enough to halt processing.
1285   *
1286   * @return  FILTER_MATCH_PREDICATES, if this method successfully matched a
1287   *          FilterExpr with one or more Predicates;
1288   *          FILTER_MATCH_PRIMARY, if this method successfully matched a
1289   *          FilterExpr that was just a PrimaryExpr; or
1290   *          FILTER_MATCH_FAILED, if this method did not match a FilterExpr
1291   *
1292   * @throws javax.xml.transform.TransformerException
1293   */
1294  protected int FilterExpr() throws javax.xml.transform.TransformerException
1295  {
1296
1297    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1298
1299    int filterMatch;
1300
1301    if (PrimaryExpr())
1302    {
1303      if (tokenIs('['))
1304      {
1305
1306        // int locationPathOpPos = opPos;
1307        insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH);
1308
1309        while (tokenIs('['))
1310        {
1311          Predicate();
1312        }
1313
1314        filterMatch = FILTER_MATCH_PREDICATES;
1315      }
1316      else
1317      {
1318        filterMatch = FILTER_MATCH_PRIMARY;
1319      }
1320    }
1321    else
1322    {
1323      filterMatch = FILTER_MATCH_FAILED;
1324    }
1325
1326    return filterMatch;
1327
1328    /*
1329     * if(tokenIs('['))
1330     * {
1331     *   Predicate();
1332     *   m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos;
1333     * }
1334     */
1335  }
1336
1337  /**
1338   *
1339   * PrimaryExpr  ::=  VariableReference
1340   * | '(' Expr ')'
1341   * | Literal
1342   * | Number
1343   * | FunctionCall
1344   *
1345   * @return true if this method successfully matched a PrimaryExpr
1346   *
1347   * @throws javax.xml.transform.TransformerException
1348   *
1349   */
1350  protected boolean PrimaryExpr() throws javax.xml.transform.TransformerException
1351  {
1352
1353    boolean matchFound;
1354    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1355
1356    if ((m_tokenChar == '\'') || (m_tokenChar == '"'))
1357    {
1358      appendOp(2, OpCodes.OP_LITERAL);
1359      Literal();
1360
1361      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1362        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1363
1364      matchFound = true;
1365    }
1366    else if (m_tokenChar == '$')
1367    {
1368      nextToken();  // consume '$'
1369      appendOp(2, OpCodes.OP_VARIABLE);
1370      QName();
1371
1372      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1373        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1374
1375      matchFound = true;
1376    }
1377    else if (m_tokenChar == '(')
1378    {
1379      nextToken();
1380      appendOp(2, OpCodes.OP_GROUP);
1381      Expr();
1382      consumeExpected(')');
1383
1384      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1385        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1386
1387      matchFound = true;
1388    }
1389    else if ((null != m_token) && ((('.' == m_tokenChar) && (m_token.length() > 1) && Character.isDigit(
1390            m_token.charAt(1))) || Character.isDigit(m_tokenChar)))
1391    {
1392      appendOp(2, OpCodes.OP_NUMBERLIT);
1393      Number();
1394
1395      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1396        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1397
1398      matchFound = true;
1399    }
1400    else if (lookahead('(', 1) || (lookahead(':', 1) && lookahead('(', 3)))
1401    {
1402      matchFound = FunctionCall();
1403    }
1404    else
1405    {
1406      matchFound = false;
1407    }
1408
1409    return matchFound;
1410  }
1411
1412  /**
1413   *
1414   * Argument    ::=    Expr
1415   *
1416   *
1417   * @throws javax.xml.transform.TransformerException
1418   */
1419  protected void Argument() throws javax.xml.transform.TransformerException
1420  {
1421
1422    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1423
1424    appendOp(2, OpCodes.OP_ARGUMENT);
1425    Expr();
1426
1427    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1428      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1429  }
1430
1431  /**
1432   *
1433   * FunctionCall    ::=    FunctionName '(' ( Argument ( ',' Argument)*)? ')'
1434   *
1435   * @return true if, and only if, a FunctionCall was matched
1436   *
1437   * @throws javax.xml.transform.TransformerException
1438   */
1439  protected boolean FunctionCall() throws javax.xml.transform.TransformerException
1440  {
1441
1442    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1443
1444    if (lookahead(':', 1))
1445    {
1446      appendOp(4, OpCodes.OP_EXTFUNCTION);
1447
1448      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, m_queueMark - 1);
1449
1450      nextToken();
1451      consumeExpected(':');
1452
1453      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 2, m_queueMark - 1);
1454
1455      nextToken();
1456    }
1457    else
1458    {
1459      int funcTok = getFunctionToken(m_token);
1460
1461      if (-1 == funcTok)
1462      {
1463        error(XPATHErrorResources.ER_COULDNOT_FIND_FUNCTION,
1464              new Object[]{ m_token });  //"Could not find function: "+m_token+"()");
1465      }
1466
1467      switch (funcTok)
1468      {
1469      case OpCodes.NODETYPE_PI :
1470      case OpCodes.NODETYPE_COMMENT :
1471      case OpCodes.NODETYPE_TEXT :
1472      case OpCodes.NODETYPE_NODE :
1473        // Node type tests look like function calls, but they're not
1474        return false;
1475      default :
1476        appendOp(3, OpCodes.OP_FUNCTION);
1477
1478        m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, funcTok);
1479      }
1480
1481      nextToken();
1482    }
1483
1484    consumeExpected('(');
1485
1486    while (!tokenIs(')') && m_token != null)
1487    {
1488      if (tokenIs(','))
1489      {
1490        error(XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_PRECEDING_ARG, null);  //"Found ',' but no preceding argument!");
1491      }
1492
1493      Argument();
1494
1495      if (!tokenIs(')'))
1496      {
1497        consumeExpected(',');
1498
1499        if (tokenIs(')'))
1500        {
1501          error(XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_FOLLOWING_ARG,
1502                null);  //"Found ',' but no following argument!");
1503        }
1504      }
1505    }
1506
1507    consumeExpected(')');
1508
1509    // Terminate for safety.
1510    m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1511    m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1512    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1513      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1514
1515    return true;
1516  }
1517
1518  // ============= GRAMMAR FUNCTIONS =================
1519
1520  /**
1521   *
1522   * LocationPath ::= RelativeLocationPath
1523   * | AbsoluteLocationPath
1524   *
1525   *
1526   * @throws javax.xml.transform.TransformerException
1527   */
1528  protected void LocationPath() throws javax.xml.transform.TransformerException
1529  {
1530
1531    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1532
1533    // int locationPathOpPos = opPos;
1534    appendOp(2, OpCodes.OP_LOCATIONPATH);
1535
1536    boolean seenSlash = tokenIs('/');
1537
1538    if (seenSlash)
1539    {
1540      appendOp(4, OpCodes.FROM_ROOT);
1541
1542      // Tell how long the step is without the predicate
1543      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
1544      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_ROOT);
1545
1546      nextToken();
1547    } else if (m_token == null) {
1548      error(XPATHErrorResources.ER_EXPECTED_LOC_PATH_AT_END_EXPR, null);
1549    }
1550
1551    if (m_token != null)
1552    {
1553      if (!RelativeLocationPath() && !seenSlash)
1554      {
1555        // Neither a '/' nor a RelativeLocationPath - i.e., matched nothing
1556        // "Location path expected, but found "+m_token+" was encountered."
1557        error(XPATHErrorResources.ER_EXPECTED_LOC_PATH,
1558              new Object [] {m_token});
1559      }
1560    }
1561
1562    // Terminate for safety.
1563    m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1564    m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1565    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1566      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1567  }
1568
1569  /**
1570   *
1571   * RelativeLocationPath ::= Step
1572   * | RelativeLocationPath '/' Step
1573   * | AbbreviatedRelativeLocationPath
1574   *
1575   * @returns true if, and only if, a RelativeLocationPath was matched
1576   *
1577   * @throws javax.xml.transform.TransformerException
1578   */
1579  protected boolean RelativeLocationPath()
1580               throws javax.xml.transform.TransformerException
1581  {
1582    if (!Step())
1583    {
1584      return false;
1585    }
1586
1587    while (tokenIs('/'))
1588    {
1589      nextToken();
1590
1591      if (!Step())
1592      {
1593        // RelativeLocationPath can't end with a trailing '/'
1594        // "Location step expected following '/' or '//'"
1595        error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null);
1596      }
1597    }
1598
1599    return true;
1600  }
1601
1602  /**
1603   *
1604   * Step    ::=    Basis Predicate
1605   * | AbbreviatedStep
1606   *
1607   * @returns false if step was empty (or only a '/'); true, otherwise
1608   *
1609   * @throws javax.xml.transform.TransformerException
1610   */
1611  protected boolean Step() throws javax.xml.transform.TransformerException
1612  {
1613    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1614
1615    boolean doubleSlash = tokenIs('/');
1616
1617    // At most a single '/' before each Step is consumed by caller; if the
1618    // first thing is a '/', that means we had '//' and the Step must not
1619    // be empty.
1620    if (doubleSlash)
1621    {
1622      nextToken();
1623
1624      appendOp(2, OpCodes.FROM_DESCENDANTS_OR_SELF);
1625
1626      // Have to fix up for patterns such as '//@foo' or '//attribute::foo',
1627      // which translate to 'descendant-or-self::node()/attribute::foo'.
1628      // notice I leave the '/' on the queue, so the next will be processed
1629      // by a regular step pattern.
1630
1631      // Make room for telling how long the step is without the predicate
1632      m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1633      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.NODETYPE_NODE);
1634      m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1635
1636      // Tell how long the step is without the predicate
1637      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
1638          m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1639
1640      // Tell how long the step is with the predicate
1641      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1642          m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1643
1644      opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1645    }
1646
1647    if (tokenIs("."))
1648    {
1649      nextToken();
1650
1651      if (tokenIs('['))
1652      {
1653        error(XPATHErrorResources.ER_PREDICATE_ILLEGAL_SYNTAX, null);  //"'..[predicate]' or '.[predicate]' is illegal syntax.  Use 'self::node()[predicate]' instead.");
1654      }
1655
1656      appendOp(4, OpCodes.FROM_SELF);
1657
1658      // Tell how long the step is without the predicate
1659      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2,4);
1660      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_NODE);
1661    }
1662    else if (tokenIs(".."))
1663    {
1664      nextToken();
1665      appendOp(4, OpCodes.FROM_PARENT);
1666
1667      // Tell how long the step is without the predicate
1668      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2,4);
1669      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_NODE);
1670    }
1671
1672    // There is probably a better way to test for this
1673    // transition... but it gets real hairy if you try
1674    // to do it in basis().
1675    else if (tokenIs('*') || tokenIs('@') || tokenIs('_')
1676             || (m_token!= null && Character.isLetter(m_token.charAt(0))))
1677    {
1678      Basis();
1679
1680      while (tokenIs('['))
1681      {
1682        Predicate();
1683      }
1684
1685      // Tell how long the entire step is.
1686      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1687        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1688    }
1689    else
1690    {
1691      // No Step matched - that's an error if previous thing was a '//'
1692      if (doubleSlash)
1693      {
1694        // "Location step expected following '/' or '//'"
1695        error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null);
1696      }
1697
1698      return false;
1699    }
1700
1701    return true;
1702  }
1703
1704  /**
1705   *
1706   * Basis    ::=    AxisName '::' NodeTest
1707   * | AbbreviatedBasis
1708   *
1709   * @throws javax.xml.transform.TransformerException
1710   */
1711  protected void Basis() throws javax.xml.transform.TransformerException
1712  {
1713
1714    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1715    int axesType;
1716
1717    // The next blocks guarantee that a FROM_XXX will be added.
1718    if (lookahead("::", 1))
1719    {
1720      axesType = AxisName();
1721
1722      nextToken();
1723      nextToken();
1724    }
1725    else if (tokenIs('@'))
1726    {
1727      axesType = OpCodes.FROM_ATTRIBUTES;
1728
1729      appendOp(2, axesType);
1730      nextToken();
1731    }
1732    else
1733    {
1734      axesType = OpCodes.FROM_CHILDREN;
1735
1736      appendOp(2, axesType);
1737    }
1738
1739    // Make room for telling how long the step is without the predicate
1740    m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1741
1742    NodeTest(axesType);
1743
1744    // Tell how long the step is without the predicate
1745    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
1746      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1747   }
1748
1749  /**
1750   *
1751   * Basis    ::=    AxisName '::' NodeTest
1752   * | AbbreviatedBasis
1753   *
1754   * @return FROM_XXX axes type, found in {@link com.sun.org.apache.xpath.internal.compiler.Keywords}.
1755   *
1756   * @throws javax.xml.transform.TransformerException
1757   */
1758  protected int AxisName() throws javax.xml.transform.TransformerException
1759  {
1760
1761    Object val = Keywords.getAxisName(m_token);
1762
1763    if (null == val)
1764    {
1765      error(XPATHErrorResources.ER_ILLEGAL_AXIS_NAME,
1766            new Object[]{ m_token });  //"illegal axis name: "+m_token);
1767    }
1768
1769    int axesType = ((Integer) val).intValue();
1770
1771    appendOp(2, axesType);
1772
1773    return axesType;
1774  }
1775
1776  /**
1777   *
1778   * NodeTest    ::=    WildcardName
1779   * | NodeType '(' ')'
1780   * | 'processing-instruction' '(' Literal ')'
1781   *
1782   * @param axesType FROM_XXX axes type, found in {@link com.sun.org.apache.xpath.internal.compiler.Keywords}.
1783   *
1784   * @throws javax.xml.transform.TransformerException
1785   */
1786  protected void NodeTest(int axesType) throws javax.xml.transform.TransformerException
1787  {
1788
1789    if (lookahead('(', 1))
1790    {
1791      Object nodeTestOp = Keywords.getNodeType(m_token);
1792
1793      if (null == nodeTestOp)
1794      {
1795        error(XPATHErrorResources.ER_UNKNOWN_NODETYPE,
1796              new Object[]{ m_token });  //"Unknown nodetype: "+m_token);
1797      }
1798      else
1799      {
1800        nextToken();
1801
1802        int nt = ((Integer) nodeTestOp).intValue();
1803
1804        m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), nt);
1805        m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1806
1807        consumeExpected('(');
1808
1809        if (OpCodes.NODETYPE_PI == nt)
1810        {
1811          if (!tokenIs(')'))
1812          {
1813            Literal();
1814          }
1815        }
1816
1817        consumeExpected(')');
1818      }
1819    }
1820    else
1821    {
1822
1823      // Assume name of attribute or element.
1824      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.NODENAME);
1825      m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1826
1827      if (lookahead(':', 1))
1828      {
1829        if (tokenIs('*'))
1830        {
1831          m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ELEMWILDCARD);
1832        }
1833        else
1834        {
1835          m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1836
1837          // Minimalist check for an NCName - just check first character
1838          // to distinguish from other possible tokens
1839          if (!Character.isLetter(m_tokenChar) && !tokenIs('_'))
1840          {
1841            // "Node test that matches either NCName:* or QName was expected."
1842            error(XPATHErrorResources.ER_EXPECTED_NODE_TEST, null);
1843          }
1844        }
1845
1846        nextToken();
1847        consumeExpected(':');
1848      }
1849      else
1850      {
1851        m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.EMPTY);
1852      }
1853
1854      m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1855
1856      if (tokenIs('*'))
1857      {
1858        m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ELEMWILDCARD);
1859      }
1860      else
1861      {
1862        m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1863
1864        // Minimalist check for an NCName - just check first character
1865        // to distinguish from other possible tokens
1866        if (!Character.isLetter(m_tokenChar) && !tokenIs('_'))
1867        {
1868          // "Node test that matches either NCName:* or QName was expected."
1869          error(XPATHErrorResources.ER_EXPECTED_NODE_TEST, null);
1870        }
1871      }
1872
1873      m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1874
1875      nextToken();
1876    }
1877  }
1878
1879  /**
1880   *
1881   * Predicate ::= '[' PredicateExpr ']'
1882   *
1883   *
1884   * @throws javax.xml.transform.TransformerException
1885   */
1886  protected void Predicate() throws javax.xml.transform.TransformerException
1887  {
1888
1889    if (tokenIs('['))
1890    {
1891      nextToken();
1892      PredicateExpr();
1893      consumeExpected(']');
1894    }
1895  }
1896
1897  /**
1898   *
1899   * PredicateExpr ::= Expr
1900   *
1901   *
1902   * @throws javax.xml.transform.TransformerException
1903   */
1904  protected void PredicateExpr() throws javax.xml.transform.TransformerException
1905  {
1906
1907    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1908
1909    appendOp(2, OpCodes.OP_PREDICATE);
1910    Expr();
1911
1912    // Terminate for safety.
1913    m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1914    m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1915    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1916      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1917  }
1918
1919  /**
1920   * QName ::=  (Prefix ':')? LocalPart
1921   * Prefix ::=  NCName
1922   * LocalPart ::=  NCName
1923   *
1924   * @throws javax.xml.transform.TransformerException
1925   */
1926  protected void QName() throws javax.xml.transform.TransformerException
1927  {
1928    // Namespace
1929    if(lookahead(':', 1))
1930    {
1931      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1932      m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1933
1934      nextToken();
1935      consumeExpected(':');
1936    }
1937    else
1938    {
1939      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.EMPTY);
1940      m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1941    }
1942
1943    // Local name
1944    m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1945    m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1946
1947    nextToken();
1948  }
1949
1950  /**
1951   * NCName ::=  (Letter | '_') (NCNameChar)
1952   * NCNameChar ::=  Letter | Digit | '.' | '-' | '_' | CombiningChar | Extender
1953   */
1954  protected void NCName()
1955  {
1956
1957    m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1958    m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1959
1960    nextToken();
1961  }
1962
1963  /**
1964   * The value of the Literal is the sequence of characters inside
1965   * the " or ' characters>.
1966   *
1967   * Literal  ::=  '"' [^"]* '"'
1968   * | "'" [^']* "'"
1969   *
1970   *
1971   * @throws javax.xml.transform.TransformerException
1972   */
1973  protected void Literal() throws javax.xml.transform.TransformerException
1974  {
1975
1976    int last = m_token.length() - 1;
1977    char c0 = m_tokenChar;
1978    char cX = m_token.charAt(last);
1979
1980    if (((c0 == '\"') && (cX == '\"')) || ((c0 == '\'') && (cX == '\'')))
1981    {
1982
1983      // Mutate the token to remove the quotes and have the XString object
1984      // already made.
1985      int tokenQueuePos = m_queueMark - 1;
1986
1987      m_ops.m_tokenQueue.setElementAt(null,tokenQueuePos);
1988
1989      Object obj = new XString(m_token.substring(1, last));
1990
1991      m_ops.m_tokenQueue.setElementAt(obj,tokenQueuePos);
1992
1993      // lit = m_token.substring(1, last);
1994      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), tokenQueuePos);
1995      m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1996
1997      nextToken();
1998    }
1999    else
2000    {
2001      error(XPATHErrorResources.ER_PATTERN_LITERAL_NEEDS_BE_QUOTED,
2002            new Object[]{ m_token });  //"Pattern literal ("+m_token+") needs to be quoted!");
2003    }
2004  }
2005
2006  /**
2007   *
2008   * Number ::= [0-9]+('.'[0-9]+)? | '.'[0-9]+
2009   *
2010   *
2011   * @throws javax.xml.transform.TransformerException
2012   */
2013  protected void Number() throws javax.xml.transform.TransformerException
2014  {
2015
2016    if (null != m_token)
2017    {
2018
2019      // Mutate the token to remove the quotes and have the XNumber object
2020      // already made.
2021      double num;
2022
2023      try
2024      {
2025        // XPath 1.0 does not support number in exp notation
2026        if ((m_token.indexOf('e') > -1)||(m_token.indexOf('E') > -1))
2027                throw new NumberFormatException();
2028        num = Double.valueOf(m_token).doubleValue();
2029      }
2030      catch (NumberFormatException nfe)
2031      {
2032        num = 0.0;  // to shut up compiler.
2033
2034        error(XPATHErrorResources.ER_COULDNOT_BE_FORMATTED_TO_NUMBER,
2035              new Object[]{ m_token });  //m_token+" could not be formatted to a number!");
2036      }
2037
2038      m_ops.m_tokenQueue.setElementAt(new XNumber(num),m_queueMark - 1);
2039      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
2040      m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
2041
2042      nextToken();
2043    }
2044  }
2045
2046  // ============= PATTERN FUNCTIONS =================
2047
2048  /**
2049   *
2050   * Pattern  ::=  LocationPathPattern
2051   * | Pattern '|' LocationPathPattern
2052   *
2053   *
2054   * @throws javax.xml.transform.TransformerException
2055   */
2056  protected void Pattern() throws javax.xml.transform.TransformerException
2057  {
2058
2059    while (true)
2060    {
2061      LocationPathPattern();
2062
2063      if (tokenIs('|'))
2064      {
2065        nextToken();
2066      }
2067      else
2068      {
2069        break;
2070      }
2071    }
2072  }
2073
2074  /**
2075   *
2076   *
2077   * LocationPathPattern  ::=  '/' RelativePathPattern?
2078   * | IdKeyPattern (('/' | '//') RelativePathPattern)?
2079   * | '//'? RelativePathPattern
2080   *
2081   *
2082   * @throws javax.xml.transform.TransformerException
2083   */
2084  protected void LocationPathPattern() throws javax.xml.transform.TransformerException
2085  {
2086
2087    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2088
2089    final int RELATIVE_PATH_NOT_PERMITTED = 0;
2090    final int RELATIVE_PATH_PERMITTED     = 1;
2091    final int RELATIVE_PATH_REQUIRED      = 2;
2092
2093    int relativePathStatus = RELATIVE_PATH_NOT_PERMITTED;
2094
2095    appendOp(2, OpCodes.OP_LOCATIONPATHPATTERN);
2096
2097    if (lookahead('(', 1)
2098            && (tokenIs(Keywords.FUNC_ID_STRING)
2099                || tokenIs(Keywords.FUNC_KEY_STRING)))
2100    {
2101      IdKeyPattern();
2102
2103      if (tokenIs('/'))
2104      {
2105        nextToken();
2106
2107        if (tokenIs('/'))
2108        {
2109          appendOp(4, OpCodes.MATCH_ANY_ANCESTOR);
2110
2111          nextToken();
2112        }
2113        else
2114        {
2115          appendOp(4, OpCodes.MATCH_IMMEDIATE_ANCESTOR);
2116        }
2117
2118        // Tell how long the step is without the predicate
2119        m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
2120        m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_FUNCTEST);
2121
2122        relativePathStatus = RELATIVE_PATH_REQUIRED;
2123      }
2124    }
2125    else if (tokenIs('/'))
2126    {
2127      if (lookahead('/', 1))
2128      {
2129        appendOp(4, OpCodes.MATCH_ANY_ANCESTOR);
2130
2131        // Added this to fix bug reported by Myriam for match="//x/a"
2132        // patterns.  If you don't do this, the 'x' step will think it's part
2133        // of a '//' pattern, and so will cause 'a' to be matched when it has
2134        // any ancestor that is 'x'.
2135        nextToken();
2136
2137        relativePathStatus = RELATIVE_PATH_REQUIRED;
2138      }
2139      else
2140      {
2141        appendOp(4, OpCodes.FROM_ROOT);
2142
2143        relativePathStatus = RELATIVE_PATH_PERMITTED;
2144      }
2145
2146
2147      // Tell how long the step is without the predicate
2148      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
2149      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_ROOT);
2150
2151      nextToken();
2152    }
2153    else
2154    {
2155      relativePathStatus = RELATIVE_PATH_REQUIRED;
2156    }
2157
2158    if (relativePathStatus != RELATIVE_PATH_NOT_PERMITTED)
2159    {
2160      if (!tokenIs('|') && (null != m_token))
2161      {
2162        RelativePathPattern();
2163      }
2164      else if (relativePathStatus == RELATIVE_PATH_REQUIRED)
2165      {
2166        // "A relative path pattern was expected."
2167        error(XPATHErrorResources.ER_EXPECTED_REL_PATH_PATTERN, null);
2168      }
2169    }
2170
2171    // Terminate for safety.
2172    m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
2173    m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
2174    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
2175      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
2176  }
2177
2178  /**
2179   *
2180   * IdKeyPattern  ::=  'id' '(' Literal ')'
2181   * | 'key' '(' Literal ',' Literal ')'
2182   * (Also handle doc())
2183   *
2184   *
2185   * @throws javax.xml.transform.TransformerException
2186   */
2187  protected void IdKeyPattern() throws javax.xml.transform.TransformerException
2188  {
2189    FunctionCall();
2190  }
2191
2192  /**
2193   *
2194   * RelativePathPattern  ::=  StepPattern
2195   * | RelativePathPattern '/' StepPattern
2196   * | RelativePathPattern '//' StepPattern
2197   *
2198   * @throws javax.xml.transform.TransformerException
2199   */
2200  protected void RelativePathPattern()
2201              throws javax.xml.transform.TransformerException
2202  {
2203
2204    // Caller will have consumed any '/' or '//' preceding the
2205    // RelativePathPattern, so let StepPattern know it can't begin with a '/'
2206    boolean trailingSlashConsumed = StepPattern(false);
2207
2208    while (tokenIs('/'))
2209    {
2210      nextToken();
2211
2212      // StepPattern() may consume first slash of pair in "a//b" while
2213      // processing StepPattern "a".  On next iteration, let StepPattern know
2214      // that happened, so it doesn't match ill-formed patterns like "a///b".
2215      trailingSlashConsumed = StepPattern(!trailingSlashConsumed);
2216    }
2217  }
2218
2219  /**
2220   *
2221   * StepPattern  ::=  AbbreviatedNodeTestStep
2222   *
2223   * @param isLeadingSlashPermitted a boolean indicating whether a slash can
2224   *        appear at the start of this step
2225   *
2226   * @return boolean indicating whether a slash following the step was consumed
2227   *
2228   * @throws javax.xml.transform.TransformerException
2229   */
2230  protected boolean StepPattern(boolean isLeadingSlashPermitted)
2231            throws javax.xml.transform.TransformerException
2232  {
2233    return AbbreviatedNodeTestStep(isLeadingSlashPermitted);
2234  }
2235
2236  /**
2237   *
2238   * AbbreviatedNodeTestStep    ::=    '@'? NodeTest Predicate
2239   *
2240   * @param isLeadingSlashPermitted a boolean indicating whether a slash can
2241   *        appear at the start of this step
2242   *
2243   * @return boolean indicating whether a slash following the step was consumed
2244   *
2245   * @throws javax.xml.transform.TransformerException
2246   */
2247  protected boolean AbbreviatedNodeTestStep(boolean isLeadingSlashPermitted)
2248            throws javax.xml.transform.TransformerException
2249  {
2250
2251    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2252    int axesType;
2253
2254    // The next blocks guarantee that a MATCH_XXX will be added.
2255    int matchTypePos = -1;
2256
2257    if (tokenIs('@'))
2258    {
2259      axesType = OpCodes.MATCH_ATTRIBUTE;
2260
2261      appendOp(2, axesType);
2262      nextToken();
2263    }
2264    else if (this.lookahead("::", 1))
2265    {
2266      if (tokenIs("attribute"))
2267      {
2268        axesType = OpCodes.MATCH_ATTRIBUTE;
2269
2270        appendOp(2, axesType);
2271      }
2272      else if (tokenIs("child"))
2273      {
2274        matchTypePos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2275        axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR;
2276
2277        appendOp(2, axesType);
2278      }
2279      else
2280      {
2281        axesType = -1;
2282
2283        this.error(XPATHErrorResources.ER_AXES_NOT_ALLOWED,
2284                   new Object[]{ this.m_token });
2285      }
2286
2287      nextToken();
2288      nextToken();
2289    }
2290    else if (tokenIs('/'))
2291    {
2292      if (!isLeadingSlashPermitted)
2293      {
2294        // "A step was expected in the pattern, but '/' was encountered."
2295        error(XPATHErrorResources.ER_EXPECTED_STEP_PATTERN, null);
2296      }
2297      axesType = OpCodes.MATCH_ANY_ANCESTOR;
2298
2299      appendOp(2, axesType);
2300      nextToken();
2301    }
2302    else
2303    {
2304      matchTypePos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2305      axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR;
2306
2307      appendOp(2, axesType);
2308    }
2309
2310    // Make room for telling how long the step is without the predicate
2311    m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
2312
2313    NodeTest(axesType);
2314
2315    // Tell how long the step is without the predicate
2316    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
2317      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
2318
2319    while (tokenIs('['))
2320    {
2321      Predicate();
2322    }
2323
2324    boolean trailingSlashConsumed;
2325
2326    // For "a//b", where "a" is current step, we need to mark operation of
2327    // current step as "MATCH_ANY_ANCESTOR".  Then we'll consume the first
2328    // slash and subsequent step will be treated as a MATCH_IMMEDIATE_ANCESTOR
2329    // (unless it too is followed by '//'.)
2330    //
2331    // %REVIEW%  Following is what happens today, but I'm not sure that's
2332    // %REVIEW%  correct behaviour.  Perhaps no valid case could be constructed
2333    // %REVIEW%  where it would matter?
2334    //
2335    // If current step is on the attribute axis (e.g., "@x//b"), we won't
2336    // change the current step, and let following step be marked as
2337    // MATCH_ANY_ANCESTOR on next call instead.
2338    if ((matchTypePos > -1) && tokenIs('/') && lookahead('/', 1))
2339    {
2340      m_ops.setOp(matchTypePos, OpCodes.MATCH_ANY_ANCESTOR);
2341
2342      nextToken();
2343
2344      trailingSlashConsumed = true;
2345    }
2346    else
2347    {
2348      trailingSlashConsumed = false;
2349    }
2350
2351    // Tell how long the entire step is.
2352    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
2353      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
2354
2355    return trailingSlashConsumed;
2356  }
2357}
2358