Source code: org/apache/xpath/compiler/XPathParser.java
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;
22 import javax.xml.transform.TransformerException;
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 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 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 errorListener, javax.xml.transform.SourceLocator 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 compiler, String expression, PrefixResolver namespaceContext)
100 throws javax.xml.transform.TransformerException
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 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[]{ 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 compiler, String expression, PrefixResolver namespaceContext)
175 throws javax.xml.transform.TransformerException
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 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[]{ 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 m_errorListener;
219
220 /** The source location of the XPath. */
221 javax.xml.transform.SourceLocator 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 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 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 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 tok = ((String) 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 lookbehind = (String) 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 lookbehind = (String) 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 s, int n)
390 {
391
392 boolean isToken;
393
394 if ((m_queueMark + n) <= m_ops.getTokenQueueSize())
395 {
396 String lookahead = (String) 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) 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 getTokenRelative(int i)
436 {
437
438 String tok;
439 int relative = m_queueMark + i;
440
441 if ((relative > 0) && (relative < m_ops.getTokenQueueSize()))
442 {
443 tok = (String) 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) 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 expected)
483 throws javax.xml.transform.TransformerException
484 {
485
486 if (tokenIs(expected))
487 {
488 nextToken();
489 }
490 else
491 {
492 error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND, new Object[]{ 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
512 {
513
514 if (tokenIs(expected))
515 {
516 nextToken();
517 }
518 else
519 {
520 error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND,
521 new Object[]{ 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 msg, Object[] args) throws TransformerException
544 {
545
546 String fmsg = XSLMessages.createXPATHWarning(msg, args);
547 ErrorListener ehandler = this.getErrorListener();
548
549 if (null != ehandler)
550 {
551 // TO DO: Need to get stylesheet Locator from here.
552 ehandler.warning(new TransformerException(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 msg)
571 {
572
573 if (!b)
574 {
575 String fMsg = XSLMessages.createXPATHMessage(
576 XPATHErrorResources.ER_INCORRECT_PROGRAMMER_ASSERTION,
577 new Object[]{ msg });
578
579 throw new RuntimeException(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 msg, Object[] args) throws TransformerException
597 {
598
599 String fmsg = XSLMessages.createXPATHMessage(msg, args);
600 ErrorListener ehandler = this.getErrorListener();
601
602 TransformerException te = new TransformerException(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 dumpRemainingTokenQueue()
623 {
624
625 int q = m_queueMark;
626 String returnMsg;
627
628 if (q < m_ops.getTokenQueueSize())
629 {
630 String msg = "\n Remaining tokens: (";
631
632 while (q < m_ops.getTokenQueueSize())
633 {
634 String t = (String) 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 key)
659 {
660
661 int tok;
662
663 try
664 {
665 tok = ((Integer) (Keywords.m_functions.get(key))).intValue();
666 }
667 catch (NullPointerException npe)
668 {
669 tok = -1;
670 }
671 catch (ClassCastException 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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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[]{ 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
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 [] {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
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
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
1