001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    /*
019     * $Id: XPathParser.java 468655 2006-10-28 07:12:06Z minchau $
020     */
021    package org.apache.xpath.compiler;
022    
023    import javax.xml.transform.ErrorListener;
024    import javax.xml.transform.TransformerException;
025    
026    import org.apache.xalan.res.XSLMessages;
027    import org.apache.xml.utils.PrefixResolver;
028    import org.apache.xpath.XPathProcessorException;
029    import org.apache.xpath.domapi.XPathStylesheetDOM3Exception;
030    import org.apache.xpath.objects.XNumber;
031    import org.apache.xpath.objects.XString;
032    import org.apache.xpath.res.XPATHErrorResources;
033    
034    /**
035     * Tokenizes and parses XPath expressions. This should really be named
036     * XPathParserImpl, and may be renamed in the future.
037     * @xsl.usage general
038     */
039    public class XPathParser
040    {
041            // %REVIEW% Is there a better way of doing this?
042            // Upside is minimum object churn. Downside is that we don't have a useful
043            // backtrace in the exception itself -- but we don't expect to need one.
044            static public final String CONTINUE_AFTER_FATAL_ERROR="CONTINUE_AFTER_FATAL_ERROR";
045    
046      /**
047       * The XPath to be processed.
048       */
049      private OpMap m_ops;
050    
051      /**
052       * The next token in the pattern.
053       */
054      transient String m_token;
055    
056      /**
057       * The first char in m_token, the theory being that this
058       * is an optimization because we won't have to do charAt(0) as
059       * often.
060       */
061      transient char m_tokenChar = 0;
062    
063      /**
064       * The position in the token queue is tracked by m_queueMark.
065       */
066      int m_queueMark = 0;
067    
068      /**
069       * Results from checking FilterExpr syntax
070       */
071      protected final static int FILTER_MATCH_FAILED     = 0;
072      protected final static int FILTER_MATCH_PRIMARY    = 1;
073      protected final static int FILTER_MATCH_PREDICATES = 2;
074    
075      /**
076       * The parser constructor.
077       */
078      public XPathParser(ErrorListener errorListener, javax.xml.transform.SourceLocator sourceLocator)
079      {
080        m_errorListener = errorListener;
081        m_sourceLocator = sourceLocator;
082      }
083    
084      /**
085       * The prefix resolver to map prefixes to namespaces in the OpMap.
086       */
087      PrefixResolver m_namespaceContext;
088    
089      /**
090       * Given an string, init an XPath object for selections,
091       * in order that a parse doesn't
092       * have to be done each time the expression is evaluated.
093       * 
094       * @param compiler The compiler object.
095       * @param expression A string conforming to the XPath grammar.
096       * @param namespaceContext An object that is able to resolve prefixes in
097       * the XPath to namespaces.
098       *
099       * @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    }