001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 /*
019 * $Id: XPathParser.java 468655 2006-10-28 07:12:06Z minchau $
020 */
021 package org.apache.xpath.compiler;
022
023 import javax.xml.transform.ErrorListener;
024 import javax.xml.transform.TransformerException;
025
026 import org.apache.xalan.res.XSLMessages;
027 import org.apache.xml.utils.PrefixResolver;
028 import org.apache.xpath.XPathProcessorException;
029 import org.apache.xpath.domapi.XPathStylesheetDOM3Exception;
030 import org.apache.xpath.objects.XNumber;
031 import org.apache.xpath.objects.XString;
032 import org.apache.xpath.res.XPATHErrorResources;
033
034 /**
035 * Tokenizes and parses XPath expressions. This should really be named
036 * XPathParserImpl, and may be renamed in the future.
037 * @xsl.usage general
038 */
039 public class XPathParser
040 {
041 // %REVIEW% Is there a better way of doing this?
042 // Upside is minimum object churn. Downside is that we don't have a useful
043 // backtrace in the exception itself -- but we don't expect to need one.
044 static public final String CONTINUE_AFTER_FATAL_ERROR="CONTINUE_AFTER_FATAL_ERROR";
045
046 /**
047 * The XPath to be processed.
048 */
049 private OpMap m_ops;
050
051 /**
052 * The next token in the pattern.
053 */
054 transient String m_token;
055
056 /**
057 * The first char in m_token, the theory being that this
058 * is an optimization because we won't have to do charAt(0) as
059 * often.
060 */
061 transient char m_tokenChar = 0;
062
063 /**
064 * The position in the token queue is tracked by m_queueMark.
065 */
066 int m_queueMark = 0;
067
068 /**
069 * Results from checking FilterExpr syntax
070 */
071 protected final static int FILTER_MATCH_FAILED = 0;
072 protected final static int FILTER_MATCH_PRIMARY = 1;
073 protected final static int FILTER_MATCH_PREDICATES = 2;
074
075 /**
076 * The parser constructor.
077 */
078 public XPathParser(ErrorListener errorListener, javax.xml.transform.SourceLocator sourceLocator)
079 {
080 m_errorListener = errorListener;
081 m_sourceLocator = sourceLocator;
082 }
083
084 /**
085 * The prefix resolver to map prefixes to namespaces in the OpMap.
086 */
087 PrefixResolver m_namespaceContext;
088
089 /**
090 * Given an string, init an XPath object for selections,
091 * in order that a parse doesn't
092 * have to be done each time the expression is evaluated.
093 *
094 * @param compiler The compiler object.
095 * @param expression A string conforming to the XPath grammar.
096 * @param namespaceContext An object that is able to resolve prefixes in
097 * the XPath to namespaces.
098 *
099 * @throws javax.xml.transform.TransformerException
100 */
101 public void initXPath(
102 Compiler compiler, String expression, PrefixResolver namespaceContext)
103 throws javax.xml.transform.TransformerException
104 {
105
106 m_ops = compiler;
107 m_namespaceContext = namespaceContext;
108 m_functionTable = compiler.getFunctionTable();
109
110 Lexer lexer = new Lexer(compiler, namespaceContext, this);
111
112 lexer.tokenize(expression);
113
114 m_ops.setOp(0,OpCodes.OP_XPATH);
115 m_ops.setOp(OpMap.MAPINDEX_LENGTH,2);
116
117
118 // Patch for Christine's gripe. She wants her errorHandler to return from
119 // a fatal error and continue trying to parse, rather than throwing an exception.
120 // Without the patch, that put us into an endless loop.
121 //
122 // %REVIEW% Is there a better way of doing this?
123 // %REVIEW% Are there any other cases which need the safety net?
124 // (and if so do we care right now, or should we rewrite the XPath
125 // grammar engine and can fix it at that time?)
126 try {
127
128 nextToken();
129 Expr();
130
131 if (null != m_token)
132 {
133 String extraTokens = "";
134
135 while (null != m_token)
136 {
137 extraTokens += "'" + m_token + "'";
138
139 nextToken();
140
141 if (null != m_token)
142 extraTokens += ", ";
143 }
144
145 error(XPATHErrorResources.ER_EXTRA_ILLEGAL_TOKENS,
146 new Object[]{ extraTokens }); //"Extra illegal tokens: "+extraTokens);
147 }
148
149 }
150 catch (org.apache.xpath.XPathProcessorException e)
151 {
152 if(CONTINUE_AFTER_FATAL_ERROR.equals(e.getMessage()))
153 {
154 // What I _want_ to do is null out this XPath.
155 // I doubt this has the desired effect, but I'm not sure what else to do.
156 // %REVIEW%!!!
157 initXPath(compiler, "/..", namespaceContext);
158 }
159 else
160 throw e;
161 }
162
163 compiler.shrink();
164 }
165
166 /**
167 * Given an string, init an XPath object for pattern matches,
168 * in order that a parse doesn't
169 * have to be done each time the expression is evaluated.
170 * @param compiler The XPath object to be initialized.
171 * @param expression A String representing the XPath.
172 * @param namespaceContext An object that is able to resolve prefixes in
173 * the XPath to namespaces.
174 *
175 * @throws javax.xml.transform.TransformerException
176 */
177 public void initMatchPattern(
178 Compiler compiler, String expression, PrefixResolver namespaceContext)
179 throws javax.xml.transform.TransformerException
180 {
181
182 m_ops = compiler;
183 m_namespaceContext = namespaceContext;
184 m_functionTable = compiler.getFunctionTable();
185
186 Lexer lexer = new Lexer(compiler, namespaceContext, this);
187
188 lexer.tokenize(expression);
189
190 m_ops.setOp(0, OpCodes.OP_MATCHPATTERN);
191 m_ops.setOp(OpMap.MAPINDEX_LENGTH, 2);
192
193 nextToken();
194 Pattern();
195
196 if (null != m_token)
197 {
198 String extraTokens = "";
199
200 while (null != m_token)
201 {
202 extraTokens += "'" + m_token + "'";
203
204 nextToken();
205
206 if (null != m_token)
207 extraTokens += ", ";
208 }
209
210 error(XPATHErrorResources.ER_EXTRA_ILLEGAL_TOKENS,
211 new Object[]{ extraTokens }); //"Extra illegal tokens: "+extraTokens);
212 }
213
214 // Terminate for safety.
215 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
216 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH)+1);
217
218 m_ops.shrink();
219 }
220
221 /** The error listener where syntax errors are to be sent.
222 */
223 private ErrorListener m_errorListener;
224
225 /** The source location of the XPath. */
226 javax.xml.transform.SourceLocator m_sourceLocator;
227
228 /** The table contains build-in functions and customized functions */
229 private FunctionTable m_functionTable;
230
231 /**
232 * Allow an application to register an error event handler, where syntax
233 * errors will be sent. If the error listener is not set, syntax errors
234 * will be sent to System.err.
235 *
236 * @param handler Reference to error listener where syntax errors will be
237 * sent.
238 */
239 public void setErrorHandler(ErrorListener handler)
240 {
241 m_errorListener = handler;
242 }
243
244 /**
245 * Return the current error listener.
246 *
247 * @return The error listener, which should not normally be null, but may be.
248 */
249 public ErrorListener getErrorListener()
250 {
251 return m_errorListener;
252 }
253
254 /**
255 * Check whether m_token matches the target string.
256 *
257 * @param s A string reference or null.
258 *
259 * @return If m_token is null, returns false (or true if s is also null), or
260 * return true if the current token matches the string, else false.
261 */
262 final boolean tokenIs(String s)
263 {
264 return (m_token != null) ? (m_token.equals(s)) : (s == null);
265 }
266
267 /**
268 * Check whether m_tokenChar==c.
269 *
270 * @param c A character to be tested.
271 *
272 * @return If m_token is null, returns false, or return true if c matches
273 * the current token.
274 */
275 final boolean tokenIs(char c)
276 {
277 return (m_token != null) ? (m_tokenChar == c) : false;
278 }
279
280 /**
281 * Look ahead of the current token in order to
282 * make a branching decision.
283 *
284 * @param c the character to be tested for.
285 * @param n number of tokens to look ahead. Must be
286 * greater than 1.
287 *
288 * @return true if the next token matches the character argument.
289 */
290 final boolean lookahead(char c, int n)
291 {
292
293 int pos = (m_queueMark + n);
294 boolean b;
295
296 if ((pos <= m_ops.getTokenQueueSize()) && (pos > 0)
297 && (m_ops.getTokenQueueSize() != 0))
298 {
299 String tok = ((String) m_ops.m_tokenQueue.elementAt(pos - 1));
300
301 b = (tok.length() == 1) ? (tok.charAt(0) == c) : false;
302 }
303 else
304 {
305 b = false;
306 }
307
308 return b;
309 }
310
311 /**
312 * Look behind the first character of the current token in order to
313 * make a branching decision.
314 *
315 * @param c the character to compare it to.
316 * @param n number of tokens to look behind. Must be
317 * greater than 1. Note that the look behind terminates
318 * at either the beginning of the string or on a '|'
319 * character. Because of this, this method should only
320 * be used for pattern matching.
321 *
322 * @return true if the token behind the current token matches the character
323 * argument.
324 */
325 private final boolean lookbehind(char c, int n)
326 {
327
328 boolean isToken;
329 int lookBehindPos = m_queueMark - (n + 1);
330
331 if (lookBehindPos >= 0)
332 {
333 String lookbehind = (String) m_ops.m_tokenQueue.elementAt(lookBehindPos);
334
335 if (lookbehind.length() == 1)
336 {
337 char c0 = (lookbehind == null) ? '|' : lookbehind.charAt(0);
338
339 isToken = (c0 == '|') ? false : (c0 == c);
340 }
341 else
342 {
343 isToken = false;
344 }
345 }
346 else
347 {
348 isToken = false;
349 }
350
351 return isToken;
352 }
353
354 /**
355 * look behind the current token in order to
356 * see if there is a useable token.
357 *
358 * @param n number of tokens to look behind. Must be
359 * greater than 1. Note that the look behind terminates
360 * at either the beginning of the string or on a '|'
361 * character. Because of this, this method should only
362 * be used for pattern matching.
363 *
364 * @return true if look behind has a token, false otherwise.
365 */
366 private final boolean lookbehindHasToken(int n)
367 {
368
369 boolean hasToken;
370
371 if ((m_queueMark - n) > 0)
372 {
373 String lookbehind = (String) m_ops.m_tokenQueue.elementAt(m_queueMark - (n - 1));
374 char c0 = (lookbehind == null) ? '|' : lookbehind.charAt(0);
375
376 hasToken = (c0 == '|') ? false : true;
377 }
378 else
379 {
380 hasToken = false;
381 }
382
383 return hasToken;
384 }
385
386 /**
387 * Look ahead of the current token in order to
388 * make a branching decision.
389 *
390 * @param s the string to compare it to.
391 * @param n number of tokens to lookahead. Must be
392 * greater than 1.
393 *
394 * @return true if the token behind the current token matches the string
395 * argument.
396 */
397 private final boolean lookahead(String s, int n)
398 {
399
400 boolean isToken;
401
402 if ((m_queueMark + n) <= m_ops.getTokenQueueSize())
403 {
404 String lookahead = (String) m_ops.m_tokenQueue.elementAt(m_queueMark + (n - 1));
405
406 isToken = (lookahead != null) ? lookahead.equals(s) : (s == null);
407 }
408 else
409 {
410 isToken = (null == s);
411 }
412
413 return isToken;
414 }
415
416 /**
417 * Retrieve the next token from the command and
418 * store it in m_token string.
419 */
420 private final void nextToken()
421 {
422
423 if (m_queueMark < m_ops.getTokenQueueSize())
424 {
425 m_token = (String) m_ops.m_tokenQueue.elementAt(m_queueMark++);
426 m_tokenChar = m_token.charAt(0);
427 }
428 else
429 {
430 m_token = null;
431 m_tokenChar = 0;
432 }
433 }
434
435 /**
436 * Retrieve a token relative to the current token.
437 *
438 * @param i Position relative to current token.
439 *
440 * @return The string at the given index, or null if the index is out
441 * of range.
442 */
443 private final String getTokenRelative(int i)
444 {
445
446 String tok;
447 int relative = m_queueMark + i;
448
449 if ((relative > 0) && (relative < m_ops.getTokenQueueSize()))
450 {
451 tok = (String) m_ops.m_tokenQueue.elementAt(relative);
452 }
453 else
454 {
455 tok = null;
456 }
457
458 return tok;
459 }
460
461 /**
462 * Retrieve the previous token from the command and
463 * store it in m_token string.
464 */
465 private final void prevToken()
466 {
467
468 if (m_queueMark > 0)
469 {
470 m_queueMark--;
471
472 m_token = (String) m_ops.m_tokenQueue.elementAt(m_queueMark);
473 m_tokenChar = m_token.charAt(0);
474 }
475 else
476 {
477 m_token = null;
478 m_tokenChar = 0;
479 }
480 }
481
482 /**
483 * Consume an expected token, throwing an exception if it
484 * isn't there.
485 *
486 * @param expected The string to be expected.
487 *
488 * @throws javax.xml.transform.TransformerException
489 */
490 private final void consumeExpected(String expected)
491 throws javax.xml.transform.TransformerException
492 {
493
494 if (tokenIs(expected))
495 {
496 nextToken();
497 }
498 else
499 {
500 error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND, new Object[]{ expected,
501 m_token }); //"Expected "+expected+", but found: "+m_token);
502
503 // Patch for Christina's gripe. She wants her errorHandler to return from
504 // this error and continue trying to parse, rather than throwing an exception.
505 // Without the patch, that put us into an endless loop.
506 throw new XPathProcessorException(CONTINUE_AFTER_FATAL_ERROR);
507 }
508 }
509
510 /**
511 * Consume an expected token, throwing an exception if it
512 * isn't there.
513 *
514 * @param expected the character to be expected.
515 *
516 * @throws javax.xml.transform.TransformerException
517 */
518 private final void consumeExpected(char expected)
519 throws javax.xml.transform.TransformerException
520 {
521
522 if (tokenIs(expected))
523 {
524 nextToken();
525 }
526 else
527 {
528 error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND,
529 new Object[]{ String.valueOf(expected),
530 m_token }); //"Expected "+expected+", but found: "+m_token);
531
532 // Patch for Christina's gripe. She wants her errorHandler to return from
533 // this error and continue trying to parse, rather than throwing an exception.
534 // Without the patch, that put us into an endless loop.
535 throw new XPathProcessorException(CONTINUE_AFTER_FATAL_ERROR);
536 }
537 }
538
539 /**
540 * Warn the user of a problem.
541 *
542 * @param msg An error msgkey that corresponds to one of the constants found
543 * in {@link org.apache.xpath.res.XPATHErrorResources}, which is
544 * a key for a format string.
545 * @param args An array of arguments represented in the format string, which
546 * may be null.
547 *
548 * @throws TransformerException if the current ErrorListoner determines to
549 * throw an exception.
550 */
551 void warn(String msg, Object[] args) throws TransformerException
552 {
553
554 String fmsg = XSLMessages.createXPATHWarning(msg, args);
555 ErrorListener ehandler = this.getErrorListener();
556
557 if (null != ehandler)
558 {
559 // TO DO: Need to get stylesheet Locator from here.
560 ehandler.warning(new TransformerException(fmsg, m_sourceLocator));
561 }
562 else
563 {
564 // Should never happen.
565 System.err.println(fmsg);
566 }
567 }
568
569 /**
570 * Notify the user of an assertion error, and probably throw an
571 * exception.
572 *
573 * @param b If false, a runtime exception will be thrown.
574 * @param msg The assertion message, which should be informative.
575 *
576 * @throws RuntimeException if the b argument is false.
577 */
578 private void assertion(boolean b, String msg)
579 {
580
581 if (!b)
582 {
583 String fMsg = XSLMessages.createXPATHMessage(
584 XPATHErrorResources.ER_INCORRECT_PROGRAMMER_ASSERTION,
585 new Object[]{ msg });
586
587 throw new RuntimeException(fMsg);
588 }
589 }
590
591 /**
592 * Notify the user of an error, and probably throw an
593 * exception.
594 *
595 * @param msg An error msgkey that corresponds to one of the constants found
596 * in {@link org.apache.xpath.res.XPATHErrorResources}, which is
597 * a key for a format string.
598 * @param args An array of arguments represented in the format string, which
599 * may be null.
600 *
601 * @throws TransformerException if the current ErrorListoner determines to
602 * throw an exception.
603 */
604 void error(String msg, Object[] args) throws TransformerException
605 {
606
607 String fmsg = XSLMessages.createXPATHMessage(msg, args);
608 ErrorListener ehandler = this.getErrorListener();
609
610 TransformerException te = new TransformerException(fmsg, m_sourceLocator);
611 if (null != ehandler)
612 {
613 // TO DO: Need to get stylesheet Locator from here.
614 ehandler.fatalError(te);
615 }
616 else
617 {
618 // System.err.println(fmsg);
619 throw te;
620 }
621 }
622
623 /**
624 * This method is added to support DOM 3 XPath API.
625 * <p>
626 * This method is exactly like error(String, Object[]); except that
627 * the underlying TransformerException is
628 * XpathStylesheetDOM3Exception (which extends TransformerException).
629 * <p>
630 * So older XPath code in Xalan is not affected by this. To older XPath code
631 * the behavior of whether error() or errorForDOM3() is called because it is
632 * always catching TransformerException objects and is oblivious to
633 * the new subclass of XPathStylesheetDOM3Exception. Older XPath code
634 * runs as before.
635 * <p>
636 * However, newer DOM3 XPath code upon catching a TransformerException can
637 * can check if the exception is an instance of XPathStylesheetDOM3Exception
638 * and take appropriate action.
639 *
640 * @param msg An error msgkey that corresponds to one of the constants found
641 * in {@link org.apache.xpath.res.XPATHErrorResources}, which is
642 * a key for a format string.
643 * @param args An array of arguments represented in the format string, which
644 * may be null.
645 *
646 * @throws TransformerException if the current ErrorListoner determines to
647 * throw an exception.
648 */
649 void errorForDOM3(String msg, Object[] args) throws TransformerException
650 {
651
652 String fmsg = XSLMessages.createXPATHMessage(msg, args);
653 ErrorListener ehandler = this.getErrorListener();
654
655 TransformerException te = new XPathStylesheetDOM3Exception(fmsg, m_sourceLocator);
656 if (null != ehandler)
657 {
658 // TO DO: Need to get stylesheet Locator from here.
659 ehandler.fatalError(te);
660 }
661 else
662 {
663 // System.err.println(fmsg);
664 throw te;
665 }
666 }
667 /**
668 * Dump the remaining token queue.
669 * Thanks to Craig for this.
670 *
671 * @return A dump of the remaining token queue, which may be appended to
672 * an error message.
673 */
674 protected String dumpRemainingTokenQueue()
675 {
676
677 int q = m_queueMark;
678 String returnMsg;
679
680 if (q < m_ops.getTokenQueueSize())
681 {
682 String msg = "\n Remaining tokens: (";
683
684 while (q < m_ops.getTokenQueueSize())
685 {
686 String t = (String) m_ops.m_tokenQueue.elementAt(q++);
687
688 msg += (" '" + t + "'");
689 }
690
691 returnMsg = msg + ")";
692 }
693 else
694 {
695 returnMsg = "";
696 }
697
698 return returnMsg;
699 }
700
701 /**
702 * Given a string, return the corresponding function token.
703 *
704 * @param key A local name of a function.
705 *
706 * @return The function ID, which may correspond to one of the FUNC_XXX
707 * values found in {@link org.apache.xpath.compiler.FunctionTable}, but may
708 * be a value installed by an external module.
709 */
710 final int getFunctionToken(String key)
711 {
712
713 int tok;
714
715 Object id;
716
717 try
718 {
719 // These are nodetests, xpathparser treats them as functions when parsing
720 // a FilterExpr.
721 id = Keywords.lookupNodeTest(key);
722 if (null == id) id = m_functionTable.getFunctionID(key);
723 tok = ((Integer) id).intValue();
724 }
725 catch (NullPointerException npe)
726 {
727 tok = -1;
728 }
729 catch (ClassCastException cce)
730 {
731 tok = -1;
732 }
733
734 return tok;
735 }
736
737 /**
738 * Insert room for operation. This will NOT set
739 * the length value of the operation, but will update
740 * the length value for the total expression.
741 *
742 * @param pos The position where the op is to be inserted.
743 * @param length The length of the operation space in the op map.
744 * @param op The op code to the inserted.
745 */
746 void insertOp(int pos, int length, int op)
747 {
748
749 int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
750
751 for (int i = totalLen - 1; i >= pos; i--)
752 {
753 m_ops.setOp(i + length, m_ops.getOp(i));
754 }
755
756 m_ops.setOp(pos,op);
757 m_ops.setOp(OpMap.MAPINDEX_LENGTH,totalLen + length);
758 }
759
760 /**
761 * Insert room for operation. This WILL set
762 * the length value of the operation, and will update
763 * the length value for the total expression.
764 *
765 * @param length The length of the operation.
766 * @param op The op code to the inserted.
767 */
768 void appendOp(int length, int op)
769 {
770
771 int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
772
773 m_ops.setOp(totalLen, op);
774 m_ops.setOp(totalLen + OpMap.MAPINDEX_LENGTH, length);
775 m_ops.setOp(OpMap.MAPINDEX_LENGTH, totalLen + length);
776 }
777
778 // ============= EXPRESSIONS FUNCTIONS =================
779
780 /**
781 *
782 *
783 * Expr ::= OrExpr
784 *
785 *
786 * @throws javax.xml.transform.TransformerException
787 */
788 protected void Expr() throws javax.xml.transform.TransformerException
789 {
790 OrExpr();
791 }
792
793 /**
794 *
795 *
796 * OrExpr ::= AndExpr
797 * | OrExpr 'or' AndExpr
798 *
799 *
800 * @throws javax.xml.transform.TransformerException
801 */
802 protected void OrExpr() throws javax.xml.transform.TransformerException
803 {
804
805 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
806
807 AndExpr();
808
809 if ((null != m_token) && tokenIs("or"))
810 {
811 nextToken();
812 insertOp(opPos, 2, OpCodes.OP_OR);
813 OrExpr();
814
815 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
816 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
817 }
818 }
819
820 /**
821 *
822 *
823 * AndExpr ::= EqualityExpr
824 * | AndExpr 'and' EqualityExpr
825 *
826 *
827 * @throws javax.xml.transform.TransformerException
828 */
829 protected void AndExpr() throws javax.xml.transform.TransformerException
830 {
831
832 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
833
834 EqualityExpr(-1);
835
836 if ((null != m_token) && tokenIs("and"))
837 {
838 nextToken();
839 insertOp(opPos, 2, OpCodes.OP_AND);
840 AndExpr();
841
842 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
843 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
844 }
845 }
846
847 /**
848 *
849 * @returns an Object which is either a String, a Number, a Boolean, or a vector
850 * of nodes.
851 *
852 * EqualityExpr ::= RelationalExpr
853 * | EqualityExpr '=' RelationalExpr
854 *
855 *
856 * @param addPos Position where expression is to be added, or -1 for append.
857 *
858 * @return the position at the end of the equality expression.
859 *
860 * @throws javax.xml.transform.TransformerException
861 */
862 protected int EqualityExpr(int addPos) throws javax.xml.transform.TransformerException
863 {
864
865 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
866
867 if (-1 == addPos)
868 addPos = opPos;
869
870 RelationalExpr(-1);
871
872 if (null != m_token)
873 {
874 if (tokenIs('!') && lookahead('=', 1))
875 {
876 nextToken();
877 nextToken();
878 insertOp(addPos, 2, OpCodes.OP_NOTEQUALS);
879
880 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
881
882 addPos = EqualityExpr(addPos);
883 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
884 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
885 addPos += 2;
886 }
887 else if (tokenIs('='))
888 {
889 nextToken();
890 insertOp(addPos, 2, OpCodes.OP_EQUALS);
891
892 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
893
894 addPos = EqualityExpr(addPos);
895 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
896 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
897 addPos += 2;
898 }
899 }
900
901 return addPos;
902 }
903
904 /**
905 * .
906 * @returns an Object which is either a String, a Number, a Boolean, or a vector
907 * of nodes.
908 *
909 * RelationalExpr ::= AdditiveExpr
910 * | RelationalExpr '<' AdditiveExpr
911 * | RelationalExpr '>' AdditiveExpr
912 * | RelationalExpr '<=' AdditiveExpr
913 * | RelationalExpr '>=' AdditiveExpr
914 *
915 *
916 * @param addPos Position where expression is to be added, or -1 for append.
917 *
918 * @return the position at the end of the relational expression.
919 *
920 * @throws javax.xml.transform.TransformerException
921 */
922 protected int RelationalExpr(int addPos) throws javax.xml.transform.TransformerException
923 {
924
925 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
926
927 if (-1 == addPos)
928 addPos = opPos;
929
930 AdditiveExpr(-1);
931
932 if (null != m_token)
933 {
934 if (tokenIs('<'))
935 {
936 nextToken();
937
938 if (tokenIs('='))
939 {
940 nextToken();
941 insertOp(addPos, 2, OpCodes.OP_LTE);
942 }
943 else
944 {
945 insertOp(addPos, 2, OpCodes.OP_LT);
946 }
947
948 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
949
950 addPos = RelationalExpr(addPos);
951 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
952 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
953 addPos += 2;
954 }
955 else if (tokenIs('>'))
956 {
957 nextToken();
958
959 if (tokenIs('='))
960 {
961 nextToken();
962 insertOp(addPos, 2, OpCodes.OP_GTE);
963 }
964 else
965 {
966 insertOp(addPos, 2, OpCodes.OP_GT);
967 }
968
969 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
970
971 addPos = RelationalExpr(addPos);
972 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
973 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
974 addPos += 2;
975 }
976 }
977
978 return addPos;
979 }
980
981 /**
982 * This has to handle construction of the operations so that they are evaluated
983 * in pre-fix order. So, for 9+7-6, instead of |+|9|-|7|6|, this needs to be
984 * evaluated as |-|+|9|7|6|.
985 *
986 * AdditiveExpr ::= MultiplicativeExpr
987 * | AdditiveExpr '+' MultiplicativeExpr
988 * | AdditiveExpr '-' MultiplicativeExpr
989 *
990 *
991 * @param addPos Position where expression is to be added, or -1 for append.
992 *
993 * @return the position at the end of the equality expression.
994 *
995 * @throws javax.xml.transform.TransformerException
996 */
997 protected int AdditiveExpr(int addPos) throws javax.xml.transform.TransformerException
998 {
999
1000 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1001
1002 if (-1 == addPos)
1003 addPos = opPos;
1004
1005 MultiplicativeExpr(-1);
1006
1007 if (null != m_token)
1008 {
1009 if (tokenIs('+'))
1010 {
1011 nextToken();
1012 insertOp(addPos, 2, OpCodes.OP_PLUS);
1013
1014 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1015
1016 addPos = AdditiveExpr(addPos);
1017 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1018 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1019 addPos += 2;
1020 }
1021 else if (tokenIs('-'))
1022 {
1023 nextToken();
1024 insertOp(addPos, 2, OpCodes.OP_MINUS);
1025
1026 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1027
1028 addPos = AdditiveExpr(addPos);
1029 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1030 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1031 addPos += 2;
1032 }
1033 }
1034
1035 return addPos;
1036 }
1037
1038 /**
1039 * This has to handle construction of the operations so that they are evaluated
1040 * in pre-fix order. So, for 9+7-6, instead of |+|9|-|7|6|, this needs to be
1041 * evaluated as |-|+|9|7|6|.
1042 *
1043 * MultiplicativeExpr ::= UnaryExpr
1044 * | MultiplicativeExpr MultiplyOperator UnaryExpr
1045 * | MultiplicativeExpr 'div' UnaryExpr
1046 * | MultiplicativeExpr 'mod' UnaryExpr
1047 * | MultiplicativeExpr 'quo' UnaryExpr
1048 *
1049 * @param addPos Position where expression is to be added, or -1 for append.
1050 *
1051 * @return the position at the end of the equality expression.
1052 *
1053 * @throws javax.xml.transform.TransformerException
1054 */
1055 protected int MultiplicativeExpr(int addPos) throws javax.xml.transform.TransformerException
1056 {
1057
1058 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1059
1060 if (-1 == addPos)
1061 addPos = opPos;
1062
1063 UnaryExpr();
1064
1065 if (null != m_token)
1066 {
1067 if (tokenIs('*'))
1068 {
1069 nextToken();
1070 insertOp(addPos, 2, OpCodes.OP_MULT);
1071
1072 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1073
1074 addPos = MultiplicativeExpr(addPos);
1075 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1076 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1077 addPos += 2;
1078 }
1079 else if (tokenIs("div"))
1080 {
1081 nextToken();
1082 insertOp(addPos, 2, OpCodes.OP_DIV);
1083
1084 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1085
1086 addPos = MultiplicativeExpr(addPos);
1087 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1088 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1089 addPos += 2;
1090 }
1091 else if (tokenIs("mod"))
1092 {
1093 nextToken();
1094 insertOp(addPos, 2, OpCodes.OP_MOD);
1095
1096 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1097
1098 addPos = MultiplicativeExpr(addPos);
1099 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1100 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1101 addPos += 2;
1102 }
1103 else if (tokenIs("quo"))
1104 {
1105 nextToken();
1106 insertOp(addPos, 2, OpCodes.OP_QUO);
1107
1108 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos;
1109
1110 addPos = MultiplicativeExpr(addPos);
1111 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH,
1112 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen);
1113 addPos += 2;
1114 }
1115 }
1116
1117 return addPos;
1118 }
1119
1120 /**
1121 *
1122 * UnaryExpr ::= UnionExpr
1123 * | '-' UnaryExpr
1124 *
1125 *
1126 * @throws javax.xml.transform.TransformerException
1127 */
1128 protected void UnaryExpr() throws javax.xml.transform.TransformerException
1129 {
1130
1131 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1132 boolean isNeg = false;
1133
1134 if (m_tokenChar == '-')
1135 {
1136 nextToken();
1137 appendOp(2, OpCodes.OP_NEG);
1138
1139 isNeg = true;
1140 }
1141
1142 UnionExpr();
1143
1144 if (isNeg)
1145 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1146 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1147 }
1148
1149 /**
1150 *
1151 * StringExpr ::= Expr
1152 *
1153 *
1154 * @throws javax.xml.transform.TransformerException
1155 */
1156 protected void StringExpr() throws javax.xml.transform.TransformerException
1157 {
1158
1159 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1160
1161 appendOp(2, OpCodes.OP_STRING);
1162 Expr();
1163
1164 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1165 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1166 }
1167
1168 /**
1169 *
1170 *
1171 * StringExpr ::= Expr
1172 *
1173 *
1174 * @throws javax.xml.transform.TransformerException
1175 */
1176 protected void BooleanExpr() throws javax.xml.transform.TransformerException
1177 {
1178
1179 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1180
1181 appendOp(2, OpCodes.OP_BOOL);
1182 Expr();
1183
1184 int opLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos;
1185
1186 if (opLen == 2)
1187 {
1188 error(XPATHErrorResources.ER_BOOLEAN_ARG_NO_LONGER_OPTIONAL, null); //"boolean(...) argument is no longer optional with 19990709 XPath draft.");
1189 }
1190
1191 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, opLen);
1192 }
1193
1194 /**
1195 *
1196 *
1197 * NumberExpr ::= Expr
1198 *
1199 *
1200 * @throws javax.xml.transform.TransformerException
1201 */
1202 protected void NumberExpr() throws javax.xml.transform.TransformerException
1203 {
1204
1205 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1206
1207 appendOp(2, OpCodes.OP_NUMBER);
1208 Expr();
1209
1210 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1211 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1212 }
1213
1214 /**
1215 * The context of the right hand side expressions is the context of the
1216 * left hand side expression. The results of the right hand side expressions
1217 * are node sets. The result of the left hand side UnionExpr is the union
1218 * of the results of the right hand side expressions.
1219 *
1220 *
1221 * UnionExpr ::= PathExpr
1222 * | UnionExpr '|' PathExpr
1223 *
1224 *
1225 * @throws javax.xml.transform.TransformerException
1226 */
1227 protected void UnionExpr() throws javax.xml.transform.TransformerException
1228 {
1229
1230 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1231 boolean continueOrLoop = true;
1232 boolean foundUnion = false;
1233
1234 do
1235 {
1236 PathExpr();
1237
1238 if (tokenIs('|'))
1239 {
1240 if (false == foundUnion)
1241 {
1242 foundUnion = true;
1243
1244 insertOp(opPos, 2, OpCodes.OP_UNION);
1245 }
1246
1247 nextToken();
1248 }
1249 else
1250 {
1251 break;
1252 }
1253
1254 // this.m_testForDocOrder = true;
1255 }
1256 while (continueOrLoop);
1257
1258 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1259 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1260 }
1261
1262 /**
1263 * PathExpr ::= LocationPath
1264 * | FilterExpr
1265 * | FilterExpr '/' RelativeLocationPath
1266 * | FilterExpr '//' RelativeLocationPath
1267 *
1268 * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
1269 * the error condition is severe enough to halt processing.
1270 *
1271 * @throws javax.xml.transform.TransformerException
1272 */
1273 protected void PathExpr() throws javax.xml.transform.TransformerException
1274 {
1275
1276 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1277
1278 int filterExprMatch = FilterExpr();
1279
1280 if (filterExprMatch != FILTER_MATCH_FAILED)
1281 {
1282 // If FilterExpr had Predicates, a OP_LOCATIONPATH opcode would already
1283 // have been inserted.
1284 boolean locationPathStarted = (filterExprMatch==FILTER_MATCH_PREDICATES);
1285
1286 if (tokenIs('/'))
1287 {
1288 nextToken();
1289
1290 if (!locationPathStarted)
1291 {
1292 // int locationPathOpPos = opPos;
1293 insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH);
1294
1295 locationPathStarted = true;
1296 }
1297
1298 if (!RelativeLocationPath())
1299 {
1300 // "Relative location path expected following '/' or '//'"
1301 error(XPATHErrorResources.ER_EXPECTED_REL_LOC_PATH, null);
1302 }
1303
1304 }
1305
1306 // Terminate for safety.
1307 if (locationPathStarted)
1308 {
1309 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1310 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1311 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1312 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1313 }
1314 }
1315 else
1316 {
1317 LocationPath();
1318 }
1319 }
1320
1321 /**
1322 *
1323 *
1324 * FilterExpr ::= PrimaryExpr
1325 * | FilterExpr Predicate
1326 *
1327 * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
1328 * the error condition is severe enough to halt processing.
1329 *
1330 * @return FILTER_MATCH_PREDICATES, if this method successfully matched a
1331 * FilterExpr with one or more Predicates;
1332 * FILTER_MATCH_PRIMARY, if this method successfully matched a
1333 * FilterExpr that was just a PrimaryExpr; or
1334 * FILTER_MATCH_FAILED, if this method did not match a FilterExpr
1335 *
1336 * @throws javax.xml.transform.TransformerException
1337 */
1338 protected int FilterExpr() throws javax.xml.transform.TransformerException
1339 {
1340
1341 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1342
1343 int filterMatch;
1344
1345 if (PrimaryExpr())
1346 {
1347 if (tokenIs('['))
1348 {
1349
1350 // int locationPathOpPos = opPos;
1351 insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH);
1352
1353 while (tokenIs('['))
1354 {
1355 Predicate();
1356 }
1357
1358 filterMatch = FILTER_MATCH_PREDICATES;
1359 }
1360 else
1361 {
1362 filterMatch = FILTER_MATCH_PRIMARY;
1363 }
1364 }
1365 else
1366 {
1367 filterMatch = FILTER_MATCH_FAILED;
1368 }
1369
1370 return filterMatch;
1371
1372 /*
1373 * if(tokenIs('['))
1374 * {
1375 * Predicate();
1376 * m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos;
1377 * }
1378 */
1379 }
1380
1381 /**
1382 *
1383 * PrimaryExpr ::= VariableReference
1384 * | '(' Expr ')'
1385 * | Literal
1386 * | Number
1387 * | FunctionCall
1388 *
1389 * @return true if this method successfully matched a PrimaryExpr
1390 *
1391 * @throws javax.xml.transform.TransformerException
1392 *
1393 */
1394 protected boolean PrimaryExpr() throws javax.xml.transform.TransformerException
1395 {
1396
1397 boolean matchFound;
1398 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1399
1400 if ((m_tokenChar == '\'') || (m_tokenChar == '"'))
1401 {
1402 appendOp(2, OpCodes.OP_LITERAL);
1403 Literal();
1404
1405 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1406 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1407
1408 matchFound = true;
1409 }
1410 else if (m_tokenChar == '$')
1411 {
1412 nextToken(); // consume '$'
1413 appendOp(2, OpCodes.OP_VARIABLE);
1414 QName();
1415
1416 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1417 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1418
1419 matchFound = true;
1420 }
1421 else if (m_tokenChar == '(')
1422 {
1423 nextToken();
1424 appendOp(2, OpCodes.OP_GROUP);
1425 Expr();
1426 consumeExpected(')');
1427
1428 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1429 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1430
1431 matchFound = true;
1432 }
1433 else if ((null != m_token) && ((('.' == m_tokenChar) && (m_token.length() > 1) && Character.isDigit(
1434 m_token.charAt(1))) || Character.isDigit(m_tokenChar)))
1435 {
1436 appendOp(2, OpCodes.OP_NUMBERLIT);
1437 Number();
1438
1439 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1440 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1441
1442 matchFound = true;
1443 }
1444 else if (lookahead('(', 1) || (lookahead(':', 1) && lookahead('(', 3)))
1445 {
1446 matchFound = FunctionCall();
1447 }
1448 else
1449 {
1450 matchFound = false;
1451 }
1452
1453 return matchFound;
1454 }
1455
1456 /**
1457 *
1458 * Argument ::= Expr
1459 *
1460 *
1461 * @throws javax.xml.transform.TransformerException
1462 */
1463 protected void Argument() throws javax.xml.transform.TransformerException
1464 {
1465
1466 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1467
1468 appendOp(2, OpCodes.OP_ARGUMENT);
1469 Expr();
1470
1471 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1472 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1473 }
1474
1475 /**
1476 *
1477 * FunctionCall ::= FunctionName '(' ( Argument ( ',' Argument)*)? ')'
1478 *
1479 * @return true if, and only if, a FunctionCall was matched
1480 *
1481 * @throws javax.xml.transform.TransformerException
1482 */
1483 protected boolean FunctionCall() throws javax.xml.transform.TransformerException
1484 {
1485
1486 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1487
1488 if (lookahead(':', 1))
1489 {
1490 appendOp(4, OpCodes.OP_EXTFUNCTION);
1491
1492 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, m_queueMark - 1);
1493
1494 nextToken();
1495 consumeExpected(':');
1496
1497 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 2, m_queueMark - 1);
1498
1499 nextToken();
1500 }
1501 else
1502 {
1503 int funcTok = getFunctionToken(m_token);
1504
1505 if (-1 == funcTok)
1506 {
1507 error(XPATHErrorResources.ER_COULDNOT_FIND_FUNCTION,
1508 new Object[]{ m_token }); //"Could not find function: "+m_token+"()");
1509 }
1510
1511 switch (funcTok)
1512 {
1513 case OpCodes.NODETYPE_PI :
1514 case OpCodes.NODETYPE_COMMENT :
1515 case OpCodes.NODETYPE_TEXT :
1516 case OpCodes.NODETYPE_NODE :
1517 // Node type tests look like function calls, but they're not
1518 return false;
1519 default :
1520 appendOp(3, OpCodes.OP_FUNCTION);
1521
1522 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, funcTok);
1523 }
1524
1525 nextToken();
1526 }
1527
1528 consumeExpected('(');
1529
1530 while (!tokenIs(')') && m_token != null)
1531 {
1532 if (tokenIs(','))
1533 {
1534 error(XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_PRECEDING_ARG, null); //"Found ',' but no preceding argument!");
1535 }
1536
1537 Argument();
1538
1539 if (!tokenIs(')'))
1540 {
1541 consumeExpected(',');
1542
1543 if (tokenIs(')'))
1544 {
1545 error(XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_FOLLOWING_ARG,
1546 null); //"Found ',' but no following argument!");
1547 }
1548 }
1549 }
1550
1551 consumeExpected(')');
1552
1553 // Terminate for safety.
1554 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1555 m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1556 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1557 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1558
1559 return true;
1560 }
1561
1562 // ============= GRAMMAR FUNCTIONS =================
1563
1564 /**
1565 *
1566 * LocationPath ::= RelativeLocationPath
1567 * | AbsoluteLocationPath
1568 *
1569 *
1570 * @throws javax.xml.transform.TransformerException
1571 */
1572 protected void LocationPath() throws javax.xml.transform.TransformerException
1573 {
1574
1575 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1576
1577 // int locationPathOpPos = opPos;
1578 appendOp(2, OpCodes.OP_LOCATIONPATH);
1579
1580 boolean seenSlash = tokenIs('/');
1581
1582 if (seenSlash)
1583 {
1584 appendOp(4, OpCodes.FROM_ROOT);
1585
1586 // Tell how long the step is without the predicate
1587 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
1588 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_ROOT);
1589
1590 nextToken();
1591 } else if (m_token == null) {
1592 error(XPATHErrorResources.ER_EXPECTED_LOC_PATH_AT_END_EXPR, null);
1593 }
1594
1595 if (m_token != null)
1596 {
1597 if (!RelativeLocationPath() && !seenSlash)
1598 {
1599 // Neither a '/' nor a RelativeLocationPath - i.e., matched nothing
1600 // "Location path expected, but found "+m_token+" was encountered."
1601 error(XPATHErrorResources.ER_EXPECTED_LOC_PATH,
1602 new Object [] {m_token});
1603 }
1604 }
1605
1606 // Terminate for safety.
1607 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1608 m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1609 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1610 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1611 }
1612
1613 /**
1614 *
1615 * RelativeLocationPath ::= Step
1616 * | RelativeLocationPath '/' Step
1617 * | AbbreviatedRelativeLocationPath
1618 *
1619 * @returns true if, and only if, a RelativeLocationPath was matched
1620 *
1621 * @throws javax.xml.transform.TransformerException
1622 */
1623 protected boolean RelativeLocationPath()
1624 throws javax.xml.transform.TransformerException
1625 {
1626 if (!Step())
1627 {
1628 return false;
1629 }
1630
1631 while (tokenIs('/'))
1632 {
1633 nextToken();
1634
1635 if (!Step())
1636 {
1637 // RelativeLocationPath can't end with a trailing '/'
1638 // "Location step expected following '/' or '//'"
1639 error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null);
1640 }
1641 }
1642
1643 return true;
1644 }
1645
1646 /**
1647 *
1648 * Step ::= Basis Predicate
1649 * | AbbreviatedStep
1650 *
1651 * @returns false if step was empty (or only a '/'); true, otherwise
1652 *
1653 * @throws javax.xml.transform.TransformerException
1654 */
1655 protected boolean Step() throws javax.xml.transform.TransformerException
1656 {
1657 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1658
1659 boolean doubleSlash = tokenIs('/');
1660
1661 // At most a single '/' before each Step is consumed by caller; if the
1662 // first thing is a '/', that means we had '//' and the Step must not
1663 // be empty.
1664 if (doubleSlash)
1665 {
1666 nextToken();
1667
1668 appendOp(2, OpCodes.FROM_DESCENDANTS_OR_SELF);
1669
1670 // Have to fix up for patterns such as '//@foo' or '//attribute::foo',
1671 // which translate to 'descendant-or-self::node()/attribute::foo'.
1672 // notice I leave the '/' on the queue, so the next will be processed
1673 // by a regular step pattern.
1674
1675 // Make room for telling how long the step is without the predicate
1676 m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1677 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.NODETYPE_NODE);
1678 m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1679
1680 // Tell how long the step is without the predicate
1681 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
1682 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1683
1684 // Tell how long the step is with the predicate
1685 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1686 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1687
1688 opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1689 }
1690
1691 if (tokenIs("."))
1692 {
1693 nextToken();
1694
1695 if (tokenIs('['))
1696 {
1697 error(XPATHErrorResources.ER_PREDICATE_ILLEGAL_SYNTAX, null); //"'..[predicate]' or '.[predicate]' is illegal syntax. Use 'self::node()[predicate]' instead.");
1698 }
1699
1700 appendOp(4, OpCodes.FROM_SELF);
1701
1702 // Tell how long the step is without the predicate
1703 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2,4);
1704 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_NODE);
1705 }
1706 else if (tokenIs(".."))
1707 {
1708 nextToken();
1709 appendOp(4, OpCodes.FROM_PARENT);
1710
1711 // Tell how long the step is without the predicate
1712 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2,4);
1713 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_NODE);
1714 }
1715
1716 // There is probably a better way to test for this
1717 // transition... but it gets real hairy if you try
1718 // to do it in basis().
1719 else if (tokenIs('*') || tokenIs('@') || tokenIs('_')
1720 || (m_token!= null && Character.isLetter(m_token.charAt(0))))
1721 {
1722 Basis();
1723
1724 while (tokenIs('['))
1725 {
1726 Predicate();
1727 }
1728
1729 // Tell how long the entire step is.
1730 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1731 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1732 }
1733 else
1734 {
1735 // No Step matched - that's an error if previous thing was a '//'
1736 if (doubleSlash)
1737 {
1738 // "Location step expected following '/' or '//'"
1739 error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null);
1740 }
1741
1742 return false;
1743 }
1744
1745 return true;
1746 }
1747
1748 /**
1749 *
1750 * Basis ::= AxisName '::' NodeTest
1751 * | AbbreviatedBasis
1752 *
1753 * @throws javax.xml.transform.TransformerException
1754 */
1755 protected void Basis() throws javax.xml.transform.TransformerException
1756 {
1757
1758 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1759 int axesType;
1760
1761 // The next blocks guarantee that a FROM_XXX will be added.
1762 if (lookahead("::", 1))
1763 {
1764 axesType = AxisName();
1765
1766 nextToken();
1767 nextToken();
1768 }
1769 else if (tokenIs('@'))
1770 {
1771 axesType = OpCodes.FROM_ATTRIBUTES;
1772
1773 appendOp(2, axesType);
1774 nextToken();
1775 }
1776 else
1777 {
1778 axesType = OpCodes.FROM_CHILDREN;
1779
1780 appendOp(2, axesType);
1781 }
1782
1783 // Make room for telling how long the step is without the predicate
1784 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1785
1786 NodeTest(axesType);
1787
1788 // Tell how long the step is without the predicate
1789 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
1790 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1791 }
1792
1793 /**
1794 *
1795 * Basis ::= AxisName '::' NodeTest
1796 * | AbbreviatedBasis
1797 *
1798 * @return FROM_XXX axes type, found in {@link org.apache.xpath.compiler.Keywords}.
1799 *
1800 * @throws javax.xml.transform.TransformerException
1801 */
1802 protected int AxisName() throws javax.xml.transform.TransformerException
1803 {
1804
1805 Object val = Keywords.getAxisName(m_token);
1806
1807 if (null == val)
1808 {
1809 error(XPATHErrorResources.ER_ILLEGAL_AXIS_NAME,
1810 new Object[]{ m_token }); //"illegal axis name: "+m_token);
1811 }
1812
1813 int axesType = ((Integer) val).intValue();
1814
1815 appendOp(2, axesType);
1816
1817 return axesType;
1818 }
1819
1820 /**
1821 *
1822 * NodeTest ::= WildcardName
1823 * | NodeType '(' ')'
1824 * | 'processing-instruction' '(' Literal ')'
1825 *
1826 * @param axesType FROM_XXX axes type, found in {@link org.apache.xpath.compiler.Keywords}.
1827 *
1828 * @throws javax.xml.transform.TransformerException
1829 */
1830 protected void NodeTest(int axesType) throws javax.xml.transform.TransformerException
1831 {
1832
1833 if (lookahead('(', 1))
1834 {
1835 Object nodeTestOp = Keywords.getNodeType(m_token);
1836
1837 if (null == nodeTestOp)
1838 {
1839 error(XPATHErrorResources.ER_UNKNOWN_NODETYPE,
1840 new Object[]{ m_token }); //"Unknown nodetype: "+m_token);
1841 }
1842 else
1843 {
1844 nextToken();
1845
1846 int nt = ((Integer) nodeTestOp).intValue();
1847
1848 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), nt);
1849 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1850
1851 consumeExpected('(');
1852
1853 if (OpCodes.NODETYPE_PI == nt)
1854 {
1855 if (!tokenIs(')'))
1856 {
1857 Literal();
1858 }
1859 }
1860
1861 consumeExpected(')');
1862 }
1863 }
1864 else
1865 {
1866
1867 // Assume name of attribute or element.
1868 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.NODENAME);
1869 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1870
1871 if (lookahead(':', 1))
1872 {
1873 if (tokenIs('*'))
1874 {
1875 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ELEMWILDCARD);
1876 }
1877 else
1878 {
1879 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1880
1881 // Minimalist check for an NCName - just check first character
1882 // to distinguish from other possible tokens
1883 if (!Character.isLetter(m_tokenChar) && !tokenIs('_'))
1884 {
1885 // "Node test that matches either NCName:* or QName was expected."
1886 error(XPATHErrorResources.ER_EXPECTED_NODE_TEST, null);
1887 }
1888 }
1889
1890 nextToken();
1891 consumeExpected(':');
1892 }
1893 else
1894 {
1895 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.EMPTY);
1896 }
1897
1898 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1899
1900 if (tokenIs('*'))
1901 {
1902 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ELEMWILDCARD);
1903 }
1904 else
1905 {
1906 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1907
1908 // Minimalist check for an NCName - just check first character
1909 // to distinguish from other possible tokens
1910 if (!Character.isLetter(m_tokenChar) && !tokenIs('_'))
1911 {
1912 // "Node test that matches either NCName:* or QName was expected."
1913 error(XPATHErrorResources.ER_EXPECTED_NODE_TEST, null);
1914 }
1915 }
1916
1917 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1918
1919 nextToken();
1920 }
1921 }
1922
1923 /**
1924 *
1925 * Predicate ::= '[' PredicateExpr ']'
1926 *
1927 *
1928 * @throws javax.xml.transform.TransformerException
1929 */
1930 protected void Predicate() throws javax.xml.transform.TransformerException
1931 {
1932
1933 if (tokenIs('['))
1934 {
1935 nextToken();
1936 PredicateExpr();
1937 consumeExpected(']');
1938 }
1939 }
1940
1941 /**
1942 *
1943 * PredicateExpr ::= Expr
1944 *
1945 *
1946 * @throws javax.xml.transform.TransformerException
1947 */
1948 protected void PredicateExpr() throws javax.xml.transform.TransformerException
1949 {
1950
1951 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
1952
1953 appendOp(2, OpCodes.OP_PREDICATE);
1954 Expr();
1955
1956 // Terminate for safety.
1957 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
1958 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1959 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
1960 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
1961 }
1962
1963 /**
1964 * QName ::= (Prefix ':')? LocalPart
1965 * Prefix ::= NCName
1966 * LocalPart ::= NCName
1967 *
1968 * @throws javax.xml.transform.TransformerException
1969 */
1970 protected void QName() throws javax.xml.transform.TransformerException
1971 {
1972 // Namespace
1973 if(lookahead(':', 1))
1974 {
1975 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1976 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1977
1978 nextToken();
1979 consumeExpected(':');
1980 }
1981 else
1982 {
1983 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.EMPTY);
1984 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1985 }
1986
1987 // Local name
1988 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
1989 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
1990
1991 nextToken();
1992 }
1993
1994 /**
1995 * NCName ::= (Letter | '_') (NCNameChar)
1996 * NCNameChar ::= Letter | Digit | '.' | '-' | '_' | CombiningChar | Extender
1997 */
1998 protected void NCName()
1999 {
2000
2001 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
2002 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
2003
2004 nextToken();
2005 }
2006
2007 /**
2008 * The value of the Literal is the sequence of characters inside
2009 * the " or ' characters>.
2010 *
2011 * Literal ::= '"' [^"]* '"'
2012 * | "'" [^']* "'"
2013 *
2014 *
2015 * @throws javax.xml.transform.TransformerException
2016 */
2017 protected void Literal() throws javax.xml.transform.TransformerException
2018 {
2019
2020 int last = m_token.length() - 1;
2021 char c0 = m_tokenChar;
2022 char cX = m_token.charAt(last);
2023
2024 if (((c0 == '\"') && (cX == '\"')) || ((c0 == '\'') && (cX == '\'')))
2025 {
2026
2027 // Mutate the token to remove the quotes and have the XString object
2028 // already made.
2029 int tokenQueuePos = m_queueMark - 1;
2030
2031 m_ops.m_tokenQueue.setElementAt(null,tokenQueuePos);
2032
2033 Object obj = new XString(m_token.substring(1, last));
2034
2035 m_ops.m_tokenQueue.setElementAt(obj,tokenQueuePos);
2036
2037 // lit = m_token.substring(1, last);
2038 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), tokenQueuePos);
2039 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
2040
2041 nextToken();
2042 }
2043 else
2044 {
2045 error(XPATHErrorResources.ER_PATTERN_LITERAL_NEEDS_BE_QUOTED,
2046 new Object[]{ m_token }); //"Pattern literal ("+m_token+") needs to be quoted!");
2047 }
2048 }
2049
2050 /**
2051 *
2052 * Number ::= [0-9]+('.'[0-9]+)? | '.'[0-9]+
2053 *
2054 *
2055 * @throws javax.xml.transform.TransformerException
2056 */
2057 protected void Number() throws javax.xml.transform.TransformerException
2058 {
2059
2060 if (null != m_token)
2061 {
2062
2063 // Mutate the token to remove the quotes and have the XNumber object
2064 // already made.
2065 double num;
2066
2067 try
2068 {
2069 // XPath 1.0 does not support number in exp notation
2070 if ((m_token.indexOf('e') > -1)||(m_token.indexOf('E') > -1))
2071 throw new NumberFormatException();
2072 num = Double.valueOf(m_token).doubleValue();
2073 }
2074 catch (NumberFormatException nfe)
2075 {
2076 num = 0.0; // to shut up compiler.
2077
2078 error(XPATHErrorResources.ER_COULDNOT_BE_FORMATTED_TO_NUMBER,
2079 new Object[]{ m_token }); //m_token+" could not be formatted to a number!");
2080 }
2081
2082 m_ops.m_tokenQueue.setElementAt(new XNumber(num),m_queueMark - 1);
2083 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
2084 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
2085
2086 nextToken();
2087 }
2088 }
2089
2090 // ============= PATTERN FUNCTIONS =================
2091
2092 /**
2093 *
2094 * Pattern ::= LocationPathPattern
2095 * | Pattern '|' LocationPathPattern
2096 *
2097 *
2098 * @throws javax.xml.transform.TransformerException
2099 */
2100 protected void Pattern() throws javax.xml.transform.TransformerException
2101 {
2102
2103 while (true)
2104 {
2105 LocationPathPattern();
2106
2107 if (tokenIs('|'))
2108 {
2109 nextToken();
2110 }
2111 else
2112 {
2113 break;
2114 }
2115 }
2116 }
2117
2118 /**
2119 *
2120 *
2121 * LocationPathPattern ::= '/' RelativePathPattern?
2122 * | IdKeyPattern (('/' | '//') RelativePathPattern)?
2123 * | '//'? RelativePathPattern
2124 *
2125 *
2126 * @throws javax.xml.transform.TransformerException
2127 */
2128 protected void LocationPathPattern() throws javax.xml.transform.TransformerException
2129 {
2130
2131 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2132
2133 final int RELATIVE_PATH_NOT_PERMITTED = 0;
2134 final int RELATIVE_PATH_PERMITTED = 1;
2135 final int RELATIVE_PATH_REQUIRED = 2;
2136
2137 int relativePathStatus = RELATIVE_PATH_NOT_PERMITTED;
2138
2139 appendOp(2, OpCodes.OP_LOCATIONPATHPATTERN);
2140
2141 if (lookahead('(', 1)
2142 && (tokenIs(Keywords.FUNC_ID_STRING)
2143 || tokenIs(Keywords.FUNC_KEY_STRING)))
2144 {
2145 IdKeyPattern();
2146
2147 if (tokenIs('/'))
2148 {
2149 nextToken();
2150
2151 if (tokenIs('/'))
2152 {
2153 appendOp(4, OpCodes.MATCH_ANY_ANCESTOR);
2154
2155 nextToken();
2156 }
2157 else
2158 {
2159 appendOp(4, OpCodes.MATCH_IMMEDIATE_ANCESTOR);
2160 }
2161
2162 // Tell how long the step is without the predicate
2163 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
2164 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_FUNCTEST);
2165
2166 relativePathStatus = RELATIVE_PATH_REQUIRED;
2167 }
2168 }
2169 else if (tokenIs('/'))
2170 {
2171 if (lookahead('/', 1))
2172 {
2173 appendOp(4, OpCodes.MATCH_ANY_ANCESTOR);
2174
2175 // Added this to fix bug reported by Myriam for match="//x/a"
2176 // patterns. If you don't do this, the 'x' step will think it's part
2177 // of a '//' pattern, and so will cause 'a' to be matched when it has
2178 // any ancestor that is 'x'.
2179 nextToken();
2180
2181 relativePathStatus = RELATIVE_PATH_REQUIRED;
2182 }
2183 else
2184 {
2185 appendOp(4, OpCodes.FROM_ROOT);
2186
2187 relativePathStatus = RELATIVE_PATH_PERMITTED;
2188 }
2189
2190
2191 // Tell how long the step is without the predicate
2192 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4);
2193 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_ROOT);
2194
2195 nextToken();
2196 }
2197 else
2198 {
2199 relativePathStatus = RELATIVE_PATH_REQUIRED;
2200 }
2201
2202 if (relativePathStatus != RELATIVE_PATH_NOT_PERMITTED)
2203 {
2204 if (!tokenIs('|') && (null != m_token))
2205 {
2206 RelativePathPattern();
2207 }
2208 else if (relativePathStatus == RELATIVE_PATH_REQUIRED)
2209 {
2210 // "A relative path pattern was expected."
2211 error(XPATHErrorResources.ER_EXPECTED_REL_PATH_PATTERN, null);
2212 }
2213 }
2214
2215 // Terminate for safety.
2216 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP);
2217 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
2218 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
2219 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
2220 }
2221
2222 /**
2223 *
2224 * IdKeyPattern ::= 'id' '(' Literal ')'
2225 * | 'key' '(' Literal ',' Literal ')'
2226 * (Also handle doc())
2227 *
2228 *
2229 * @throws javax.xml.transform.TransformerException
2230 */
2231 protected void IdKeyPattern() throws javax.xml.transform.TransformerException
2232 {
2233 FunctionCall();
2234 }
2235
2236 /**
2237 *
2238 * RelativePathPattern ::= StepPattern
2239 * | RelativePathPattern '/' StepPattern
2240 * | RelativePathPattern '//' StepPattern
2241 *
2242 * @throws javax.xml.transform.TransformerException
2243 */
2244 protected void RelativePathPattern()
2245 throws javax.xml.transform.TransformerException
2246 {
2247
2248 // Caller will have consumed any '/' or '//' preceding the
2249 // RelativePathPattern, so let StepPattern know it can't begin with a '/'
2250 boolean trailingSlashConsumed = StepPattern(false);
2251
2252 while (tokenIs('/'))
2253 {
2254 nextToken();
2255
2256 // StepPattern() may consume first slash of pair in "a//b" while
2257 // processing StepPattern "a". On next iteration, let StepPattern know
2258 // that happened, so it doesn't match ill-formed patterns like "a///b".
2259 trailingSlashConsumed = StepPattern(!trailingSlashConsumed);
2260 }
2261 }
2262
2263 /**
2264 *
2265 * StepPattern ::= AbbreviatedNodeTestStep
2266 *
2267 * @param isLeadingSlashPermitted a boolean indicating whether a slash can
2268 * appear at the start of this step
2269 *
2270 * @return boolean indicating whether a slash following the step was consumed
2271 *
2272 * @throws javax.xml.transform.TransformerException
2273 */
2274 protected boolean StepPattern(boolean isLeadingSlashPermitted)
2275 throws javax.xml.transform.TransformerException
2276 {
2277 return AbbreviatedNodeTestStep(isLeadingSlashPermitted);
2278 }
2279
2280 /**
2281 *
2282 * AbbreviatedNodeTestStep ::= '@'? NodeTest Predicate
2283 *
2284 * @param isLeadingSlashPermitted a boolean indicating whether a slash can
2285 * appear at the start of this step
2286 *
2287 * @return boolean indicating whether a slash following the step was consumed
2288 *
2289 * @throws javax.xml.transform.TransformerException
2290 */
2291 protected boolean AbbreviatedNodeTestStep(boolean isLeadingSlashPermitted)
2292 throws javax.xml.transform.TransformerException
2293 {
2294
2295 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2296 int axesType;
2297
2298 // The next blocks guarantee that a MATCH_XXX will be added.
2299 int matchTypePos = -1;
2300
2301 if (tokenIs('@'))
2302 {
2303 axesType = OpCodes.MATCH_ATTRIBUTE;
2304
2305 appendOp(2, axesType);
2306 nextToken();
2307 }
2308 else if (this.lookahead("::", 1))
2309 {
2310 if (tokenIs("attribute"))
2311 {
2312 axesType = OpCodes.MATCH_ATTRIBUTE;
2313
2314 appendOp(2, axesType);
2315 }
2316 else if (tokenIs("child"))
2317 {
2318 matchTypePos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2319 axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR;
2320
2321 appendOp(2, axesType);
2322 }
2323 else
2324 {
2325 axesType = -1;
2326
2327 this.error(XPATHErrorResources.ER_AXES_NOT_ALLOWED,
2328 new Object[]{ this.m_token });
2329 }
2330
2331 nextToken();
2332 nextToken();
2333 }
2334 else if (tokenIs('/'))
2335 {
2336 if (!isLeadingSlashPermitted)
2337 {
2338 // "A step was expected in the pattern, but '/' was encountered."
2339 error(XPATHErrorResources.ER_EXPECTED_STEP_PATTERN, null);
2340 }
2341 axesType = OpCodes.MATCH_ANY_ANCESTOR;
2342
2343 appendOp(2, axesType);
2344 nextToken();
2345 }
2346 else
2347 {
2348 matchTypePos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
2349 axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR;
2350
2351 appendOp(2, axesType);
2352 }
2353
2354 // Make room for telling how long the step is without the predicate
2355 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
2356
2357 NodeTest(axesType);
2358
2359 // Tell how long the step is without the predicate
2360 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1,
2361 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
2362
2363 while (tokenIs('['))
2364 {
2365 Predicate();
2366 }
2367
2368 boolean trailingSlashConsumed;
2369
2370 // For "a//b", where "a" is current step, we need to mark operation of
2371 // current step as "MATCH_ANY_ANCESTOR". Then we'll consume the first
2372 // slash and subsequent step will be treated as a MATCH_IMMEDIATE_ANCESTOR
2373 // (unless it too is followed by '//'.)
2374 //
2375 // %REVIEW% Following is what happens today, but I'm not sure that's
2376 // %REVIEW% correct behaviour. Perhaps no valid case could be constructed
2377 // %REVIEW% where it would matter?
2378 //
2379 // If current step is on the attribute axis (e.g., "@x//b"), we won't
2380 // change the current step, and let following step be marked as
2381 // MATCH_ANY_ANCESTOR on next call instead.
2382 if ((matchTypePos > -1) && tokenIs('/') && lookahead('/', 1))
2383 {
2384 m_ops.setOp(matchTypePos, OpCodes.MATCH_ANY_ANCESTOR);
2385
2386 nextToken();
2387
2388 trailingSlashConsumed = true;
2389 }
2390 else
2391 {
2392 trailingSlashConsumed = false;
2393 }
2394
2395 // Tell how long the entire step is.
2396 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
2397 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
2398
2399 return trailingSlashConsumed;
2400 }
2401 }