1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 /* 19 * $Id: XPathParser.java 468655 2006-10-28 07:12:06Z minchau $ 20 */ 21 package org.apache.xpath.compiler; 22 23 import javax.xml.transform.ErrorListener; 24 import javax.xml.transform.TransformerException; 25 26 import org.apache.xalan.res.XSLMessages; 27 import org.apache.xml.utils.PrefixResolver; 28 import org.apache.xpath.XPathProcessorException; 29 import org.apache.xpath.domapi.XPathStylesheetDOM3Exception; 30 import org.apache.xpath.objects.XNumber; 31 import org.apache.xpath.objects.XString; 32 import org.apache.xpath.res.XPATHErrorResources; 33 34 /** 35 * Tokenizes and parses XPath expressions. This should really be named 36 * XPathParserImpl, and may be renamed in the future. 37 * @xsl.usage general 38 */ 39 public class XPathParser 40 { 41 // %REVIEW% Is there a better way of doing this? 42 // Upside is minimum object churn. Downside is that we don't have a useful 43 // backtrace in the exception itself -- but we don't expect to need one. 44 static public final String CONTINUE_AFTER_FATAL_ERROR="CONTINUE_AFTER_FATAL_ERROR"; 45 46 /** 47 * The XPath to be processed. 48 */ 49 private OpMap m_ops; 50 51 /** 52 * The next token in the pattern. 53 */ 54 transient String m_token; 55 56 /** 57 * The first char in m_token, the theory being that this 58 * is an optimization because we won't have to do charAt(0) as 59 * often. 60 */ 61 transient char m_tokenChar = 0; 62 63 /** 64 * The position in the token queue is tracked by m_queueMark. 65 */ 66 int m_queueMark = 0; 67 68 /** 69 * Results from checking FilterExpr syntax 70 */ 71 protected final static int FILTER_MATCH_FAILED = 0; 72 protected final static int FILTER_MATCH_PRIMARY = 1; 73 protected final static int FILTER_MATCH_PREDICATES = 2; 74 75 /** 76 * The parser constructor. 77 */ 78 public XPathParser(ErrorListener errorListener, javax.xml.transform.SourceLocator sourceLocator) 79 { 80 m_errorListener = errorListener; 81 m_sourceLocator = sourceLocator; 82 } 83 84 /** 85 * The prefix resolver to map prefixes to namespaces in the OpMap. 86 */ 87 PrefixResolver m_namespaceContext; 88 89 /** 90 * Given an string, init an XPath object for selections, 91 * in order that a parse doesn't 92 * have to be done each time the expression is evaluated. 93 * 94 * @param compiler The compiler object. 95 * @param expression A string conforming to the XPath grammar. 96 * @param namespaceContext An object that is able to resolve prefixes in 97 * the XPath to namespaces. 98 * 99 * @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 } 2402