KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > xpath > compiler > XPathParser


1 /*
2  * Copyright 1999-2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 /*
17  * $Id: XPathParser.java,v 1.28 2004/02/17 04:32:49 minchau Exp $
18  */

19 package org.apache.xpath.compiler;
20
21 import javax.xml.transform.ErrorListener JavaDoc;
22 import javax.xml.transform.TransformerException JavaDoc;
23
24 import org.apache.xalan.res.XSLMessages;
25 import org.apache.xml.utils.PrefixResolver;
26 import org.apache.xpath.XPathProcessorException;
27 import org.apache.xpath.objects.XNumber;
28 import org.apache.xpath.objects.XString;
29 import org.apache.xpath.res.XPATHErrorResources;
30
31 /**
32  * Tokenizes and parses XPath expressions. This should really be named
33  * XPathParserImpl, and may be renamed in the future.
34  * @xsl.usage general
35  */

36 public class XPathParser
37 {
38     // %REVIEW% Is there a better way of doing this?
39
// Upside is minimum object churn. Downside is that we don't have a useful
40
// backtrace in the exception itself -- but we don't expect to need one.
41
static public final String JavaDoc CONTINUE_AFTER_FATAL_ERROR="CONTINUE_AFTER_FATAL_ERROR";
42
43   /**
44    * The XPath to be processed.
45    */

46   private OpMap m_ops;
47
48   /**
49    * The next token in the pattern.
50    */

51   transient String JavaDoc m_token;
52
53   /**
54    * The first char in m_token, the theory being that this
55    * is an optimization because we won't have to do charAt(0) as
56    * often.
57    */

58   transient char m_tokenChar = 0;
59
60   /**
61    * The position in the token queue is tracked by m_queueMark.
62    */

63   int m_queueMark = 0;
64
65   /**
66    * Results from checking FilterExpr syntax
67    */

68   protected final static int FILTER_MATCH_FAILED = 0;
69   protected final static int FILTER_MATCH_PRIMARY = 1;
70   protected final static int FILTER_MATCH_PREDICATES = 2;
71
72   /**
73    * The parser constructor.
74    */

75   public XPathParser(ErrorListener JavaDoc errorListener, javax.xml.transform.SourceLocator JavaDoc sourceLocator)
76   {
77     m_errorListener = errorListener;
78     m_sourceLocator = sourceLocator;
79   }
80
81   /**
82    * The prefix resolver to map prefixes to namespaces in the OpMap.
83    */

84   PrefixResolver m_namespaceContext;
85
86   /**
87    * Given an string, init an XPath object for selections,
88    * in order that a parse doesn't
89    * have to be done each time the expression is evaluated.
90    *
91    * @param compiler The compiler object.
92    * @param expression A string conforming to the XPath grammar.
93    * @param namespaceContext An object that is able to resolve prefixes in
94    * the XPath to namespaces.
95    *
96    * @throws javax.xml.transform.TransformerException
97    */

98   public void initXPath(
99           Compiler JavaDoc compiler, String JavaDoc expression, PrefixResolver namespaceContext)
100             throws javax.xml.transform.TransformerException JavaDoc
101   {
102
103     m_ops = compiler;
104     m_namespaceContext = namespaceContext;
105
106     Lexer lexer = new Lexer(compiler, namespaceContext, this);
107
108     lexer.tokenize(expression);
109
110     m_ops.setOp(0,OpCodes.OP_XPATH);
111     m_ops.setOp(OpMap.MAPINDEX_LENGTH,2);
112     
113     
114     // Patch for Christine's gripe. She wants her errorHandler to return from
115
// a fatal error and continue trying to parse, rather than throwing an exception.
116
// Without the patch, that put us into an endless loop.
117
//
118
// %REVIEW% Is there a better way of doing this?
119
// %REVIEW% Are there any other cases which need the safety net?
120
// (and if so do we care right now, or should we rewrite the XPath
121
// grammar engine and can fix it at that time?)
122
try {
123
124       nextToken();
125       Expr();
126
127       if (null != m_token)
128       {
129         String JavaDoc extraTokens = "";
130
131         while (null != m_token)
132         {
133           extraTokens += "'" + m_token + "'";
134
135           nextToken();
136
137           if (null != m_token)
138             extraTokens += ", ";
139         }
140
141         error(XPATHErrorResources.ER_EXTRA_ILLEGAL_TOKENS,
142               new Object JavaDoc[]{ extraTokens }); //"Extra illegal tokens: "+extraTokens);
143
}
144
145     }
146     catch (org.apache.xpath.XPathProcessorException e)
147     {
148       if(CONTINUE_AFTER_FATAL_ERROR.equals(e.getMessage()))
149       {
150         // What I _want_ to do is null out this XPath.
151
// I doubt this has the desired effect, but I'm not sure what else to do.
152
// %REVIEW%!!!
153
initXPath(compiler, "/..", namespaceContext);
154       }
155       else
156         throw e;
157     }
158
159     compiler.shrink();
160   }
161
162   /**
163    * Given an string, init an XPath object for pattern matches,
164    * in order that a parse doesn't
165    * have to be done each time the expression is evaluated.
166    * @param compiler The XPath object to be initialized.
167    * @param expression A String representing the XPath.
168    * @param namespaceContext An object that is able to resolve prefixes in
169    * the XPath to namespaces.
170    *
171    * @throws javax.xml.transform.TransformerException
172    */

173   public void initMatchPattern(
174           Compiler JavaDoc compiler, String JavaDoc expression, PrefixResolver namespaceContext)
175             throws javax.xml.transform.TransformerException JavaDoc
176   {
177
178     m_ops = compiler;
179     m_namespaceContext = namespaceContext;
180
181     Lexer lexer = new Lexer(compiler, namespaceContext, this);
182
183     lexer.tokenize(expression);
184
185     m_ops.setOp(0, OpCodes.OP_MATCHPATTERN);
186     m_ops.setOp(OpMap.MAPINDEX_LENGTH, 2);
187
188     nextToken();
189     Pattern();
190
191     if (null != m_token)
192     {
193       String JavaDoc extraTokens = "";
194
195       while (null != m_token)
196       {
197         extraTokens += "'" + m_token + "'";
198
199         nextToken();
200
201         if (null != m_token)
202           extraTokens += ", ";
203       }
204
205       error(XPATHErrorResources.ER_EXTRA_ILLEGAL_TOKENS,
206             new Object JavaDoc[]{ extraTokens }); //"Extra illegal tokens: "+extraTokens);
207
}
208
209     // Terminate for safety.
210
m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
211     m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH)+1);
212
213     m_ops.shrink();
214   }
215
216   /** The error listener where syntax errors are to be sent.
217    */

218   private ErrorListener JavaDoc m_errorListener;
219   
220   /** The source location of the XPath. */
221   javax.xml.transform.SourceLocator JavaDoc m_sourceLocator;
222
223   /**
224    * Allow an application to register an error event handler, where syntax
225    * errors will be sent. If the error listener is not set, syntax errors
226    * will be sent to System.err.
227    *
228    * @param handler Reference to error listener where syntax errors will be
229    * sent.
230    */

231   public void setErrorHandler(ErrorListener JavaDoc handler)
232   {
233     m_errorListener = handler;
234   }
235
236   /**
237    * Return the current error listener.
238    *
239    * @return The error listener, which should not normally be null, but may be.
240    */

241   public ErrorListener JavaDoc getErrorListener()
242   {
243     return m_errorListener;
244   }
245
246   /**
247    * Check whether m_token matches the target string.
248    *
249    * @param s A string reference or null.
250    *
251    * @return If m_token is null, returns false (or true if s is also null), or
252    * return true if the current token matches the string, else false.
253    */

254   final boolean tokenIs(String JavaDoc s)
255   {
256     return (m_token != null) ? (m_token.equals(s)) : (s == null);
257   }
258
259   /**
260    * Check whether m_tokenChar==c.
261    *
262    * @param c A character to be tested.
263    *
264    * @return If m_token is null, returns false, or return true if c matches
265    * the current token.
266    */

267   final boolean tokenIs(char c)
268   {
269     return (m_token != null) ? (m_tokenChar == c) : false;
270   }
271
272   /**
273    * Look ahead of the current token in order to
274    * make a branching decision.
275    *
276    * @param c the character to be tested for.
277    * @param n number of tokens to look ahead. Must be
278    * greater than 1.
279    *
280    * @return true if the next token matches the character argument.
281    */

282   final boolean lookahead(char c, int n)
283   {
284
285     int pos = (m_queueMark + n);
286     boolean b;
287
288     if ((pos <= m_ops.getTokenQueueSize()) && (pos > 0)
289             && (m_ops.getTokenQueueSize() != 0))
290     {
291       String JavaDoc tok = ((String JavaDoc) m_ops.m_tokenQueue.elementAt(pos - 1));
292
293       b = (tok.length() == 1) ? (tok.charAt(0) == c) : false;
294     }
295     else
296     {
297       b = false;
298     }
299
300     return b;
301   }
302
303   /**
304    * Look behind the first character of the current token in order to
305    * make a branching decision.
306    *
307    * @param c the character to compare it to.
308    * @param n number of tokens to look behind. Must be
309    * greater than 1. Note that the look behind terminates
310    * at either the beginning of the string or on a '|'
311    * character. Because of this, this method should only
312    * be used for pattern matching.
313    *
314    * @return true if the token behind the current token matches the character
315    * argument.
316    */

317   private final boolean lookbehind(char c, int n)
318   {
319
320     boolean isToken;
321     int lookBehindPos = m_queueMark - (n + 1);
322
323     if (lookBehindPos >= 0)
324     {
325       String JavaDoc lookbehind = (String JavaDoc) m_ops.m_tokenQueue.elementAt(lookBehindPos);
326
327       if (lookbehind.length() == 1)
328       {
329         char c0 = (lookbehind == null) ? '|' : lookbehind.charAt(0);
330
331         isToken = (c0 == '|') ? false : (c0 == c);
332       }
333       else
334       {
335         isToken = false;
336       }
337     }
338     else
339     {
340       isToken = false;
341     }
342
343     return isToken;
344   }
345
346   /**
347    * look behind the current token in order to
348    * see if there is a useable token.
349    *
350    * @param n number of tokens to look behind. Must be
351    * greater than 1. Note that the look behind terminates
352    * at either the beginning of the string or on a '|'
353    * character. Because of this, this method should only
354    * be used for pattern matching.
355    *
356    * @return true if look behind has a token, false otherwise.
357    */

358   private final boolean lookbehindHasToken(int n)
359   {
360
361     boolean hasToken;
362
363     if ((m_queueMark - n) > 0)
364     {
365       String JavaDoc lookbehind = (String JavaDoc) m_ops.m_tokenQueue.elementAt(m_queueMark - (n - 1));
366       char c0 = (lookbehind == null) ? '|' : lookbehind.charAt(0);
367
368       hasToken = (c0 == '|') ? false : true;
369     }
370     else
371     {
372       hasToken = false;
373     }
374
375     return hasToken;
376   }
377
378   /**
379    * Look ahead of the current token in order to
380    * make a branching decision.
381    *
382    * @param s the string to compare it to.
383    * @param n number of tokens to lookahead. Must be
384    * greater than 1.
385    *
386    * @return true if the token behind the current token matches the string
387    * argument.
388    */

389   private final boolean lookahead(String JavaDoc s, int n)
390   {
391
392     boolean isToken;
393
394     if ((m_queueMark + n) <= m_ops.getTokenQueueSize())
395     {
396       String JavaDoc lookahead = (String JavaDoc) m_ops.m_tokenQueue.elementAt(m_queueMark + (n - 1));
397
398       isToken = (lookahead != null) ? lookahead.equals(s) : (s == null);
399     }
400     else
401     {
402       isToken = (null == s);
403     }
404
405     return isToken;
406   }
407
408   /**
409    * Retrieve the next token from the command and
410    * store it in m_token string.
411    */

412   private final void nextToken()
413   {
414
415     if (m_queueMark < m_ops.getTokenQueueSize())
416     {
417       m_token = (String JavaDoc) m_ops.m_tokenQueue.elementAt(m_queueMark++);
418       m_tokenChar = m_token.charAt(0);
419     }
420     else
421     {
422       m_token = null;
423       m_tokenChar = 0;
424     }
425   }
426
427   /**
428    * Retrieve a token relative to the current token.
429    *
430    * @param i Position relative to current token.
431    *
432    * @return The string at the given index, or null if the index is out
433    * of range.
434    */

435   private final String JavaDoc getTokenRelative(int i)
436   {
437
438     String JavaDoc tok;
439     int relative = m_queueMark + i;
440
441     if ((relative > 0) && (relative < m_ops.getTokenQueueSize()))
442     {
443       tok = (String JavaDoc) m_ops.m_tokenQueue.elementAt(relative);
444     }
445     else
446     {
447       tok = null;
448     }
449
450     return tok;
451   }
452
453   /**
454    * Retrieve the previous token from the command and
455    * store it in m_token string.
456    */

457   private final void prevToken()
458   {
459
460     if (m_queueMark > 0)
461     {
462       m_queueMark--;
463
464       m_token = (String JavaDoc) m_ops.m_tokenQueue.elementAt(m_queueMark);
465       m_tokenChar = m_token.charAt(0);
466     }
467     else
468     {
469       m_token = null;
470       m_tokenChar = 0;
471     }
472   }
473
474   /**
475    * Consume an expected token, throwing an exception if it
476    * isn't there.
477    *
478    * @param expected The string to be expected.
479    *
480    * @throws javax.xml.transform.TransformerException
481    */

482   private final void consumeExpected(String JavaDoc expected)
483           throws javax.xml.transform.TransformerException JavaDoc
484   {
485
486     if (tokenIs(expected))
487     {
488       nextToken();
489     }
490     else
491     {
492       error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND, new Object JavaDoc[]{ expected,
493                                                                      m_token }); //"Expected "+expected+", but found: "+m_token);
494

495       // Patch for Christina's gripe. She wants her errorHandler to return from
496
// this error and continue trying to parse, rather than throwing an exception.
497
// Without the patch, that put us into an endless loop.
498
throw new XPathProcessorException(CONTINUE_AFTER_FATAL_ERROR);
499     }
500   }
501
502   /**
503    * Consume an expected token, throwing an exception if it
504    * isn't there.
505    *
506    * @param expected the character to be expected.
507    *
508    * @throws javax.xml.transform.TransformerException
509    */

510   private final void consumeExpected(char expected)
511           throws javax.xml.transform.TransformerException JavaDoc
512   {
513
514     if (tokenIs(expected))
515     {
516       nextToken();
517     }
518     else
519     {
520       error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND,
521             new Object JavaDoc[]{ String.valueOf(expected),
522                           m_token }); //"Expected "+expected+", but found: "+m_token);
523

524       // Patch for Christina's gripe. She wants her errorHandler to return from
525
// this error and continue trying to parse, rather than throwing an exception.
526
// Without the patch, that put us into an endless loop.
527
throw new XPathProcessorException(CONTINUE_AFTER_FATAL_ERROR);
528     }
529   }
530
531   /**
532    * Warn the user of a problem.
533    *
534    * @param msg An error msgkey that corresponds to one of the constants found
535    * in {@link org.apache.xpath.res.XPATHErrorResources}, which is
536    * a key for a format string.
537    * @param args An array of arguments represented in the format string, which
538    * may be null.
539    *
540    * @throws TransformerException if the current ErrorListoner determines to
541    * throw an exception.
542    */

543   void warn(String JavaDoc msg, Object JavaDoc[] args) throws TransformerException JavaDoc
544   {
545
546     String JavaDoc fmsg = XSLMessages.createXPATHWarning(msg, args);
547     ErrorListener JavaDoc ehandler = this.getErrorListener();
548
549     if (null != ehandler)
550     {
551       // TO DO: Need to get stylesheet Locator from here.
552
ehandler.warning(new TransformerException JavaDoc(fmsg, m_sourceLocator));
553     }
554     else
555     {
556       // Should never happen.
557
System.err.println(fmsg);
558     }
559   }
560
561   /**
562    * Notify the user of an assertion error, and probably throw an
563    * exception.
564    *
565    * @param b If false, a runtime exception will be thrown.
566    * @param msg The assertion message, which should be informative.
567    *
568    * @throws RuntimeException if the b argument is false.
569    */

570   private void assertion(boolean b, String JavaDoc msg)
571   {
572
573     if (!b)
574     {
575       String JavaDoc fMsg = XSLMessages.createXPATHMessage(
576         XPATHErrorResources.ER_INCORRECT_PROGRAMMER_ASSERTION,
577         new Object JavaDoc[]{ msg });
578
579       throw new RuntimeException JavaDoc(fMsg);
580     }
581   }
582
583   /**
584    * Notify the user of an error, and probably throw an
585    * exception.
586    *
587    * @param msg An error msgkey that corresponds to one of the constants found
588    * in {@link org.apache.xpath.res.XPATHErrorResources}, which is
589    * a key for a format string.
590    * @param args An array of arguments represented in the format string, which
591    * may be null.
592    *
593    * @throws TransformerException if the current ErrorListoner determines to
594    * throw an exception.
595    */

596   void error(String JavaDoc msg, Object JavaDoc[] args) throws TransformerException JavaDoc
597   {
598
599     String JavaDoc fmsg = XSLMessages.createXPATHMessage(msg, args);
600     ErrorListener JavaDoc ehandler = this.getErrorListener();
601
602     TransformerException JavaDoc te = new TransformerException JavaDoc(fmsg, m_sourceLocator);
603     if (null != ehandler)
604     {
605       // TO DO: Need to get stylesheet Locator from here.
606
ehandler.fatalError(te);
607     }
608     else
609     {
610       // System.err.println(fmsg);
611
throw te;
612     }
613   }
614
615   /**
616    * Dump the remaining token queue.
617    * Thanks to Craig for this.
618    *
619    * @return A dump of the remaining token queue, which may be appended to
620    * an error message.
621    */

622   protected String JavaDoc dumpRemainingTokenQueue()
623   {
624
625     int q = m_queueMark;
626     String JavaDoc returnMsg;
627
628     if (q < m_ops.getTokenQueueSize())
629     {
630       String JavaDoc msg = "\n Remaining tokens: (";
631
632       while (q < m_ops.getTokenQueueSize())
633       {
634         String JavaDoc t = (String JavaDoc) m_ops.m_tokenQueue.elementAt(q++);
635
636         msg += (" '" + t + "'");
637       }
638
639       returnMsg = msg + ")";
640     }
641     else
642     {
643       returnMsg = "";
644     }
645
646     return returnMsg;
647   }
648
649   /**
650    * Given a string, return the corresponding function token.
651    *
652    * @param key A local name of a function.
653    *
654    * @return The function ID, which may correspond to one of the FUNC_XXX
655    * values found in {@link org.apache.xpath.compiler.FunctionTable}, but may
656    * be a value installed by an external module.
657    */

658   final int getFunctionToken(String JavaDoc key)
659   {
660
661     int tok;
662
663     try
664     {
665       tok = ((Integer JavaDoc) (Keywords.m_functions.get(key))).intValue();
666     }
667     catch (NullPointerException JavaDoc npe)
668     {
669       tok = -1;
670     }
671     catch (ClassCastException JavaDoc cce)
672     {
673       tok = -1;
674     }
675
676     return tok;
677   }
678
679   /**
680    * Insert room for operation. This will NOT set
681    * the length value of the operation, but will update
682    * the length value for the total expression.
683    *
684    * @param pos The position where the op is to be inserted.
685    * @param length The length of the operation space in the op map.
686    * @param op The op code to the inserted.
687    */

688   void insertOp(int pos, int length, int op)
689   {
690
691     int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
692
693     for (int i = totalLen - 1; i >= pos; i--)
694     {
695       m_ops.setOp(i + length, m_ops.getOp(i));
696     }
697
698     m_ops.setOp(pos,op);
699     m_ops.setOp(OpMap.MAPINDEX_LENGTH,totalLen + length);
700   }
701
702   /**
703    * Insert room for operation. This WILL set
704    * the length value of the operation, and will update
705    * the length value for the total expression.
706    *
707    * @param length The length of the operation.
708    * @param op The op code to the inserted.
709    */

710   void appendOp(int length, int op)
711   {
712
713     int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
714
715     m_ops.setOp(totalLen, op);
716     m_ops.setOp(totalLen + OpMap.MAPINDEX_LENGTH, length);
717     m_ops.setOp(OpMap.MAPINDEX_LENGTH, totalLen + length);
718   }
719
720   // ============= EXPRESSIONS FUNCTIONS =================
721

722   /**
723    *
724    *
725    * Expr ::= OrExpr
726    *
727    *
728    * @throws javax.xml.transform.TransformerException
729    */

730   protected void Expr() throws javax.xml.transform.TransformerException JavaDoc
731   {
732     OrExpr();
733   }
734
735   /**
736    *
737    *
738    * OrExpr ::= AndExpr
739    * | OrExpr 'or' AndExpr
740    *
741    *
742    * @throws javax.xml.transform.TransformerException
743    */

744   protected void OrExpr() throws javax.xml.transform.TransformerException JavaDoc
745   {
746
747     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
748
749     AndExpr();
750
751     if ((null != m_token) && tokenIs("or"))
752     {
753       nextToken();
754       insertOp(opPos, 2, OpCodes.OP_OR);
755       OrExpr();
756
757       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
758         m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
759     }
760   }
761
762   /**
763    *
764    *
765    * AndExpr ::= EqualityExpr
766    * | AndExpr 'and' EqualityExpr
767    *
768    *
769    * @throws javax.xml.transform.TransformerException
770    */

771   protected void AndExpr() throws javax.xml.transform.TransformerException JavaDoc
772   {
773
774     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
775
776     EqualityExpr(-1);
777
778     if ((null != m_token) && tokenIs("and"))
779     {
780       nextToken();
781       insertOp(opPos, 2, OpCodes.OP_AND);
782       AndExpr();
783
784       m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
785         m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
786     }
787   }
788
789   /**
790    *
791    * @returns an Object which is either a String, a Number, a Boolean, or a vector
792    * of nodes.
793    *
794    * EqualityExpr ::= RelationalExpr
795    * | EqualityExpr '=' RelationalExpr
796    *
797    *
798    * @param addPos Position where expression is to be added, or -1 for append.
799    *
800    * @return the position at the end of the equality expression.
801    *
802    * @throws javax.xml.transform.TransformerException
803    */

804   protected int EqualityExpr(int addPos) throws javax.xml.transform.TransformerException JavaDoc
805   {
806
807     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
808
809     if (-1 == addPos)
810       addPos = opPos;
811
812     RelationalExpr(-1);
813
814     if (null != m_token)
815     {
816       if (tokenIs('!') && lookahead('=', 1))
817       {
818         nextToken();
819         nextToken();
820         insertOp(addPos, 2, OpCodes.OP_NOTEQUALS);
821
822         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
823
824         addPos = EqualityExpr(addPos);
825         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
826           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
827         addPos += 2;
828       }
829       else if (tokenIs('='))
830       {
831         nextToken();
832         insertOp(addPos, 2, OpCodes.OP_EQUALS);
833
834         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
835
836         addPos = EqualityExpr(addPos);
837         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
838           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
839         addPos += 2;
840       }
841     }
842
843     return addPos;
844   }
845
846   /**
847    * .
848    * @returns an Object which is either a String, a Number, a Boolean, or a vector
849    * of nodes.
850    *
851    * RelationalExpr ::= AdditiveExpr
852    * | RelationalExpr '<' AdditiveExpr
853    * | RelationalExpr '>' AdditiveExpr
854    * | RelationalExpr '<=' AdditiveExpr
855    * | RelationalExpr '>=' AdditiveExpr
856    *
857    *
858    * @param addPos Position where expression is to be added, or -1 for append.
859    *
860    * @return the position at the end of the relational expression.
861    *
862    * @throws javax.xml.transform.TransformerException
863    */

864   protected int RelationalExpr(int addPos) throws javax.xml.transform.TransformerException JavaDoc
865   {
866
867     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
868
869     if (-1 == addPos)
870       addPos = opPos;
871
872     AdditiveExpr(-1);
873
874     if (null != m_token)
875     {
876       if (tokenIs('<'))
877       {
878         nextToken();
879
880         if (tokenIs('='))
881         {
882           nextToken();
883           insertOp(addPos, 2, OpCodes.OP_LTE);
884         }
885         else
886         {
887           insertOp(addPos, 2, OpCodes.OP_LT);
888         }
889
890         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
891
892         addPos = RelationalExpr(addPos);
893         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
894           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
895         addPos += 2;
896       }
897       else if (tokenIs('>'))
898       {
899         nextToken();
900
901         if (tokenIs('='))
902         {
903           nextToken();
904           insertOp(addPos, 2, OpCodes.OP_GTE);
905         }
906         else
907         {
908           insertOp(addPos, 2, OpCodes.OP_GT);
909         }
910
911         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
912
913         addPos = RelationalExpr(addPos);
914         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
915           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
916         addPos += 2;
917       }
918     }
919
920     return addPos;
921   }
922
923   /**
924    * This has to handle construction of the operations so that they are evaluated
925    * in pre-fix order. So, for 9+7-6, instead of |+|9|-|7|6|, this needs to be
926    * evaluated as |-|+|9|7|6|.
927    *
928    * AdditiveExpr ::= MultiplicativeExpr
929    * | AdditiveExpr '+' MultiplicativeExpr
930    * | AdditiveExpr '-' MultiplicativeExpr
931    *
932    *
933    * @param addPos Position where expression is to be added, or -1 for append.
934    *
935    * @return the position at the end of the equality expression.
936    *
937    * @throws javax.xml.transform.TransformerException
938    */

939   protected int AdditiveExpr(int addPos) throws javax.xml.transform.TransformerException JavaDoc
940   {
941
942     int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
943
944     if (-1 == addPos)
945       addPos = opPos;
946
947     MultiplicativeExpr(-1);
948
949     if (null != m_token)
950     {
951       if (tokenIs('+'))
952       {
953         nextToken();
954         insertOp(addPos, 2, OpCodes.OP_PLUS);
955
956         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
957
958         addPos = AdditiveExpr(addPos);
959         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
960           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
961         addPos += 2;
962       }
963       else if (tokenIs('-'))
964       {
965         nextToken();
966         insertOp(addPos, 2, OpCodes.OP_MINUS);
967
968         int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
969
970         addPos = AdditiveExpr(addPos);
971         m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
972           m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
973         addPos += 2;
974       }
975     }
976
977     return addPos;
978   }
979
980   /**
981    * This has to handle construction of the operations so that they are evaluated
982    * in pre-fix order. So, for 9+7-6, instead of |+|9|-|7|6|, this needs to be
983    * evaluated as |-|+|9|7|6|.
984    *
985    * MultiplicativeExpr ::= UnaryExpr
986    * | MultiplicativeExpr MultiplyOperator UnaryExpr
987    * | MultiplicativeExpr 'div' UnaryExpr
988    * | MultiplicativeExpr 'mod' UnaryExpr
989    * | MultiplicativeExpr 'quo' UnaryExpr
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 MultiplicativeExpr(int addPos) throws javax.xml.transform.TransformerException JavaDoc
998   {
999
1000    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1001
1002    if (-1 == addPos)
1003      addPos = opPos;
1004
1005    UnaryExpr();
1006
1007    if (null != m_token)
1008    {
1009      if (tokenIs('*'))
1010      {
1011        nextToken();
1012        insertOp(addPos, 2, OpCodes.OP_MULT);
1013
1014        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1015
1016        addPos = MultiplicativeExpr(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("div"))
1022      {
1023        nextToken();
1024        insertOp(addPos, 2, OpCodes.OP_DIV);
1025
1026        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1027
1028        addPos = MultiplicativeExpr(addPos);
1029        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1030          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1031        addPos += 2;
1032      }
1033      else if (tokenIs("mod"))
1034      {
1035        nextToken();
1036        insertOp(addPos, 2, OpCodes.OP_MOD);
1037
1038        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1039
1040        addPos = MultiplicativeExpr(addPos);
1041        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1042          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1043        addPos += 2;
1044      }
1045      else if (tokenIs("quo"))
1046      {
1047        nextToken();
1048        insertOp(addPos, 2, OpCodes.OP_QUO);
1049
1050        int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1051
1052        addPos = MultiplicativeExpr(addPos);
1053        m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1054          m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1055        addPos += 2;
1056      }
1057    }
1058
1059    return addPos;
1060  }
1061
1062  /**
1063   *
1064   * UnaryExpr ::= UnionExpr
1065   * | '-' UnaryExpr
1066   *
1067   *
1068   * @throws javax.xml.transform.TransformerException
1069   */

1070  protected void UnaryExpr() throws javax.xml.transform.TransformerException JavaDoc
1071  {
1072
1073    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1074    boolean isNeg = false;
1075
1076    if (m_tokenChar == '-')
1077    {
1078      nextToken();
1079      appendOp(2, OpCodes.OP_NEG);
1080
1081      isNeg = true;
1082    }
1083
1084    UnionExpr();
1085
1086    if (isNeg)
1087      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1088        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1089  }
1090
1091  /**
1092   *
1093   * StringExpr ::= Expr
1094   *
1095   *
1096   * @throws javax.xml.transform.TransformerException
1097   */

1098  protected void StringExpr() throws javax.xml.transform.TransformerException JavaDoc
1099  {
1100
1101    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1102
1103    appendOp(2, OpCodes.OP_STRING);
1104    Expr();
1105
1106    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1107      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1108  }
1109
1110  /**
1111   *
1112   *
1113   * StringExpr ::= Expr
1114   *
1115   *
1116   * @throws javax.xml.transform.TransformerException
1117   */

1118  protected void BooleanExpr() throws javax.xml.transform.TransformerException JavaDoc
1119  {
1120
1121    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1122
1123    appendOp(2, OpCodes.OP_BOOL);
1124    Expr();
1125
1126    int opLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos;
1127
1128    if (opLen == 2)
1129    {
1130      error(XPATHErrorResources.ER_BOOLEAN_ARG_NO_LONGER_OPTIONAL, null); //"boolean(...) argument is no longer optional with 19990709 XPath draft.");
1131
}
1132
1133    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, opLen);
1134  }
1135
1136  /**
1137   *
1138   *
1139   * NumberExpr ::= Expr
1140   *
1141   *
1142   * @throws javax.xml.transform.TransformerException
1143   */

1144  protected void NumberExpr() throws javax.xml.transform.TransformerException JavaDoc
1145  {
1146
1147    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1148
1149    appendOp(2, OpCodes.OP_NUMBER);
1150    Expr();
1151
1152    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1153      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1154  }
1155
1156  /**
1157   * The context of the right hand side expressions is the context of the
1158   * left hand side expression. The results of the right hand side expressions
1159   * are node sets. The result of the left hand side UnionExpr is the union
1160   * of the results of the right hand side expressions.
1161   *
1162   *
1163   * UnionExpr ::= PathExpr
1164   * | UnionExpr '|' PathExpr
1165   *
1166   *
1167   * @throws javax.xml.transform.TransformerException
1168   */

1169  protected void UnionExpr() throws javax.xml.transform.TransformerException JavaDoc
1170  {
1171
1172    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1173    boolean continueOrLoop = true;
1174    boolean foundUnion = false;
1175
1176    do
1177    {
1178      PathExpr();
1179
1180      if (tokenIs('|'))
1181      {
1182        if (false == foundUnion)
1183        {
1184          foundUnion = true;
1185
1186          insertOp(opPos, 2, OpCodes.OP_UNION);
1187        }
1188
1189        nextToken();
1190      }
1191      else
1192      {
1193        break;
1194      }
1195
1196      // this.m_testForDocOrder = true;
1197
}
1198    while (continueOrLoop);
1199
1200    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1201          m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1202  }
1203
1204  /**
1205   * PathExpr ::= LocationPath
1206   * | FilterExpr
1207   * | FilterExpr '/' RelativeLocationPath
1208   * | FilterExpr '//' RelativeLocationPath
1209   *
1210   * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
1211   * the error condition is severe enough to halt processing.
1212   *
1213   * @throws javax.xml.transform.TransformerException
1214   */

1215  protected void PathExpr() throws javax.xml.transform.TransformerException JavaDoc
1216  {
1217
1218    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1219
1220    int filterExprMatch = FilterExpr();
1221
1222    if (filterExprMatch != FILTER_MATCH_FAILED)
1223    {
1224      // If FilterExpr had Predicates, a OP_LOCATIONPATH opcode would already
1225
// have been inserted.
1226
boolean locationPathStarted = (filterExprMatch==FILTER_MATCH_PREDICATES);
1227
1228      if (tokenIs('/'))
1229      {
1230        nextToken();
1231
1232        if (!locationPathStarted)
1233        {
1234          // int locationPathOpPos = opPos;
1235
insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH);
1236
1237          locationPathStarted = true;
1238        }
1239
1240        if (!RelativeLocationPath())
1241        {
1242          // "Relative location path expected following '/' or '//'"
1243
error(XPATHErrorResources.ER_EXPECTED_REL_LOC_PATH, null);
1244        }
1245
1246      }
1247
1248      // Terminate for safety.
1249
if (locationPathStarted)
1250      {
1251        m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1252        m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1253        m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1254          m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1255      }
1256    }
1257    else
1258    {
1259      LocationPath();
1260    }
1261  }
1262
1263  /**
1264   *
1265   *
1266   * FilterExpr ::= PrimaryExpr
1267   * | FilterExpr Predicate
1268   *
1269   * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
1270   * the error condition is severe enough to halt processing.
1271   *
1272   * @return FILTER_MATCH_PREDICATES, if this method successfully matched a
1273   * FilterExpr with one or more Predicates;
1274   * FILTER_MATCH_PRIMARY, if this method successfully matched a
1275   * FilterExpr that was just a PrimaryExpr; or
1276   * FILTER_MATCH_FAILED, if this method did not match a FilterExpr
1277   *
1278   * @throws javax.xml.transform.TransformerException
1279   */

1280  protected int FilterExpr() throws javax.xml.transform.TransformerException JavaDoc
1281  {
1282
1283    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1284
1285    int filterMatch;
1286
1287    if (PrimaryExpr())
1288    {
1289      if (tokenIs('['))
1290      {
1291
1292        // int locationPathOpPos = opPos;
1293
insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH);
1294
1295        while (tokenIs('['))
1296        {
1297          Predicate();
1298        }
1299
1300        filterMatch = FILTER_MATCH_PREDICATES;
1301      }
1302      else
1303      {
1304        filterMatch = FILTER_MATCH_PRIMARY;
1305      }
1306    }
1307    else
1308    {
1309      filterMatch = FILTER_MATCH_FAILED;
1310    }
1311
1312    return filterMatch;
1313
1314    /*
1315     * if(tokenIs('['))
1316     * {
1317     * Predicate();
1318     * m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos;
1319     * }
1320     */

1321  }
1322
1323  /**
1324   *
1325   * PrimaryExpr ::= VariableReference
1326   * | '(' Expr ')'
1327   * | Literal
1328   * | Number
1329   * | FunctionCall
1330   *
1331   * @return true if this method successfully matched a PrimaryExpr
1332   *
1333   * @throws javax.xml.transform.TransformerException
1334   *
1335   */

1336  protected boolean PrimaryExpr() throws javax.xml.transform.TransformerException JavaDoc
1337  {
1338
1339    boolean matchFound;
1340    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1341
1342    if ((m_tokenChar == '\'') || (m_tokenChar == '"'))
1343    {
1344      appendOp(2, OpCodes.OP_LITERAL);
1345      Literal();
1346
1347      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1348        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1349
1350      matchFound = true;
1351    }
1352    else if (m_tokenChar == '$')
1353    {
1354      nextToken(); // consume '$'
1355
appendOp(2, OpCodes.OP_VARIABLE);
1356      QName();
1357      
1358      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1359        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1360
1361      matchFound = true;
1362    }
1363    else if (m_tokenChar == '(')
1364    {
1365      nextToken();
1366      appendOp(2, OpCodes.OP_GROUP);
1367      Expr();
1368      consumeExpected(')');
1369
1370      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1371        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1372
1373      matchFound = true;
1374    }
1375    else if ((null != m_token) && ((('.' == m_tokenChar) && (m_token.length() > 1) && Character.isDigit(
1376            m_token.charAt(1))) || Character.isDigit(m_tokenChar)))
1377    {
1378      appendOp(2, OpCodes.OP_NUMBERLIT);
1379      Number();
1380
1381      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1382        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1383
1384      matchFound = true;
1385    }
1386    else if (lookahead('(', 1) || (lookahead(':', 1) && lookahead('(', 3)))
1387    {
1388      matchFound = FunctionCall();
1389    }
1390    else
1391    {
1392      matchFound = false;
1393    }
1394
1395    return matchFound;
1396  }
1397
1398  /**
1399   *
1400   * Argument ::= Expr
1401   *
1402   *
1403   * @throws javax.xml.transform.TransformerException
1404   */

1405  protected void Argument() throws javax.xml.transform.TransformerException JavaDoc
1406  {
1407
1408    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1409
1410    appendOp(2, OpCodes.OP_ARGUMENT);
1411    Expr();
1412
1413    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1414      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1415  }
1416
1417  /**
1418   *
1419   * FunctionCall ::= FunctionName '(' ( Argument ( ',' Argument)*)? ')'
1420   *
1421   * @return true if, and only if, a FunctionCall was matched
1422   *
1423   * @throws javax.xml.transform.TransformerException
1424   */

1425  protected boolean FunctionCall() throws javax.xml.transform.TransformerException JavaDoc
1426  {
1427
1428    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1429
1430    if (lookahead(':', 1))
1431    {
1432      appendOp(4, OpCodes.OP_EXTFUNCTION);
1433
1434      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, m_queueMark - 1);
1435
1436      nextToken();
1437      consumeExpected(':');
1438
1439      m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 2, m_queueMark - 1);
1440
1441      nextToken();
1442    }
1443    else
1444    {
1445      int funcTok = getFunctionToken(m_token);
1446
1447      if (-1 == funcTok)
1448      {
1449        error(XPATHErrorResources.ER_COULDNOT_FIND_FUNCTION,
1450              new Object JavaDoc[]{ m_token }); //"Could not find function: "+m_token+"()");
1451
}
1452
1453      switch (funcTok)
1454      {
1455      case OpCodes.NODETYPE_PI :
1456      case OpCodes.NODETYPE_COMMENT :
1457      case OpCodes.NODETYPE_TEXT :
1458      case OpCodes.NODETYPE_NODE :
1459        // Node type tests look like function calls, but they're not
1460
return false;
1461      default :
1462        appendOp(3, OpCodes.OP_FUNCTION);
1463
1464        m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, funcTok);
1465      }
1466
1467      nextToken();
1468    }
1469
1470    consumeExpected('(');
1471
1472    while (!tokenIs(')') && m_token != null)
1473    {
1474      if (tokenIs(','))
1475      {
1476        error(XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_PRECEDING_ARG, null); //"Found ',' but no preceding argument!");
1477
}
1478
1479      Argument();
1480
1481      if (!tokenIs(')'))
1482      {
1483        consumeExpected(',');
1484
1485        if (tokenIs(')'))
1486        {
1487          error(XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_FOLLOWING_ARG,
1488                null); //"Found ',' but no following argument!");
1489
}
1490      }
1491    }
1492
1493    consumeExpected(')');
1494
1495    // Terminate for safety.
1496
m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1497    m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1498    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1499      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1500
1501    return true;
1502  }
1503
1504  // ============= GRAMMAR FUNCTIONS =================
1505

1506  /**
1507   *
1508   * LocationPath ::= RelativeLocationPath
1509   * | AbsoluteLocationPath
1510   *
1511   *
1512   * @throws javax.xml.transform.TransformerException
1513   */

1514  protected void LocationPath() throws javax.xml.transform.TransformerException JavaDoc
1515  {
1516
1517    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1518
1519    // int locationPathOpPos = opPos;
1520
appendOp(2, OpCodes.OP_LOCATIONPATH);
1521
1522    boolean seenSlash = tokenIs('/');
1523
1524    if (seenSlash)
1525    {
1526      appendOp(4, OpCodes.FROM_ROOT);
1527
1528      // Tell how long the step is without the predicate
1529
m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
1530      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_ROOT);
1531
1532      nextToken();
1533    }
1534
1535    if (m_token != null)
1536    {
1537      if (!RelativeLocationPath() && !seenSlash)
1538      {
1539        // Neither a '/' nor a RelativeLocationPath - i.e., matched nothing
1540
// "Location path expected, but found "+m_token+" was encountered."
1541
error(XPATHErrorResources.ER_EXPECTED_LOC_PATH,
1542              new Object JavaDoc [] {m_token});
1543      }
1544    }
1545
1546    // Terminate for safety.
1547
m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1548    m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1549    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1550      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1551  }
1552
1553  /**
1554   *
1555   * RelativeLocationPath ::= Step
1556   * | RelativeLocationPath '/' Step
1557   * | AbbreviatedRelativeLocationPath
1558   *
1559   * @returns true if, and only if, a RelativeLocationPath was matched
1560   *
1561   * @throws javax.xml.transform.TransformerException
1562   */

1563  protected boolean RelativeLocationPath()
1564               throws javax.xml.transform.TransformerException JavaDoc
1565  {
1566    if (!Step())
1567    {
1568      return false;
1569    }
1570
1571    while (tokenIs('/'))
1572    {
1573      nextToken();
1574
1575      if (!Step())
1576      {
1577        // RelativeLocationPath can't end with a trailing '/'
1578
// "Location step expected following '/' or '//'"
1579
error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null);
1580      }
1581    }
1582
1583    return true;
1584  }
1585
1586  /**
1587   *
1588   * Step ::= Basis Predicate
1589   * | AbbreviatedStep
1590   *
1591   * @returns false if step was empty (or only a '/'); true, otherwise
1592   *
1593   * @throws javax.xml.transform.TransformerException
1594   */

1595  protected boolean Step() throws javax.xml.transform.TransformerException JavaDoc
1596  {
1597    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1598
1599    boolean doubleSlash = tokenIs('/');
1600
1601    // At most a single '/' before each Step is consumed by caller; if the
1602
// first thing is a '/', that means we had '//' and the Step must not
1603
// be empty.
1604
if (doubleSlash)
1605    {
1606      nextToken();
1607
1608      appendOp(2, OpCodes.FROM_DESCENDANTS_OR_SELF);
1609
1610      // Have to fix up for patterns such as '//@foo' or '//attribute::foo',
1611
// which translate to 'descendant-or-self::node()/attribute::foo'.
1612
// notice I leave the '/' on the queue, so the next will be processed
1613
// by a regular step pattern.
1614

1615      // Make room for telling how long the step is without the predicate
1616
m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1617      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.NODETYPE_NODE);
1618      m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1619
1620      // Tell how long the step is without the predicate
1621
m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
1622          m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1623
1624      // Tell how long the step is with the predicate
1625
m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1626          m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1627
1628      opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1629    }
1630
1631    if (tokenIs("."))
1632    {
1633      nextToken();
1634
1635      if (tokenIs('['))
1636      {
1637        error(XPATHErrorResources.ER_PREDICATE_ILLEGAL_SYNTAX, null); //"'..[predicate]' or '.[predicate]' is illegal syntax. Use 'self::node()[predicate]' instead.");
1638
}
1639
1640      appendOp(4, OpCodes.FROM_SELF);
1641
1642      // Tell how long the step is without the predicate
1643
m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2,4);
1644      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_NODE);
1645    }
1646    else if (tokenIs(".."))
1647    {
1648      nextToken();
1649      appendOp(4, OpCodes.FROM_PARENT);
1650
1651      // Tell how long the step is without the predicate
1652
m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2,4);
1653      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_NODE);
1654    }
1655
1656    // There is probably a better way to test for this
1657
// transition... but it gets real hairy if you try
1658
// to do it in basis().
1659
else if (tokenIs('*') || tokenIs('@') || tokenIs('_')
1660             || (m_token!= null && Character.isLetter(m_token.charAt(0))))
1661    {
1662      Basis();
1663
1664      while (tokenIs('['))
1665      {
1666        Predicate();
1667      }
1668
1669      // Tell how long the entire step is.
1670
m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1671        m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1672    }
1673    else
1674    {
1675      // No Step matched - that's an error if previous thing was a '//'
1676
if (doubleSlash)
1677      {
1678        // "Location step expected following '/' or '//'"
1679
error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null);
1680      }
1681
1682      return false;
1683    }
1684
1685    return true;
1686  }
1687
1688  /**
1689   *
1690   * Basis ::= AxisName '::' NodeTest
1691   * | AbbreviatedBasis
1692   *
1693   * @throws javax.xml.transform.TransformerException
1694   */

1695  protected void Basis() throws javax.xml.transform.TransformerException JavaDoc
1696  {
1697
1698    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1699    int axesType;
1700
1701    // The next blocks guarantee that a FROM_XXX will be added.
1702
if (lookahead("::", 1))
1703    {
1704      axesType = AxisName();
1705
1706      nextToken();
1707      nextToken();
1708    }
1709    else if (tokenIs('@'))
1710    {
1711      axesType = OpCodes.FROM_ATTRIBUTES;
1712
1713      appendOp(2, axesType);
1714      nextToken();
1715    }
1716    else
1717    {
1718      axesType = OpCodes.FROM_CHILDREN;
1719
1720      appendOp(2, axesType);
1721    }
1722
1723    // Make room for telling how long the step is without the predicate
1724
m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1725
1726    NodeTest(axesType);
1727
1728    // Tell how long the step is without the predicate
1729
m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
1730      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1731   }
1732
1733  /**
1734   *
1735   * Basis ::= AxisName '::' NodeTest
1736   * | AbbreviatedBasis
1737   *
1738   * @return FROM_XXX axes type, found in {@link org.apache.xpath.compiler.Keywords}.
1739   *
1740   * @throws javax.xml.transform.TransformerException
1741   */

1742  protected int AxisName() throws javax.xml.transform.TransformerException JavaDoc
1743  {
1744
1745    Object JavaDoc val = Keywords.m_axisnames.get(m_token);
1746
1747    if (null == val)
1748    {
1749      error(XPATHErrorResources.ER_ILLEGAL_AXIS_NAME,
1750            new Object JavaDoc[]{ m_token }); //"illegal axis name: "+m_token);
1751
}
1752
1753    int axesType = ((Integer JavaDoc) val).intValue();
1754
1755    appendOp(2, axesType);
1756
1757    return axesType;
1758  }
1759
1760  /**
1761   *
1762   * NodeTest ::= WildcardName
1763   * | NodeType '(' ')'
1764   * | 'processing-instruction' '(' Literal ')'
1765   *
1766   * @param axesType FROM_XXX axes type, found in {@link org.apache.xpath.compiler.Keywords}.
1767   *
1768   * @throws javax.xml.transform.TransformerException
1769   */

1770  protected void NodeTest(int axesType) throws javax.xml.transform.TransformerException JavaDoc
1771  {
1772
1773    if (lookahead('(', 1))
1774    {
1775      Object JavaDoc nodeTestOp = Keywords.m_nodetypes.get(m_token);
1776
1777      if (null == nodeTestOp)
1778      {
1779        error(XPATHErrorResources.ER_UNKNOWN_NODETYPE,
1780              new Object JavaDoc[]{ m_token }); //"Unknown nodetype: "+m_token);
1781
}
1782      else
1783      {
1784        nextToken();
1785
1786        int nt = ((Integer JavaDoc) nodeTestOp).intValue();
1787
1788        m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), nt);
1789        m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1790
1791        consumeExpected('(');
1792
1793        if (OpCodes.NODETYPE_PI == nt)
1794        {
1795          if (!tokenIs(')'))
1796          {
1797            Literal();
1798          }
1799        }
1800
1801        consumeExpected(')');
1802      }
1803    }
1804    else
1805    {
1806
1807      // Assume name of attribute or element.
1808
m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.NODENAME);
1809      m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1810
1811      if (lookahead(':', 1))
1812      {
1813        if (tokenIs('*'))
1814        {
1815          m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ELEMWILDCARD);
1816        }
1817        else
1818        {
1819          m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1820
1821          // Minimalist check for an NCName - just check first character
1822
// to distinguish from other possible tokens
1823
if (!Character.isLetter(m_tokenChar) && !tokenIs('_'))
1824          {
1825            // "Node test that matches either NCName:* or QName was expected."
1826
error(XPATHErrorResources.ER_EXPECTED_NODE_TEST, null);
1827          }
1828        }
1829
1830        nextToken();
1831        consumeExpected(':');
1832      }
1833      else
1834      {
1835        m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.EMPTY);
1836      }
1837
1838      m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1839
1840      if (tokenIs('*'))
1841      {
1842        m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ELEMWILDCARD);
1843      }
1844      else
1845      {
1846        if (OpCodes.FROM_NAMESPACE == axesType)
1847        {
1848          String JavaDoc prefix = (String JavaDoc) this.m_ops.m_tokenQueue.elementAt(m_queueMark - 1);
1849          String JavaDoc namespace =
1850            ((PrefixResolver) m_namespaceContext).getNamespaceForPrefix(
1851              prefix);
1852
1853          this.m_ops.m_tokenQueue.setElementAt(namespace,m_queueMark - 1);
1854        }
1855
1856        m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1857
1858        // Minimalist check for an NCName - just check first character
1859
// to distinguish from other possible tokens
1860
if (!Character.isLetter(m_tokenChar) && !tokenIs('_'))
1861        {
1862          // "Node test that matches either NCName:* or QName was expected."
1863
error(XPATHErrorResources.ER_EXPECTED_NODE_TEST, null);
1864        }
1865      }
1866
1867      m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1868
1869      nextToken();
1870    }
1871  }
1872
1873  /**
1874   *
1875   * Predicate ::= '[' PredicateExpr ']'
1876   *
1877   *
1878   * @throws javax.xml.transform.TransformerException
1879   */

1880  protected void Predicate() throws javax.xml.transform.TransformerException JavaDoc
1881  {
1882
1883    if (tokenIs('['))
1884    {
1885      nextToken();
1886      PredicateExpr();
1887      consumeExpected(']');
1888    }
1889  }
1890
1891  /**
1892   *
1893   * PredicateExpr ::= Expr
1894   *
1895   *
1896   * @throws javax.xml.transform.TransformerException
1897   */

1898  protected void PredicateExpr() throws javax.xml.transform.TransformerException JavaDoc
1899  {
1900
1901    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1902
1903    appendOp(2, OpCodes.OP_PREDICATE);
1904    Expr();
1905
1906    // Terminate for safety.
1907
m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1908    m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1909    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1910      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1911  }
1912
1913  /**
1914   * QName ::= (Prefix ':')? LocalPart
1915   * Prefix ::= NCName
1916   * LocalPart ::= NCName
1917   *
1918   * @throws javax.xml.transform.TransformerException
1919   */

1920  protected void QName() throws javax.xml.transform.TransformerException JavaDoc
1921  {
1922    // Namespace
1923
if(lookahead(':', 1))
1924    {
1925      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1926      m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1927
1928      nextToken();
1929      consumeExpected(':');
1930    }
1931    else
1932    {
1933      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.EMPTY);
1934      m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1935    }
1936    
1937    // Local name
1938
m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1939    m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1940
1941    nextToken();
1942  }
1943
1944  /**
1945   * NCName ::= (Letter | '_') (NCNameChar)
1946   * NCNameChar ::= Letter | Digit | '.' | '-' | '_' | CombiningChar | Extender
1947   */

1948  protected void NCName()
1949  {
1950
1951    m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1952    m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1953
1954    nextToken();
1955  }
1956
1957  /**
1958   * The value of the Literal is the sequence of characters inside
1959   * the " or ' characters>.
1960   *
1961   * Literal ::= '"' [^"]* '"'
1962   * | "'" [^']* "'"
1963   *
1964   *
1965   * @throws javax.xml.transform.TransformerException
1966   */

1967  protected void Literal() throws javax.xml.transform.TransformerException JavaDoc
1968  {
1969
1970    int last = m_token.length() - 1;
1971    char c0 = m_tokenChar;
1972    char cX = m_token.charAt(last);
1973
1974    if (((c0 == '\"') && (cX == '\"')) || ((c0 == '\'') && (cX == '\'')))
1975    {
1976
1977      // Mutate the token to remove the quotes and have the XString object
1978
// already made.
1979
int tokenQueuePos = m_queueMark - 1;
1980
1981      m_ops.m_tokenQueue.setElementAt(null,tokenQueuePos);
1982
1983      Object JavaDoc obj = new XString(m_token.substring(1, last));
1984
1985      m_ops.m_tokenQueue.setElementAt(obj,tokenQueuePos);
1986
1987      // lit = m_token.substring(1, last);
1988
m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), tokenQueuePos);
1989      m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1990
1991      nextToken();
1992    }
1993    else
1994    {
1995      error(XPATHErrorResources.ER_PATTERN_LITERAL_NEEDS_BE_QUOTED,
1996            new Object JavaDoc[]{ m_token }); //"Pattern literal ("+m_token+") needs to be quoted!");
1997
}
1998  }
1999
2000  /**
2001   *
2002   * Number ::= [0-9]+('.'[0-9]+)? | '.'[0-9]+
2003   *
2004   *
2005   * @throws javax.xml.transform.TransformerException
2006   */

2007  protected void Number() throws javax.xml.transform.TransformerException JavaDoc
2008  {
2009
2010    if (null != m_token)
2011    {
2012
2013      // Mutate the token to remove the quotes and have the XNumber object
2014
// already made.
2015
double num;
2016
2017      try
2018      {
2019        // XPath 1.0 does not support number in exp notation
2020
if ((m_token.indexOf('e') > -1)||(m_token.indexOf('E') > -1))
2021            throw new NumberFormatException JavaDoc();
2022        num = Double.valueOf(m_token).doubleValue();
2023      }
2024      catch (NumberFormatException JavaDoc nfe)
2025      {
2026        num = 0.0; // to shut up compiler.
2027

2028        error(XPATHErrorResources.ER_COULDNOT_BE_FORMATTED_TO_NUMBER,
2029              new Object JavaDoc[]{ m_token }); //m_token+" could not be formatted to a number!");
2030
}
2031
2032      m_ops.m_tokenQueue.setElementAt(new XNumber(num),m_queueMark - 1);
2033      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
2034      m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
2035
2036      nextToken();
2037    }
2038  }
2039
2040  // ============= PATTERN FUNCTIONS =================
2041

2042  /**
2043   *
2044   * Pattern ::= LocationPathPattern
2045   * | Pattern '|' LocationPathPattern
2046   *
2047   *
2048   * @throws javax.xml.transform.TransformerException
2049   */

2050  protected void Pattern() throws javax.xml.transform.TransformerException JavaDoc
2051  {
2052
2053    while (true)
2054    {
2055      LocationPathPattern();
2056
2057      if (tokenIs('|'))
2058      {
2059        nextToken();
2060      }
2061      else
2062      {
2063        break;
2064      }
2065    }
2066  }
2067
2068  /**
2069   *
2070   *
2071   * LocationPathPattern ::= '/' RelativePathPattern?
2072   * | IdKeyPattern (('/' | '//') RelativePathPattern)?
2073   * | '//'? RelativePathPattern
2074   *
2075   *
2076   * @throws javax.xml.transform.TransformerException
2077   */

2078  protected void LocationPathPattern() throws javax.xml.transform.TransformerException JavaDoc
2079  {
2080
2081    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2082
2083    final int RELATIVE_PATH_NOT_PERMITTED = 0;
2084    final int RELATIVE_PATH_PERMITTED = 1;
2085    final int RELATIVE_PATH_REQUIRED = 2;
2086
2087    int relativePathStatus = RELATIVE_PATH_NOT_PERMITTED;
2088
2089    appendOp(2, OpCodes.OP_LOCATIONPATHPATTERN);
2090
2091    if (lookahead('(', 1)
2092            && (tokenIs(Keywords.FUNC_ID_STRING)
2093                || tokenIs(Keywords.FUNC_KEY_STRING)))
2094    {
2095      IdKeyPattern();
2096
2097      if (tokenIs('/'))
2098      {
2099        nextToken();
2100
2101        if (tokenIs('/'))
2102        {
2103          appendOp(4, OpCodes.MATCH_ANY_ANCESTOR);
2104
2105          nextToken();
2106        }
2107        else
2108        {
2109          appendOp(4, OpCodes.MATCH_IMMEDIATE_ANCESTOR);
2110        }
2111
2112        // Tell how long the step is without the predicate
2113
m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
2114        m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_FUNCTEST);
2115
2116        relativePathStatus = RELATIVE_PATH_REQUIRED;
2117      }
2118    }
2119    else if (tokenIs('/'))
2120    {
2121      if (lookahead('/', 1))
2122      {
2123        appendOp(4, OpCodes.MATCH_ANY_ANCESTOR);
2124        
2125        // Added this to fix bug reported by Myriam for match="//x/a"
2126
// patterns. If you don't do this, the 'x' step will think it's part
2127
// of a '//' pattern, and so will cause 'a' to be matched when it has
2128
// any ancestor that is 'x'.
2129
nextToken();
2130
2131        relativePathStatus = RELATIVE_PATH_REQUIRED;
2132      }
2133      else
2134      {
2135        appendOp(4, OpCodes.FROM_ROOT);
2136
2137        relativePathStatus = RELATIVE_PATH_PERMITTED;
2138      }
2139
2140
2141      // Tell how long the step is without the predicate
2142
m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
2143      m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_ROOT);
2144
2145      nextToken();
2146    }
2147    else
2148    {
2149      relativePathStatus = RELATIVE_PATH_REQUIRED;
2150    }
2151
2152    if (relativePathStatus != RELATIVE_PATH_NOT_PERMITTED)
2153    {
2154      if (!tokenIs('|') && (null != m_token))
2155      {
2156        RelativePathPattern();
2157      }
2158      else if (relativePathStatus == RELATIVE_PATH_REQUIRED)
2159      {
2160        // "A relative path pattern was expected."
2161
error(XPATHErrorResources.ER_EXPECTED_REL_PATH_PATTERN, null);
2162      }
2163    }
2164
2165    // Terminate for safety.
2166
m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
2167    m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
2168    m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
2169      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
2170  }
2171
2172  /**
2173   *
2174   * IdKeyPattern ::= 'id' '(' Literal ')'
2175   * | 'key' '(' Literal ',' Literal ')'
2176   * (Also handle doc())
2177   *
2178   *
2179   * @throws javax.xml.transform.TransformerException
2180   */

2181  protected void IdKeyPattern() throws javax.xml.transform.TransformerException JavaDoc
2182  {
2183    FunctionCall();
2184  }
2185
2186  /**
2187   *
2188   * RelativePathPattern ::= StepPattern
2189   * | RelativePathPattern '/' StepPattern
2190   * | RelativePathPattern '//' StepPattern
2191   *
2192   * @throws javax.xml.transform.TransformerException
2193   */

2194  protected void RelativePathPattern()
2195              throws javax.xml.transform.TransformerException JavaDoc
2196  {
2197
2198    // Caller will have consumed any '/' or '//' preceding the
2199
// RelativePathPattern, so let StepPattern know it can't begin with a '/'
2200
boolean trailingSlashConsumed = StepPattern(false);
2201
2202    while (tokenIs('/'))
2203    {
2204      nextToken();
2205
2206      // StepPattern() may consume first slash of pair in "a//b" while
2207
// processing StepPattern "a". On next iteration, let StepPattern know
2208
// that happened, so it doesn't match ill-formed patterns like "a///b".
2209
trailingSlashConsumed = StepPattern(!trailingSlashConsumed);
2210    }
2211  }
2212
2213  /**
2214   *
2215   * StepPattern ::= AbbreviatedNodeTestStep
2216   *
2217   * @param isLeadingSlashPermitted a boolean indicating whether a slash can
2218   * appear at the start of this step
2219   *
2220   * @return boolean indicating whether a slash following the step was consumed
2221   *
2222   * @throws javax.xml.transform.TransformerException
2223   */

2224  protected boolean StepPattern(boolean isLeadingSlashPermitted)
2225            throws javax.xml.transform.TransformerException JavaDoc
2226  {
2227    return AbbreviatedNodeTestStep(isLeadingSlashPermitted);
2228  }
2229
2230  /**
2231   *
2232   * AbbreviatedNodeTestStep ::= '@'? NodeTest Predicate
2233   *
2234   * @param isLeadingSlashPermitted a boolean indicating whether a slash can
2235   * appear at the start of this step
2236   *
2237   * @return boolean indicating whether a slash following the step was consumed
2238   *
2239   * @throws javax.xml.transform.TransformerException
2240   */

2241  protected boolean AbbreviatedNodeTestStep(boolean isLeadingSlashPermitted)
2242            throws javax.xml.transform.TransformerException JavaDoc
2243  {
2244
2245    int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2246    int axesType;
2247
2248    // The next blocks guarantee that a MATCH_XXX will be added.
2249
int matchTypePos = -1;
2250
2251    if (tokenIs('@'))
2252    {
2253      axesType = OpCodes.MATCH_ATTRIBUTE;
2254
2255      appendOp(2, axesType);
2256      nextToken();
2257    }
2258    else if (this.lookahead("::", 1))
2259    {
2260      if (tokenIs("attribute"))
2261      {
2262        axesType = OpCodes.MATCH_ATTRIBUTE;
2263
2264        appendOp(2, axesType);
2265      }
2266      else if (tokenIs("child"))
2267      {
2268        matchTypePos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2269        axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR;
2270
2271        appendOp(2, axesType);
2272      }
2273      else
2274      {
2275        axesType = -1;
2276
2277        this.error(XPATHErrorResources.ER_AXES_NOT_ALLOWED,
2278                   new Object JavaDoc[]{ this.m_token });
2279      }
2280
2281      nextToken();
2282      nextToken();
2283    }
2284    else if (tokenIs('/'))
2285    {
2286      if (!isLeadingSlashPermitted)
2287      {
2288        // "A step was expected in the pattern, but '/' was encountered."
2289
error(XPATHErrorResources.ER_EXPECTED_STEP_PATTERN, null);
2290      }
2291      axesType = OpCodes.MATCH_ANY_ANCESTOR;
2292
2293      appendOp(2, axesType);
2294      nextToken();
2295    }
2296    else
2297    {
2298      matchTypePos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2299      axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR;
2300
2301      appendOp(2, axesType);
2302    }
2303
2304    // Make room for telling how long the step is without the predicate
2305
m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
2306
2307    NodeTest(axesType);
2308
2309    // Tell how long the step is without the predicate
2310
m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
2311      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
2312
2313    while (tokenIs('['))
2314    {
2315      Predicate();
2316    }
2317
2318    boolean trailingSlashConsumed;
2319
2320    // For "a//b", where "a" is current step, we need to mark operation of
2321
// current step as "MATCH_ANY_ANCESTOR". Then we'll consume the first
2322
// slash and subsequent step will be treated as a MATCH_IMMEDIATE_ANCESTOR
2323
// (unless it too is followed by '//'.)
2324
//
2325
// %REVIEW% Following is what happens today, but I'm not sure that's
2326
// %REVIEW% correct behaviour. Perhaps no valid case could be constructed
2327
// %REVIEW% where it would matter?
2328
//
2329
// If current step is on the attribute axis (e.g., "@x//b"), we won't
2330
// change the current step, and let following step be marked as
2331
// MATCH_ANY_ANCESTOR on next call instead.
2332
if ((matchTypePos > -1) && tokenIs('/') && lookahead('/', 1))
2333    {
2334      m_ops.setOp(matchTypePos, OpCodes.MATCH_ANY_ANCESTOR);
2335
2336      nextToken();
2337
2338      trailingSlashConsumed = true;
2339    }
2340    else
2341    {
2342      trailingSlashConsumed = false;
2343    }
2344
2345    // Tell how long the entire step is.
2346
m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
2347      m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
2348
2349    return trailingSlashConsumed;
2350  }
2351}
2352
Popular Tags