View Javadoc
1   /*
2    * $Header$
3    * $Revision$
4    * $Date$
5    *
6    * ====================================================================
7    *
8    * Copyright 2000-2002 bob mcwhirter & James Strachan.
9    * All rights reserved.
10   *
11   *
12   * Redistribution and use in source and binary forms, with or without
13   * modification, are permitted provided that the following conditions are
14   * met:
15   * 
16   *   * Redistributions of source code must retain the above copyright
17   *     notice, this list of conditions and the following disclaimer.
18   * 
19   *   * Redistributions in binary form must reproduce the above copyright
20   *     notice, this list of conditions and the following disclaimer in the
21   *     documentation and/or other materials provided with the distribution.
22   * 
23   *   * Neither the name of the Jaxen Project nor the names of its
24   *     contributors may be used to endorse or promote products derived 
25   *     from this software without specific prior written permission.
26   * 
27   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
28   * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
29   * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
30   * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
31   * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
32   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
33   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
34   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
35   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
36   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
37   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38   *
39   * ====================================================================
40   * This software consists of voluntary contributions made by many
41   * individuals on behalf of the Jaxen Project and was originally
42   * created by bob mcwhirter <bob@werken.com> and
43   * James Strachan <jstrachan@apache.org>.  For more information on the
44   * Jaxen Project, please see <http://www.jaxen.org/>.
45   *
46   * $Id$
47   */
48  
49  
50  package org.jaxen.saxpath.base;
51  
52  import java.util.ArrayList;
53  
54  import org.jaxen.saxpath.Axis;
55  import org.jaxen.saxpath.Operator;
56  import org.jaxen.saxpath.SAXPathException;
57  import org.jaxen.saxpath.XPathHandler;
58  import org.jaxen.saxpath.XPathSyntaxException;
59  import org.jaxen.saxpath.helpers.DefaultXPathHandler;
60  
61  /** Implementation of SAXPath's <code>XPathReader</code> which
62   *  generates callbacks to an <code>XPathHandler</code>.
63   *
64   *  @author bob mcwhirter (bob@werken.com)
65   */
66  public class XPathReader implements org.jaxen.saxpath.XPathReader
67  {
68      private ArrayList<Token>  tokens;
69      private XPathLexer lexer;
70  
71      private XPathHandler handler;
72      
73      private static XPathHandler defaultHandler = new DefaultXPathHandler();
74  
75      /**
76       * Create a new <code>XPathReader</code> with a do-nothing
77       * <code>XPathHandler</code>.
78       */
79      public XPathReader()
80      {
81          setXPathHandler( defaultHandler );
82      }
83  
84      public void setXPathHandler(XPathHandler handler)
85      {
86          this.handler = handler;
87      }
88  
89      public XPathHandler getXPathHandler()
90      {
91          return this.handler;
92      }
93  
94      public void parse(String xpath) throws SAXPathException
95      {
96          setUpParse( xpath );
97  
98          getXPathHandler().startXPath();
99  
100         expr();
101 
102         getXPathHandler().endXPath();
103 
104         if ( LA(1) != TokenTypes.EOF )
105         {
106             XPathSyntaxException ex = createSyntaxException( "Unexpected '" + LT(1).getTokenText() + "'" );
107             throw ex;
108         }
109 
110         lexer  = null;
111         tokens = null;
112     }
113 
114     void setUpParse(String xpath)
115     {
116         this.tokens = new ArrayList<Token>();
117         this.lexer = new XPathLexer( xpath );
118     }
119 
120     private void pathExpr() throws SAXPathException
121     {
122         getXPathHandler().startPathExpr();
123 
124         switch ( LA(1) )
125         {
126             case TokenTypes.DOUBLE:
127             case TokenTypes.LITERAL:
128             {
129                 filterExpr();
130 
131                 if ( LA(1) == TokenTypes.SLASH || LA(1) == TokenTypes.DOUBLE_SLASH )
132                 {
133                     XPathSyntaxException ex = createSyntaxException("Node-set expected");
134                     throw ex;
135                 }
136 
137                 break;
138             }                
139             case TokenTypes.LEFT_PAREN:
140             case TokenTypes.DOLLAR:
141             {
142                 filterExpr();
143                     
144                 if ( LA(1) == TokenTypes.SLASH || LA(1) == TokenTypes.DOUBLE_SLASH)
145                 {
146                     locationPath( false );
147                 }
148                 break;
149             }
150             case TokenTypes.IDENTIFIER:
151             {
152 
153                 if ( ( LA(2) == TokenTypes.LEFT_PAREN
154                      &&
155                        ! isNodeTypeName( LT(1) ) )
156                      ||
157                     ( LA(2) == TokenTypes.COLON
158                       &&
159                       LA(4) == TokenTypes.LEFT_PAREN) ) 
160                 {
161                     filterExpr();
162                     
163                     if ( LA(1) == TokenTypes.SLASH || LA(1) == TokenTypes.DOUBLE_SLASH)
164                     {
165                         locationPath( false );
166                     }
167                 }
168                 else
169                 {
170                     locationPath( false );
171                 }
172                 break;
173             }
174             case TokenTypes.DOT:
175             case TokenTypes.DOT_DOT:
176             case TokenTypes.STAR:
177             case TokenTypes.AT:
178             {
179                 locationPath( false );
180                 break;
181             }
182             case TokenTypes.SLASH:
183             case TokenTypes.DOUBLE_SLASH:
184             {
185                 locationPath( true );
186                 break;
187             }
188             default:
189             {
190                 XPathSyntaxException ex = createSyntaxException( "Unexpected '" + LT(1).getTokenText() + "'" );
191                 throw ex;
192             }
193         }
194 
195         getXPathHandler().endPathExpr();
196     }
197 
198     private void literal() throws SAXPathException
199     {
200         Token token = match( TokenTypes.LITERAL );
201 
202         getXPathHandler().literal( token.getTokenText() );
203     }
204 
205     private void functionCall() throws SAXPathException
206     {
207         String prefix       = null;
208         String functionName = null;
209 
210         if ( LA(2) == TokenTypes.COLON )
211         {
212             prefix = match( TokenTypes.IDENTIFIER ).getTokenText();
213             match( TokenTypes.COLON );
214         }
215         else
216         {
217             prefix = "";
218         }
219 
220         functionName = match( TokenTypes.IDENTIFIER ).getTokenText();
221 
222         getXPathHandler().startFunction( prefix,
223                                          functionName );
224 
225         match ( TokenTypes.LEFT_PAREN );
226 
227         arguments();
228 
229         match ( TokenTypes.RIGHT_PAREN );
230 
231         getXPathHandler().endFunction();
232     }
233 
234     private void arguments() throws SAXPathException
235     {
236         while ( LA(1) != TokenTypes.RIGHT_PAREN )
237         {
238             expr();
239 
240             if ( LA(1) == TokenTypes.COMMA )
241             {
242                 match( TokenTypes.COMMA );
243             }
244             else
245             {
246                 break;
247             }
248         }
249     }
250 
251     private void filterExpr() throws SAXPathException
252     {
253 
254         getXPathHandler().startFilterExpr();
255 
256         switch ( LA(1) )
257         {
258             case TokenTypes.DOUBLE:
259             {
260                 Token token = match( TokenTypes.DOUBLE );
261                 
262                 getXPathHandler().number( Double.parseDouble( token.getTokenText() ) );
263                 break;
264             }
265             case TokenTypes.LITERAL:
266             {
267                 literal();
268                 break;
269             }
270             case TokenTypes.LEFT_PAREN:
271             {
272                 match( TokenTypes.LEFT_PAREN );
273                 expr();
274                 match( TokenTypes.RIGHT_PAREN );
275                 break;
276             }
277             case TokenTypes.IDENTIFIER:
278             {
279                 functionCall();
280                 break;
281             }
282             case TokenTypes.DOLLAR:
283             {
284                 variableReference();
285                 break;
286             }
287         }
288 
289         predicates();
290 
291         getXPathHandler().endFilterExpr();
292     }
293 
294     private void variableReference() throws SAXPathException
295     {
296         match( TokenTypes.DOLLAR );
297 
298         String prefix       = null;
299         String variableName = null;
300 
301         if ( LA(2) == TokenTypes.COLON )
302         {
303             prefix = match( TokenTypes.IDENTIFIER ).getTokenText();
304             match( TokenTypes.COLON );
305         }
306         else
307         {
308             prefix = "";
309         }
310 
311         variableName = match( TokenTypes.IDENTIFIER ).getTokenText();
312 
313         getXPathHandler().variableReference( prefix,
314                                              variableName );
315     }
316 
317     void locationPath(boolean isAbsolute) throws SAXPathException
318     {
319         switch ( LA(1) )
320         {
321             case TokenTypes.SLASH:
322             case TokenTypes.DOUBLE_SLASH:
323             {
324                 if ( isAbsolute )
325                 {
326                     absoluteLocationPath();
327                 }
328                 else
329                 {
330                     relativeLocationPath();
331                 }
332                 break;
333             }
334             case TokenTypes.AT:
335             case TokenTypes.IDENTIFIER:
336             case TokenTypes.DOT:
337             case TokenTypes.DOT_DOT:
338             case TokenTypes.STAR:
339             {
340                 relativeLocationPath();
341                 break;
342             }
343             default:
344             {
345                 XPathSyntaxException ex = createSyntaxException( "Unexpected '" + LT(1).getTokenText() + "'" );
346                 throw ex;
347             }
348         }
349     }
350 
351     private void absoluteLocationPath() throws SAXPathException
352     {
353         getXPathHandler().startAbsoluteLocationPath();
354 
355         switch ( LA(1) )
356         {
357             case TokenTypes.SLASH:
358             {
359                 match( TokenTypes.SLASH );
360 
361                 switch ( LA(1) )
362                 {
363 
364                     case TokenTypes.DOT:
365                     case TokenTypes.DOT_DOT:
366                     case TokenTypes.AT:
367                     case TokenTypes.IDENTIFIER:
368                     case TokenTypes.STAR:
369                     {
370                         steps();
371                         break;
372                     }
373                 }
374                 break;
375             }
376             case TokenTypes.DOUBLE_SLASH:
377             {
378                 getXPathHandler().startAllNodeStep( Axis.DESCENDANT_OR_SELF );
379                 getXPathHandler().endAllNodeStep();
380 
381                 match( TokenTypes.DOUBLE_SLASH );
382                 switch ( LA(1) )
383                 {
384                     case TokenTypes.DOT:
385                     case TokenTypes.DOT_DOT:
386                     case TokenTypes.AT:
387                     case TokenTypes.IDENTIFIER:
388                     case TokenTypes.STAR:
389                     {
390                         steps();
391                         break;
392                     }
393                     default:
394                         XPathSyntaxException ex = this.createSyntaxException("Location path cannot end with //");
395                         throw ex;
396                 }
397                 break;
398             }
399         }
400         
401         getXPathHandler().endAbsoluteLocationPath();
402     }
403 
404     private void relativeLocationPath() throws SAXPathException
405     {
406         getXPathHandler().startRelativeLocationPath();
407 
408         switch ( LA(1) )
409         {
410             case TokenTypes.SLASH:
411             {
412                 match( TokenTypes.SLASH );
413                 break;
414             }
415             case TokenTypes.DOUBLE_SLASH:
416             {
417                 getXPathHandler().startAllNodeStep( Axis.DESCENDANT_OR_SELF );
418                 getXPathHandler().endAllNodeStep();
419 
420                 match( TokenTypes.DOUBLE_SLASH );
421 
422                 break;
423             }
424         }
425 
426         steps();
427 
428         getXPathHandler().endRelativeLocationPath();
429     }
430 
431     private void steps() throws SAXPathException
432     {
433         switch ( LA(1) )
434         {
435 
436             case TokenTypes.DOT:
437             case TokenTypes.DOT_DOT:
438             case TokenTypes.AT:
439             case TokenTypes.IDENTIFIER:
440             case TokenTypes.STAR:
441             {
442                 step();
443                 break;
444             }
445             case TokenTypes.EOF:
446             {
447                 return;
448             }
449             default:
450             {
451                 XPathSyntaxException ex = createSyntaxException( "Expected one of '.', '..', '@', '*', <QName>" );
452                 throw ex;
453             }
454         }
455 
456         do
457         {
458             if ( ( LA(1) == TokenTypes.SLASH)
459                  ||
460                  ( LA(1) == TokenTypes.DOUBLE_SLASH ) )
461             {
462                 switch ( LA(1) )
463                 {
464                     case TokenTypes.SLASH:
465                     {
466                         match( TokenTypes.SLASH );
467                         break;
468                     }
469                     case TokenTypes.DOUBLE_SLASH:
470                     {
471                         getXPathHandler().startAllNodeStep( Axis.DESCENDANT_OR_SELF );
472                         getXPathHandler().endAllNodeStep();
473 
474                         match( TokenTypes.DOUBLE_SLASH );
475                         break;
476                     }
477                 }
478             }
479             else
480             {
481                 return;
482             }
483             
484             switch ( LA(1) )
485             {
486                 case TokenTypes.DOT:
487                 case TokenTypes.DOT_DOT:
488                 case TokenTypes.AT:
489                 case TokenTypes.IDENTIFIER:
490                 case TokenTypes.STAR:
491                 {
492                     step();
493                     break;
494                 }
495                 default:
496                 {
497                     XPathSyntaxException ex = createSyntaxException( "Expected one of '.', '..', '@', '*', <QName>" );
498                     throw ex;
499                 }
500             }
501 
502         } while ( true );
503     }
504 
505     void step() throws SAXPathException
506     {
507         int axis = 0;
508 
509         switch ( LA(1) )
510         {
511             case TokenTypes.DOT:
512             case TokenTypes.DOT_DOT:
513             {
514                 abbrStep();
515                 return;
516             }
517             case TokenTypes.AT:
518             {
519                 axis = axisSpecifier();
520                 break;
521             }
522             case TokenTypes.IDENTIFIER:
523             {
524                 if ( LA(2) == TokenTypes.DOUBLE_COLON )
525                 {
526                     axis = axisSpecifier();
527                 }
528                 else
529                 {
530                     axis = Axis.CHILD;
531                 }
532                 break;
533             }
534             case TokenTypes.STAR:
535             {
536                 axis = Axis.CHILD;
537                 break;
538             }
539         }
540 
541         nodeTest( axis );
542     }
543 
544     private int axisSpecifier() throws SAXPathException
545     {
546         int axis = 0;
547 
548         switch ( LA(1) )
549         {
550             case TokenTypes.AT:
551             {
552                 match( TokenTypes.AT );
553                 axis = Axis.ATTRIBUTE;
554                 break;
555             }
556             case TokenTypes.IDENTIFIER:
557             {
558                 Token token = LT( 1 );
559 
560                 axis = Axis.lookup( token.getTokenText() );
561 
562                 if ( axis == Axis.INVALID_AXIS )
563                 {
564                     throwInvalidAxis( token.getTokenText() );
565                 }
566 
567                 match( TokenTypes.IDENTIFIER );
568                 match( TokenTypes.DOUBLE_COLON );
569 
570                 break;
571             }
572         }
573 
574         return axis;
575     }
576 
577     private void nodeTest(int axis) throws SAXPathException
578     {
579         switch ( LA(1) )
580         {
581             case TokenTypes.IDENTIFIER:
582             {
583                 switch ( LA(2) )
584                 {
585                     case TokenTypes.LEFT_PAREN:
586                     {
587                         nodeTypeTest( axis );
588                         break;
589                     }
590                     default:
591                     {
592                         nameTest( axis );
593                         break;
594                     }
595                 }
596                 break;
597             }
598             case TokenTypes.STAR:
599             {
600                 nameTest( axis );
601                 break;
602             }
603             default:
604                 XPathSyntaxException ex = createSyntaxException("Expected <QName> or *");
605                 throw ex;
606         }
607     }
608 
609     private void nodeTypeTest(int axis) throws SAXPathException
610     {
611         Token  nodeTypeToken = match( TokenTypes.IDENTIFIER );
612         String nodeType      = nodeTypeToken.getTokenText();
613 
614         match( TokenTypes.LEFT_PAREN );
615 
616         if ( "processing-instruction".equals( nodeType ) )
617         {
618             String piName = "";
619 
620             if ( LA(1) == TokenTypes.LITERAL )
621             {
622                 piName = match( TokenTypes.LITERAL ).getTokenText();
623             }
624 
625             match( TokenTypes.RIGHT_PAREN );
626 
627             getXPathHandler().startProcessingInstructionNodeStep( axis,
628                                                                   piName );
629 
630             predicates();
631 
632             getXPathHandler().endProcessingInstructionNodeStep();
633         }
634         else if ( "node".equals( nodeType ) )
635         {
636             match( TokenTypes.RIGHT_PAREN );
637 
638             getXPathHandler().startAllNodeStep( axis );
639 
640             predicates();
641 
642             getXPathHandler().endAllNodeStep();
643         }
644         else if ( "text".equals( nodeType ) )
645         {
646             match( TokenTypes.RIGHT_PAREN );
647 
648             getXPathHandler().startTextNodeStep( axis );
649 
650             predicates();
651 
652             getXPathHandler().endTextNodeStep();
653         }
654         else if ( "comment".equals( nodeType ) )
655         {
656             match( TokenTypes.RIGHT_PAREN );
657 
658             getXPathHandler().startCommentNodeStep( axis );
659 
660             predicates();
661 
662             getXPathHandler().endCommentNodeStep();
663         }
664         else
665         {
666             XPathSyntaxException ex = createSyntaxException( "Expected node-type" );
667             throw ex;
668         }
669     }
670 
671     private void nameTest(int axis) throws SAXPathException
672     {
673         String prefix    = null;
674         String localName = null;
675 
676         switch ( LA(2) )
677         {
678             case TokenTypes.COLON:
679             {
680                 switch ( LA(1) )
681                 {
682                     case TokenTypes.IDENTIFIER:
683                     {
684                         prefix = match( TokenTypes.IDENTIFIER ).getTokenText();
685                         match( TokenTypes.COLON );
686                         break;
687                     }
688                 }
689                 break;
690             }
691         }
692         
693         switch ( LA(1) )
694         {
695             case TokenTypes.IDENTIFIER:
696             {
697                 localName = match( TokenTypes.IDENTIFIER ).getTokenText();
698                 break;
699             }
700             case TokenTypes.STAR:
701             {
702                 match( TokenTypes.STAR );
703                 localName = "*";
704                 break;
705             }
706         }
707 
708         if ( prefix == null )
709         {
710             prefix = "";
711         }
712         
713         getXPathHandler().startNameStep( axis,
714                                          prefix,
715                                          localName );
716 
717         predicates();
718 
719         getXPathHandler().endNameStep();
720     }
721 
722     private void abbrStep() throws SAXPathException
723     {
724         switch ( LA(1) )
725         {
726             case TokenTypes.DOT:
727             {
728                 match( TokenTypes.DOT );
729                 getXPathHandler().startAllNodeStep( Axis.SELF );
730                 predicates();
731                 getXPathHandler().endAllNodeStep();
732                 break;
733             }
734             case TokenTypes.DOT_DOT:
735             {
736                 match( TokenTypes.DOT_DOT );
737                 getXPathHandler().startAllNodeStep( Axis.PARENT );
738                 predicates();
739                 getXPathHandler().endAllNodeStep();
740                 break;
741             }
742         }
743     }
744 
745     private void predicates() throws SAXPathException
746     {
747         while (true )
748         {
749             if ( LA(1) == TokenTypes.LEFT_BRACKET )
750             {
751                 predicate();
752             }
753             else
754             {
755                 break;
756             }
757         }
758     }
759     
760     void predicate() throws SAXPathException
761     {
762         getXPathHandler().startPredicate();
763         
764         match( TokenTypes.LEFT_BRACKET );
765         
766         predicateExpr();
767 
768         match( TokenTypes.RIGHT_BRACKET );
769 
770         getXPathHandler().endPredicate();
771     }
772 
773     private void predicateExpr() throws SAXPathException
774     {
775         expr();
776     }
777 
778     private void expr() throws SAXPathException
779     {
780         orExpr();
781     }
782 
783     private void orExpr() throws SAXPathException
784     {
785         getXPathHandler().startOrExpr();
786         
787         andExpr();
788 
789         boolean create = false;
790 
791         switch ( LA(1) )
792         {
793             case TokenTypes.OR:
794             {
795                 create = true;
796                 match( TokenTypes.OR );
797                 orExpr();
798                 break;
799             }
800         }
801 
802         getXPathHandler().endOrExpr( create );
803     }
804 
805     private void andExpr() throws SAXPathException
806     {
807         getXPathHandler().startAndExpr();
808 
809         equalityExpr();
810 
811         boolean create = false;
812 
813         switch ( LA(1) )
814         {
815             case TokenTypes.AND:
816             {
817                 create = true;
818                 match( TokenTypes.AND );
819                 andExpr();
820                 break;
821             }
822         }
823 
824         getXPathHandler().endAndExpr( create );
825     }
826 
827     private void equalityExpr() throws SAXPathException
828     {
829         relationalExpr();
830 
831         int la = LA(1);
832         while (la == TokenTypes.EQUALS || la == TokenTypes.NOT_EQUALS)
833         {
834             switch ( la )
835             {
836                 case TokenTypes.EQUALS:
837                 {
838                     match( TokenTypes.EQUALS );
839                     getXPathHandler().startEqualityExpr();
840                     relationalExpr();
841                     getXPathHandler().endEqualityExpr( Operator.EQUALS );
842                     break;
843                 }
844                 case TokenTypes.NOT_EQUALS:
845                 {
846                     match( TokenTypes.NOT_EQUALS );
847                     getXPathHandler().startEqualityExpr();
848                     relationalExpr();
849                     getXPathHandler().endEqualityExpr( Operator.NOT_EQUALS );
850                     break;
851                 }
852             }
853             la = LA(1);
854         }
855     }
856     
857     private void relationalExpr() throws SAXPathException
858     {
859 
860         additiveExpr();
861 
862         int la = LA(1);
863         // Very important: TokenTypes.LESS_THAN != Operator.LESS_THAN
864         //                 TokenTypes.GREATER_THAN != Operator.GREATER_THAN
865         //                 TokenTypes.GREATER_THAN_EQUALS != Operator.GREATER_THAN_EQUALS
866         //                 TokenTypes.LESS_THAN_EQUALS != Operator.LESS_THAN_EQUALS
867         while (la == TokenTypes.LESS_THAN_SIGN 
868             || la == TokenTypes.GREATER_THAN_SIGN 
869             || la == TokenTypes.LESS_THAN_OR_EQUALS_SIGN 
870             || la == TokenTypes.GREATER_THAN_OR_EQUALS_SIGN ) {
871             switch ( la )
872             {
873                 case TokenTypes.LESS_THAN_SIGN:
874                 {
875                     match( TokenTypes.LESS_THAN_SIGN );
876                     getXPathHandler().startRelationalExpr();
877                     additiveExpr();
878                     getXPathHandler().endRelationalExpr( Operator.LESS_THAN );
879                     break;
880                 }
881                 case TokenTypes.GREATER_THAN_SIGN:
882                 {
883                     match( TokenTypes.GREATER_THAN_SIGN );
884                     getXPathHandler().startRelationalExpr();
885                     additiveExpr();
886                     getXPathHandler().endRelationalExpr( Operator.GREATER_THAN );
887                     break;
888                 }
889                 case TokenTypes.GREATER_THAN_OR_EQUALS_SIGN:
890                 {
891                     match( TokenTypes.GREATER_THAN_OR_EQUALS_SIGN );
892                     getXPathHandler().startRelationalExpr();
893                     additiveExpr();
894                     getXPathHandler().endRelationalExpr( Operator.GREATER_THAN_EQUALS );
895                     break;
896                 }
897                 case TokenTypes.LESS_THAN_OR_EQUALS_SIGN:
898                 {
899                     match( TokenTypes.LESS_THAN_OR_EQUALS_SIGN );
900                     getXPathHandler().startRelationalExpr();
901                     additiveExpr();
902                     getXPathHandler().endRelationalExpr( Operator.LESS_THAN_EQUALS );
903                     break;
904                 }
905             }
906             la = LA(1);
907         }
908     } 
909 
910     
911     private void additiveExpr() throws SAXPathException
912     {
913         multiplicativeExpr();
914 
915         int la = LA(1);
916         while (la == TokenTypes.PLUS || la == TokenTypes.MINUS)
917         {
918             switch ( la )
919             {
920                 case TokenTypes.PLUS:
921                 {
922                     match( TokenTypes.PLUS );
923                     getXPathHandler().startAdditiveExpr();
924                     multiplicativeExpr();
925                     getXPathHandler().endAdditiveExpr( Operator.ADD );
926                     break;
927                 }
928                 case TokenTypes.MINUS:
929                 {
930                     match( TokenTypes.MINUS );
931                     getXPathHandler().startAdditiveExpr();
932                     multiplicativeExpr();
933                     getXPathHandler().endAdditiveExpr( Operator.SUBTRACT );
934                     break;
935                 }
936             }
937             la = LA(1);
938         }
939     }
940 
941     private void multiplicativeExpr() throws SAXPathException
942     {
943         unaryExpr();
944        
945         int la = LA(1);
946         while (la == TokenTypes.STAR_OPERATOR || la == TokenTypes.DIV || la == TokenTypes.MOD)
947         {
948             switch ( la )
949             {
950                 case TokenTypes.STAR:
951                 case TokenTypes.STAR_OPERATOR:
952                 {
953                     match( TokenTypes.STAR_OPERATOR );
954                     getXPathHandler().startMultiplicativeExpr();
955                     unaryExpr();
956                     getXPathHandler().endMultiplicativeExpr( Operator.MULTIPLY );
957                     break;
958                 }
959                 case TokenTypes.DIV:
960                 {
961                     match( TokenTypes.DIV );
962                     getXPathHandler().startMultiplicativeExpr();
963                     unaryExpr();
964                     getXPathHandler().endMultiplicativeExpr( Operator.DIV );
965                     break;
966                 }
967                 case TokenTypes.MOD:
968                 {
969                     match( TokenTypes.MOD );
970                     getXPathHandler().startMultiplicativeExpr();
971                     unaryExpr();
972                     getXPathHandler().endMultiplicativeExpr( Operator.MOD );
973                     break;
974                 }
975             }
976             la = LA(1);
977         }
978 
979     }
980 
981     private void unaryExpr() throws SAXPathException
982     {
983         switch ( LA(1) )
984         {
985             case TokenTypes.MINUS:
986             {
987                 getXPathHandler().startUnaryExpr();
988                 match( TokenTypes.MINUS );
989                 unaryExpr();
990                 getXPathHandler().endUnaryExpr( Operator.NEGATIVE );
991                 break;
992             }
993             default:
994             {
995                 unionExpr();
996                 break;
997             }
998         }
999 
1000         
1001     }
1002 
1003     private void unionExpr() throws SAXPathException
1004     {
1005         getXPathHandler().startUnionExpr();
1006 
1007         pathExpr();
1008 
1009         boolean create = false;
1010 
1011         switch ( LA(1) )
1012         {
1013             case TokenTypes.PIPE:
1014             {
1015                 match( TokenTypes.PIPE );
1016                 create = true;
1017                 expr();
1018                 break;
1019             }
1020         }
1021 
1022         getXPathHandler().endUnionExpr( create );
1023     }
1024 
1025     private Token match(int tokenType) throws XPathSyntaxException
1026     {
1027         LT(1);
1028 
1029         Token token = (Token) tokens.get( 0 );
1030 
1031         if ( token.getTokenType() == tokenType )
1032         {
1033             tokens.remove(0);
1034             return token;
1035         }
1036 
1037         
1038         XPathSyntaxException ex = createSyntaxException( "Expected: " + TokenTypes.getTokenText( tokenType ) );
1039         throw ex;
1040     }
1041 
1042     private int LA(int position)
1043     {
1044         return LT(position).getTokenType();
1045     }
1046 
1047     
1048     // XXX This method's a HotSpot; could we improve it?
1049     private Token LT(int position)
1050     {
1051         if ( tokens.size() <= ( position - 1 ) )
1052         {
1053             for ( int i = 0 ; i < position ; ++i )
1054             {
1055                 tokens.add( lexer.nextToken() );
1056             }
1057         }
1058 
1059         return tokens.get( position - 1 );
1060     }
1061 
1062     private boolean isNodeTypeName(Token name)
1063     {
1064         String text = name.getTokenText();
1065 
1066         if ( "node".equals( text )
1067              ||
1068              "comment".equals( text )
1069              ||
1070              "text".equals( text )
1071              ||
1072              "processing-instruction".equals( text ) )
1073         {
1074             return true;
1075         }
1076 
1077         return false;
1078     }
1079 
1080     private XPathSyntaxException createSyntaxException(String message)
1081     {
1082         String xpath    = this.lexer.getXPath();
1083         int    position = LT(1).getTokenBegin();
1084 
1085         return new XPathSyntaxException( xpath,
1086                                          position,
1087                                          message );
1088     }
1089 
1090     private void throwInvalidAxis(String invalidAxis) throws SAXPathException
1091     {
1092         String xpath    = this.lexer.getXPath();
1093         int    position = LT(1).getTokenBegin();
1094 
1095         String message  = "Expected valid axis name instead of [" + invalidAxis + "]";
1096 
1097         throw new XPathSyntaxException( xpath,
1098                                         position,
1099                                         message );
1100     }
1101 }