1/* 2 * reserved comment block 3 * DO NOT REMOVE OR ALTER! 4 */ 5/* 6 * Licensed to the Apache Software Foundation (ASF) under one or more 7 * contributor license agreements. See the NOTICE file distributed with 8 * this work for additional information regarding copyright ownership. 9 * The ASF licenses this file to You under the Apache License, Version 2.0 10 * (the "License"); you may not use this file except in compliance with 11 * the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, software 16 * distributed under the License is distributed on an "AS IS" BASIS, 17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 * See the License for the specific language governing permissions and 19 * limitations under the License. 20 */ 21 22package com.sun.org.apache.xpath.internal.compiler; 23 24import javax.xml.transform.ErrorListener; 25import javax.xml.transform.TransformerException; 26 27import com.sun.org.apache.xalan.internal.res.XSLMessages; 28import com.sun.org.apache.xml.internal.utils.PrefixResolver; 29import com.sun.org.apache.xpath.internal.XPathProcessorException; 30import com.sun.org.apache.xpath.internal.objects.XNumber; 31import com.sun.org.apache.xpath.internal.objects.XString; 32import com.sun.org.apache.xpath.internal.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 */ 39public 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 (com.sun.org.apache.xpath.internal.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 com.sun.org.apache.xpath.internal.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 com.sun.org.apache.xpath.internal.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 * Dump the remaining token queue. 625 * Thanks to Craig for this. 626 * 627 * @return A dump of the remaining token queue, which may be appended to 628 * an error message. 629 */ 630 protected String dumpRemainingTokenQueue() 631 { 632 633 int q = m_queueMark; 634 String returnMsg; 635 636 if (q < m_ops.getTokenQueueSize()) 637 { 638 String msg = "\n Remaining tokens: ("; 639 640 while (q < m_ops.getTokenQueueSize()) 641 { 642 String t = (String) m_ops.m_tokenQueue.elementAt(q++); 643 644 msg += (" '" + t + "'"); 645 } 646 647 returnMsg = msg + ")"; 648 } 649 else 650 { 651 returnMsg = ""; 652 } 653 654 return returnMsg; 655 } 656 657 /** 658 * Given a string, return the corresponding function token. 659 * 660 * @param key A local name of a function. 661 * 662 * @return The function ID, which may correspond to one of the FUNC_XXX 663 * values found in {@link com.sun.org.apache.xpath.internal.compiler.FunctionTable}, but may 664 * be a value installed by an external module. 665 */ 666 final int getFunctionToken(String key) 667 { 668 669 int tok; 670 671 Object id; 672 673 try 674 { 675 // These are nodetests, xpathparser treats them as functions when parsing 676 // a FilterExpr. 677 id = Keywords.lookupNodeTest(key); 678 if (null == id) id = m_functionTable.getFunctionID(key); 679 tok = ((Integer) id).intValue(); 680 } 681 catch (NullPointerException npe) 682 { 683 tok = -1; 684 } 685 catch (ClassCastException cce) 686 { 687 tok = -1; 688 } 689 690 return tok; 691 } 692 693 /** 694 * Insert room for operation. This will NOT set 695 * the length value of the operation, but will update 696 * the length value for the total expression. 697 * 698 * @param pos The position where the op is to be inserted. 699 * @param length The length of the operation space in the op map. 700 * @param op The op code to the inserted. 701 */ 702 void insertOp(int pos, int length, int op) 703 { 704 705 int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 706 707 for (int i = totalLen - 1; i >= pos; i--) 708 { 709 m_ops.setOp(i + length, m_ops.getOp(i)); 710 } 711 712 m_ops.setOp(pos,op); 713 m_ops.setOp(OpMap.MAPINDEX_LENGTH,totalLen + length); 714 } 715 716 /** 717 * Insert room for operation. This WILL set 718 * the length value of the operation, and will update 719 * the length value for the total expression. 720 * 721 * @param length The length of the operation. 722 * @param op The op code to the inserted. 723 */ 724 void appendOp(int length, int op) 725 { 726 727 int totalLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 728 729 m_ops.setOp(totalLen, op); 730 m_ops.setOp(totalLen + OpMap.MAPINDEX_LENGTH, length); 731 m_ops.setOp(OpMap.MAPINDEX_LENGTH, totalLen + length); 732 } 733 734 // ============= EXPRESSIONS FUNCTIONS ================= 735 736 /** 737 * 738 * 739 * Expr ::= OrExpr 740 * 741 * 742 * @throws javax.xml.transform.TransformerException 743 */ 744 protected void Expr() throws javax.xml.transform.TransformerException 745 { 746 OrExpr(); 747 } 748 749 /** 750 * 751 * 752 * OrExpr ::= AndExpr 753 * | OrExpr 'or' AndExpr 754 * 755 * 756 * @throws javax.xml.transform.TransformerException 757 */ 758 protected void OrExpr() throws javax.xml.transform.TransformerException 759 { 760 761 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 762 763 AndExpr(); 764 765 if ((null != m_token) && tokenIs("or")) 766 { 767 nextToken(); 768 insertOp(opPos, 2, OpCodes.OP_OR); 769 OrExpr(); 770 771 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 772 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 773 } 774 } 775 776 /** 777 * 778 * 779 * AndExpr ::= EqualityExpr 780 * | AndExpr 'and' EqualityExpr 781 * 782 * 783 * @throws javax.xml.transform.TransformerException 784 */ 785 protected void AndExpr() throws javax.xml.transform.TransformerException 786 { 787 788 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 789 790 EqualityExpr(-1); 791 792 if ((null != m_token) && tokenIs("and")) 793 { 794 nextToken(); 795 insertOp(opPos, 2, OpCodes.OP_AND); 796 AndExpr(); 797 798 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 799 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 800 } 801 } 802 803 /** 804 * 805 * @returns an Object which is either a String, a Number, a Boolean, or a vector 806 * of nodes. 807 * 808 * EqualityExpr ::= RelationalExpr 809 * | EqualityExpr '=' RelationalExpr 810 * 811 * 812 * @param addPos Position where expression is to be added, or -1 for append. 813 * 814 * @return the position at the end of the equality expression. 815 * 816 * @throws javax.xml.transform.TransformerException 817 */ 818 protected int EqualityExpr(int addPos) throws javax.xml.transform.TransformerException 819 { 820 821 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 822 823 if (-1 == addPos) 824 addPos = opPos; 825 826 RelationalExpr(-1); 827 828 if (null != m_token) 829 { 830 if (tokenIs('!') && lookahead('=', 1)) 831 { 832 nextToken(); 833 nextToken(); 834 insertOp(addPos, 2, OpCodes.OP_NOTEQUALS); 835 836 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos; 837 838 addPos = EqualityExpr(addPos); 839 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, 840 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen); 841 addPos += 2; 842 } 843 else if (tokenIs('=')) 844 { 845 nextToken(); 846 insertOp(addPos, 2, OpCodes.OP_EQUALS); 847 848 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos; 849 850 addPos = EqualityExpr(addPos); 851 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, 852 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen); 853 addPos += 2; 854 } 855 } 856 857 return addPos; 858 } 859 860 /** 861 * . 862 * @returns an Object which is either a String, a Number, a Boolean, or a vector 863 * of nodes. 864 * 865 * RelationalExpr ::= AdditiveExpr 866 * | RelationalExpr '<' AdditiveExpr 867 * | RelationalExpr '>' AdditiveExpr 868 * | RelationalExpr '<=' AdditiveExpr 869 * | RelationalExpr '>=' AdditiveExpr 870 * 871 * 872 * @param addPos Position where expression is to be added, or -1 for append. 873 * 874 * @return the position at the end of the relational expression. 875 * 876 * @throws javax.xml.transform.TransformerException 877 */ 878 protected int RelationalExpr(int addPos) throws javax.xml.transform.TransformerException 879 { 880 881 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 882 883 if (-1 == addPos) 884 addPos = opPos; 885 886 AdditiveExpr(-1); 887 888 if (null != m_token) 889 { 890 if (tokenIs('<')) 891 { 892 nextToken(); 893 894 if (tokenIs('=')) 895 { 896 nextToken(); 897 insertOp(addPos, 2, OpCodes.OP_LTE); 898 } 899 else 900 { 901 insertOp(addPos, 2, OpCodes.OP_LT); 902 } 903 904 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos; 905 906 addPos = RelationalExpr(addPos); 907 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, 908 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen); 909 addPos += 2; 910 } 911 else if (tokenIs('>')) 912 { 913 nextToken(); 914 915 if (tokenIs('=')) 916 { 917 nextToken(); 918 insertOp(addPos, 2, OpCodes.OP_GTE); 919 } 920 else 921 { 922 insertOp(addPos, 2, OpCodes.OP_GT); 923 } 924 925 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos; 926 927 addPos = RelationalExpr(addPos); 928 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, 929 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen); 930 addPos += 2; 931 } 932 } 933 934 return addPos; 935 } 936 937 /** 938 * This has to handle construction of the operations so that they are evaluated 939 * in pre-fix order. So, for 9+7-6, instead of |+|9|-|7|6|, this needs to be 940 * evaluated as |-|+|9|7|6|. 941 * 942 * AdditiveExpr ::= MultiplicativeExpr 943 * | AdditiveExpr '+' MultiplicativeExpr 944 * | AdditiveExpr '-' MultiplicativeExpr 945 * 946 * 947 * @param addPos Position where expression is to be added, or -1 for append. 948 * 949 * @return the position at the end of the equality expression. 950 * 951 * @throws javax.xml.transform.TransformerException 952 */ 953 protected int AdditiveExpr(int addPos) throws javax.xml.transform.TransformerException 954 { 955 956 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 957 958 if (-1 == addPos) 959 addPos = opPos; 960 961 MultiplicativeExpr(-1); 962 963 if (null != m_token) 964 { 965 if (tokenIs('+')) 966 { 967 nextToken(); 968 insertOp(addPos, 2, OpCodes.OP_PLUS); 969 970 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos; 971 972 addPos = AdditiveExpr(addPos); 973 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, 974 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen); 975 addPos += 2; 976 } 977 else if (tokenIs('-')) 978 { 979 nextToken(); 980 insertOp(addPos, 2, OpCodes.OP_MINUS); 981 982 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos; 983 984 addPos = AdditiveExpr(addPos); 985 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, 986 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen); 987 addPos += 2; 988 } 989 } 990 991 return addPos; 992 } 993 994 /** 995 * This has to handle construction of the operations so that they are evaluated 996 * in pre-fix order. So, for 9+7-6, instead of |+|9|-|7|6|, this needs to be 997 * evaluated as |-|+|9|7|6|. 998 * 999 * MultiplicativeExpr ::= UnaryExpr 1000 * | MultiplicativeExpr MultiplyOperator UnaryExpr 1001 * | MultiplicativeExpr 'div' UnaryExpr 1002 * | MultiplicativeExpr 'mod' UnaryExpr 1003 * | MultiplicativeExpr 'quo' UnaryExpr 1004 * 1005 * @param addPos Position where expression is to be added, or -1 for append. 1006 * 1007 * @return the position at the end of the equality expression. 1008 * 1009 * @throws javax.xml.transform.TransformerException 1010 */ 1011 protected int MultiplicativeExpr(int addPos) throws javax.xml.transform.TransformerException 1012 { 1013 1014 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 1015 1016 if (-1 == addPos) 1017 addPos = opPos; 1018 1019 UnaryExpr(); 1020 1021 if (null != m_token) 1022 { 1023 if (tokenIs('*')) 1024 { 1025 nextToken(); 1026 insertOp(addPos, 2, OpCodes.OP_MULT); 1027 1028 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos; 1029 1030 addPos = MultiplicativeExpr(addPos); 1031 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, 1032 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen); 1033 addPos += 2; 1034 } 1035 else if (tokenIs("div")) 1036 { 1037 nextToken(); 1038 insertOp(addPos, 2, OpCodes.OP_DIV); 1039 1040 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos; 1041 1042 addPos = MultiplicativeExpr(addPos); 1043 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, 1044 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen); 1045 addPos += 2; 1046 } 1047 else if (tokenIs("mod")) 1048 { 1049 nextToken(); 1050 insertOp(addPos, 2, OpCodes.OP_MOD); 1051 1052 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos; 1053 1054 addPos = MultiplicativeExpr(addPos); 1055 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, 1056 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen); 1057 addPos += 2; 1058 } 1059 else if (tokenIs("quo")) 1060 { 1061 nextToken(); 1062 insertOp(addPos, 2, OpCodes.OP_QUO); 1063 1064 int opPlusLeftHandLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - addPos; 1065 1066 addPos = MultiplicativeExpr(addPos); 1067 m_ops.setOp(addPos + OpMap.MAPINDEX_LENGTH, 1068 m_ops.getOp(addPos + opPlusLeftHandLen + 1) + opPlusLeftHandLen); 1069 addPos += 2; 1070 } 1071 } 1072 1073 return addPos; 1074 } 1075 1076 /** 1077 * 1078 * UnaryExpr ::= UnionExpr 1079 * | '-' UnaryExpr 1080 * 1081 * 1082 * @throws javax.xml.transform.TransformerException 1083 */ 1084 protected void UnaryExpr() throws javax.xml.transform.TransformerException 1085 { 1086 1087 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 1088 boolean isNeg = false; 1089 1090 if (m_tokenChar == '-') 1091 { 1092 nextToken(); 1093 appendOp(2, OpCodes.OP_NEG); 1094 1095 isNeg = true; 1096 } 1097 1098 UnionExpr(); 1099 1100 if (isNeg) 1101 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 1102 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 1103 } 1104 1105 /** 1106 * 1107 * StringExpr ::= Expr 1108 * 1109 * 1110 * @throws javax.xml.transform.TransformerException 1111 */ 1112 protected void StringExpr() throws javax.xml.transform.TransformerException 1113 { 1114 1115 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 1116 1117 appendOp(2, OpCodes.OP_STRING); 1118 Expr(); 1119 1120 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 1121 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 1122 } 1123 1124 /** 1125 * 1126 * 1127 * StringExpr ::= Expr 1128 * 1129 * 1130 * @throws javax.xml.transform.TransformerException 1131 */ 1132 protected void BooleanExpr() throws javax.xml.transform.TransformerException 1133 { 1134 1135 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 1136 1137 appendOp(2, OpCodes.OP_BOOL); 1138 Expr(); 1139 1140 int opLen = m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos; 1141 1142 if (opLen == 2) 1143 { 1144 error(XPATHErrorResources.ER_BOOLEAN_ARG_NO_LONGER_OPTIONAL, null); //"boolean(...) argument is no longer optional with 19990709 XPath draft."); 1145 } 1146 1147 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, opLen); 1148 } 1149 1150 /** 1151 * 1152 * 1153 * NumberExpr ::= Expr 1154 * 1155 * 1156 * @throws javax.xml.transform.TransformerException 1157 */ 1158 protected void NumberExpr() throws javax.xml.transform.TransformerException 1159 { 1160 1161 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 1162 1163 appendOp(2, OpCodes.OP_NUMBER); 1164 Expr(); 1165 1166 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 1167 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 1168 } 1169 1170 /** 1171 * The context of the right hand side expressions is the context of the 1172 * left hand side expression. The results of the right hand side expressions 1173 * are node sets. The result of the left hand side UnionExpr is the union 1174 * of the results of the right hand side expressions. 1175 * 1176 * 1177 * UnionExpr ::= PathExpr 1178 * | UnionExpr '|' PathExpr 1179 * 1180 * 1181 * @throws javax.xml.transform.TransformerException 1182 */ 1183 protected void UnionExpr() throws javax.xml.transform.TransformerException 1184 { 1185 1186 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 1187 boolean continueOrLoop = true; 1188 boolean foundUnion = false; 1189 1190 do 1191 { 1192 PathExpr(); 1193 1194 if (tokenIs('|')) 1195 { 1196 if (false == foundUnion) 1197 { 1198 foundUnion = true; 1199 1200 insertOp(opPos, 2, OpCodes.OP_UNION); 1201 } 1202 1203 nextToken(); 1204 } 1205 else 1206 { 1207 break; 1208 } 1209 1210 // this.m_testForDocOrder = true; 1211 } 1212 while (continueOrLoop); 1213 1214 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 1215 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 1216 } 1217 1218 /** 1219 * PathExpr ::= LocationPath 1220 * | FilterExpr 1221 * | FilterExpr '/' RelativeLocationPath 1222 * | FilterExpr '//' RelativeLocationPath 1223 * 1224 * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide 1225 * the error condition is severe enough to halt processing. 1226 * 1227 * @throws javax.xml.transform.TransformerException 1228 */ 1229 protected void PathExpr() throws javax.xml.transform.TransformerException 1230 { 1231 1232 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 1233 1234 int filterExprMatch = FilterExpr(); 1235 1236 if (filterExprMatch != FILTER_MATCH_FAILED) 1237 { 1238 // If FilterExpr had Predicates, a OP_LOCATIONPATH opcode would already 1239 // have been inserted. 1240 boolean locationPathStarted = (filterExprMatch==FILTER_MATCH_PREDICATES); 1241 1242 if (tokenIs('/')) 1243 { 1244 nextToken(); 1245 1246 if (!locationPathStarted) 1247 { 1248 // int locationPathOpPos = opPos; 1249 insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH); 1250 1251 locationPathStarted = true; 1252 } 1253 1254 if (!RelativeLocationPath()) 1255 { 1256 // "Relative location path expected following '/' or '//'" 1257 error(XPATHErrorResources.ER_EXPECTED_REL_LOC_PATH, null); 1258 } 1259 1260 } 1261 1262 // Terminate for safety. 1263 if (locationPathStarted) 1264 { 1265 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP); 1266 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1); 1267 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 1268 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 1269 } 1270 } 1271 else 1272 { 1273 LocationPath(); 1274 } 1275 } 1276 1277 /** 1278 * 1279 * 1280 * FilterExpr ::= PrimaryExpr 1281 * | FilterExpr Predicate 1282 * 1283 * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide 1284 * the error condition is severe enough to halt processing. 1285 * 1286 * @return FILTER_MATCH_PREDICATES, if this method successfully matched a 1287 * FilterExpr with one or more Predicates; 1288 * FILTER_MATCH_PRIMARY, if this method successfully matched a 1289 * FilterExpr that was just a PrimaryExpr; or 1290 * FILTER_MATCH_FAILED, if this method did not match a FilterExpr 1291 * 1292 * @throws javax.xml.transform.TransformerException 1293 */ 1294 protected int FilterExpr() throws javax.xml.transform.TransformerException 1295 { 1296 1297 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 1298 1299 int filterMatch; 1300 1301 if (PrimaryExpr()) 1302 { 1303 if (tokenIs('[')) 1304 { 1305 1306 // int locationPathOpPos = opPos; 1307 insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH); 1308 1309 while (tokenIs('[')) 1310 { 1311 Predicate(); 1312 } 1313 1314 filterMatch = FILTER_MATCH_PREDICATES; 1315 } 1316 else 1317 { 1318 filterMatch = FILTER_MATCH_PRIMARY; 1319 } 1320 } 1321 else 1322 { 1323 filterMatch = FILTER_MATCH_FAILED; 1324 } 1325 1326 return filterMatch; 1327 1328 /* 1329 * if(tokenIs('[')) 1330 * { 1331 * Predicate(); 1332 * m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos; 1333 * } 1334 */ 1335 } 1336 1337 /** 1338 * 1339 * PrimaryExpr ::= VariableReference 1340 * | '(' Expr ')' 1341 * | Literal 1342 * | Number 1343 * | FunctionCall 1344 * 1345 * @return true if this method successfully matched a PrimaryExpr 1346 * 1347 * @throws javax.xml.transform.TransformerException 1348 * 1349 */ 1350 protected boolean PrimaryExpr() throws javax.xml.transform.TransformerException 1351 { 1352 1353 boolean matchFound; 1354 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 1355 1356 if ((m_tokenChar == '\'') || (m_tokenChar == '"')) 1357 { 1358 appendOp(2, OpCodes.OP_LITERAL); 1359 Literal(); 1360 1361 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 1362 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 1363 1364 matchFound = true; 1365 } 1366 else if (m_tokenChar == '$') 1367 { 1368 nextToken(); // consume '$' 1369 appendOp(2, OpCodes.OP_VARIABLE); 1370 QName(); 1371 1372 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 1373 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 1374 1375 matchFound = true; 1376 } 1377 else if (m_tokenChar == '(') 1378 { 1379 nextToken(); 1380 appendOp(2, OpCodes.OP_GROUP); 1381 Expr(); 1382 consumeExpected(')'); 1383 1384 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 1385 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 1386 1387 matchFound = true; 1388 } 1389 else if ((null != m_token) && ((('.' == m_tokenChar) && (m_token.length() > 1) && Character.isDigit( 1390 m_token.charAt(1))) || Character.isDigit(m_tokenChar))) 1391 { 1392 appendOp(2, OpCodes.OP_NUMBERLIT); 1393 Number(); 1394 1395 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 1396 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 1397 1398 matchFound = true; 1399 } 1400 else if (lookahead('(', 1) || (lookahead(':', 1) && lookahead('(', 3))) 1401 { 1402 matchFound = FunctionCall(); 1403 } 1404 else 1405 { 1406 matchFound = false; 1407 } 1408 1409 return matchFound; 1410 } 1411 1412 /** 1413 * 1414 * Argument ::= Expr 1415 * 1416 * 1417 * @throws javax.xml.transform.TransformerException 1418 */ 1419 protected void Argument() throws javax.xml.transform.TransformerException 1420 { 1421 1422 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 1423 1424 appendOp(2, OpCodes.OP_ARGUMENT); 1425 Expr(); 1426 1427 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 1428 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 1429 } 1430 1431 /** 1432 * 1433 * FunctionCall ::= FunctionName '(' ( Argument ( ',' Argument)*)? ')' 1434 * 1435 * @return true if, and only if, a FunctionCall was matched 1436 * 1437 * @throws javax.xml.transform.TransformerException 1438 */ 1439 protected boolean FunctionCall() throws javax.xml.transform.TransformerException 1440 { 1441 1442 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 1443 1444 if (lookahead(':', 1)) 1445 { 1446 appendOp(4, OpCodes.OP_EXTFUNCTION); 1447 1448 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, m_queueMark - 1); 1449 1450 nextToken(); 1451 consumeExpected(':'); 1452 1453 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 2, m_queueMark - 1); 1454 1455 nextToken(); 1456 } 1457 else 1458 { 1459 int funcTok = getFunctionToken(m_token); 1460 1461 if (-1 == funcTok) 1462 { 1463 error(XPATHErrorResources.ER_COULDNOT_FIND_FUNCTION, 1464 new Object[]{ m_token }); //"Could not find function: "+m_token+"()"); 1465 } 1466 1467 switch (funcTok) 1468 { 1469 case OpCodes.NODETYPE_PI : 1470 case OpCodes.NODETYPE_COMMENT : 1471 case OpCodes.NODETYPE_TEXT : 1472 case OpCodes.NODETYPE_NODE : 1473 // Node type tests look like function calls, but they're not 1474 return false; 1475 default : 1476 appendOp(3, OpCodes.OP_FUNCTION); 1477 1478 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, funcTok); 1479 } 1480 1481 nextToken(); 1482 } 1483 1484 consumeExpected('('); 1485 1486 while (!tokenIs(')') && m_token != null) 1487 { 1488 if (tokenIs(',')) 1489 { 1490 error(XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_PRECEDING_ARG, null); //"Found ',' but no preceding argument!"); 1491 } 1492 1493 Argument(); 1494 1495 if (!tokenIs(')')) 1496 { 1497 consumeExpected(','); 1498 1499 if (tokenIs(')')) 1500 { 1501 error(XPATHErrorResources.ER_FOUND_COMMA_BUT_NO_FOLLOWING_ARG, 1502 null); //"Found ',' but no following argument!"); 1503 } 1504 } 1505 } 1506 1507 consumeExpected(')'); 1508 1509 // Terminate for safety. 1510 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP); 1511 m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1); 1512 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 1513 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 1514 1515 return true; 1516 } 1517 1518 // ============= GRAMMAR FUNCTIONS ================= 1519 1520 /** 1521 * 1522 * LocationPath ::= RelativeLocationPath 1523 * | AbsoluteLocationPath 1524 * 1525 * 1526 * @throws javax.xml.transform.TransformerException 1527 */ 1528 protected void LocationPath() throws javax.xml.transform.TransformerException 1529 { 1530 1531 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 1532 1533 // int locationPathOpPos = opPos; 1534 appendOp(2, OpCodes.OP_LOCATIONPATH); 1535 1536 boolean seenSlash = tokenIs('/'); 1537 1538 if (seenSlash) 1539 { 1540 appendOp(4, OpCodes.FROM_ROOT); 1541 1542 // Tell how long the step is without the predicate 1543 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4); 1544 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_ROOT); 1545 1546 nextToken(); 1547 } else if (m_token == null) { 1548 error(XPATHErrorResources.ER_EXPECTED_LOC_PATH_AT_END_EXPR, null); 1549 } 1550 1551 if (m_token != null) 1552 { 1553 if (!RelativeLocationPath() && !seenSlash) 1554 { 1555 // Neither a '/' nor a RelativeLocationPath - i.e., matched nothing 1556 // "Location path expected, but found "+m_token+" was encountered." 1557 error(XPATHErrorResources.ER_EXPECTED_LOC_PATH, 1558 new Object [] {m_token}); 1559 } 1560 } 1561 1562 // Terminate for safety. 1563 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP); 1564 m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1); 1565 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 1566 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 1567 } 1568 1569 /** 1570 * 1571 * RelativeLocationPath ::= Step 1572 * | RelativeLocationPath '/' Step 1573 * | AbbreviatedRelativeLocationPath 1574 * 1575 * @returns true if, and only if, a RelativeLocationPath was matched 1576 * 1577 * @throws javax.xml.transform.TransformerException 1578 */ 1579 protected boolean RelativeLocationPath() 1580 throws javax.xml.transform.TransformerException 1581 { 1582 if (!Step()) 1583 { 1584 return false; 1585 } 1586 1587 while (tokenIs('/')) 1588 { 1589 nextToken(); 1590 1591 if (!Step()) 1592 { 1593 // RelativeLocationPath can't end with a trailing '/' 1594 // "Location step expected following '/' or '//'" 1595 error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null); 1596 } 1597 } 1598 1599 return true; 1600 } 1601 1602 /** 1603 * 1604 * Step ::= Basis Predicate 1605 * | AbbreviatedStep 1606 * 1607 * @returns false if step was empty (or only a '/'); true, otherwise 1608 * 1609 * @throws javax.xml.transform.TransformerException 1610 */ 1611 protected boolean Step() throws javax.xml.transform.TransformerException 1612 { 1613 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 1614 1615 boolean doubleSlash = tokenIs('/'); 1616 1617 // At most a single '/' before each Step is consumed by caller; if the 1618 // first thing is a '/', that means we had '//' and the Step must not 1619 // be empty. 1620 if (doubleSlash) 1621 { 1622 nextToken(); 1623 1624 appendOp(2, OpCodes.FROM_DESCENDANTS_OR_SELF); 1625 1626 // Have to fix up for patterns such as '//@foo' or '//attribute::foo', 1627 // which translate to 'descendant-or-self::node()/attribute::foo'. 1628 // notice I leave the '/' on the queue, so the next will be processed 1629 // by a regular step pattern. 1630 1631 // Make room for telling how long the step is without the predicate 1632 m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1); 1633 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.NODETYPE_NODE); 1634 m_ops.setOp(OpMap.MAPINDEX_LENGTH,m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1); 1635 1636 // Tell how long the step is without the predicate 1637 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, 1638 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 1639 1640 // Tell how long the step is with the predicate 1641 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 1642 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 1643 1644 opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 1645 } 1646 1647 if (tokenIs(".")) 1648 { 1649 nextToken(); 1650 1651 if (tokenIs('[')) 1652 { 1653 error(XPATHErrorResources.ER_PREDICATE_ILLEGAL_SYNTAX, null); //"'..[predicate]' or '.[predicate]' is illegal syntax. Use 'self::node()[predicate]' instead."); 1654 } 1655 1656 appendOp(4, OpCodes.FROM_SELF); 1657 1658 // Tell how long the step is without the predicate 1659 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2,4); 1660 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_NODE); 1661 } 1662 else if (tokenIs("..")) 1663 { 1664 nextToken(); 1665 appendOp(4, OpCodes.FROM_PARENT); 1666 1667 // Tell how long the step is without the predicate 1668 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2,4); 1669 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_NODE); 1670 } 1671 1672 // There is probably a better way to test for this 1673 // transition... but it gets real hairy if you try 1674 // to do it in basis(). 1675 else if (tokenIs('*') || tokenIs('@') || tokenIs('_') 1676 || (m_token!= null && Character.isLetter(m_token.charAt(0)))) 1677 { 1678 Basis(); 1679 1680 while (tokenIs('[')) 1681 { 1682 Predicate(); 1683 } 1684 1685 // Tell how long the entire step is. 1686 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 1687 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 1688 } 1689 else 1690 { 1691 // No Step matched - that's an error if previous thing was a '//' 1692 if (doubleSlash) 1693 { 1694 // "Location step expected following '/' or '//'" 1695 error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null); 1696 } 1697 1698 return false; 1699 } 1700 1701 return true; 1702 } 1703 1704 /** 1705 * 1706 * Basis ::= AxisName '::' NodeTest 1707 * | AbbreviatedBasis 1708 * 1709 * @throws javax.xml.transform.TransformerException 1710 */ 1711 protected void Basis() throws javax.xml.transform.TransformerException 1712 { 1713 1714 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 1715 int axesType; 1716 1717 // The next blocks guarantee that a FROM_XXX will be added. 1718 if (lookahead("::", 1)) 1719 { 1720 axesType = AxisName(); 1721 1722 nextToken(); 1723 nextToken(); 1724 } 1725 else if (tokenIs('@')) 1726 { 1727 axesType = OpCodes.FROM_ATTRIBUTES; 1728 1729 appendOp(2, axesType); 1730 nextToken(); 1731 } 1732 else 1733 { 1734 axesType = OpCodes.FROM_CHILDREN; 1735 1736 appendOp(2, axesType); 1737 } 1738 1739 // Make room for telling how long the step is without the predicate 1740 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1); 1741 1742 NodeTest(axesType); 1743 1744 // Tell how long the step is without the predicate 1745 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, 1746 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 1747 } 1748 1749 /** 1750 * 1751 * Basis ::= AxisName '::' NodeTest 1752 * | AbbreviatedBasis 1753 * 1754 * @return FROM_XXX axes type, found in {@link com.sun.org.apache.xpath.internal.compiler.Keywords}. 1755 * 1756 * @throws javax.xml.transform.TransformerException 1757 */ 1758 protected int AxisName() throws javax.xml.transform.TransformerException 1759 { 1760 1761 Object val = Keywords.getAxisName(m_token); 1762 1763 if (null == val) 1764 { 1765 error(XPATHErrorResources.ER_ILLEGAL_AXIS_NAME, 1766 new Object[]{ m_token }); //"illegal axis name: "+m_token); 1767 } 1768 1769 int axesType = ((Integer) val).intValue(); 1770 1771 appendOp(2, axesType); 1772 1773 return axesType; 1774 } 1775 1776 /** 1777 * 1778 * NodeTest ::= WildcardName 1779 * | NodeType '(' ')' 1780 * | 'processing-instruction' '(' Literal ')' 1781 * 1782 * @param axesType FROM_XXX axes type, found in {@link com.sun.org.apache.xpath.internal.compiler.Keywords}. 1783 * 1784 * @throws javax.xml.transform.TransformerException 1785 */ 1786 protected void NodeTest(int axesType) throws javax.xml.transform.TransformerException 1787 { 1788 1789 if (lookahead('(', 1)) 1790 { 1791 Object nodeTestOp = Keywords.getNodeType(m_token); 1792 1793 if (null == nodeTestOp) 1794 { 1795 error(XPATHErrorResources.ER_UNKNOWN_NODETYPE, 1796 new Object[]{ m_token }); //"Unknown nodetype: "+m_token); 1797 } 1798 else 1799 { 1800 nextToken(); 1801 1802 int nt = ((Integer) nodeTestOp).intValue(); 1803 1804 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), nt); 1805 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1); 1806 1807 consumeExpected('('); 1808 1809 if (OpCodes.NODETYPE_PI == nt) 1810 { 1811 if (!tokenIs(')')) 1812 { 1813 Literal(); 1814 } 1815 } 1816 1817 consumeExpected(')'); 1818 } 1819 } 1820 else 1821 { 1822 1823 // Assume name of attribute or element. 1824 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.NODENAME); 1825 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1); 1826 1827 if (lookahead(':', 1)) 1828 { 1829 if (tokenIs('*')) 1830 { 1831 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ELEMWILDCARD); 1832 } 1833 else 1834 { 1835 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1); 1836 1837 // Minimalist check for an NCName - just check first character 1838 // to distinguish from other possible tokens 1839 if (!Character.isLetter(m_tokenChar) && !tokenIs('_')) 1840 { 1841 // "Node test that matches either NCName:* or QName was expected." 1842 error(XPATHErrorResources.ER_EXPECTED_NODE_TEST, null); 1843 } 1844 } 1845 1846 nextToken(); 1847 consumeExpected(':'); 1848 } 1849 else 1850 { 1851 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.EMPTY); 1852 } 1853 1854 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1); 1855 1856 if (tokenIs('*')) 1857 { 1858 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ELEMWILDCARD); 1859 } 1860 else 1861 { 1862 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1); 1863 1864 // Minimalist check for an NCName - just check first character 1865 // to distinguish from other possible tokens 1866 if (!Character.isLetter(m_tokenChar) && !tokenIs('_')) 1867 { 1868 // "Node test that matches either NCName:* or QName was expected." 1869 error(XPATHErrorResources.ER_EXPECTED_NODE_TEST, null); 1870 } 1871 } 1872 1873 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1); 1874 1875 nextToken(); 1876 } 1877 } 1878 1879 /** 1880 * 1881 * Predicate ::= '[' PredicateExpr ']' 1882 * 1883 * 1884 * @throws javax.xml.transform.TransformerException 1885 */ 1886 protected void Predicate() throws javax.xml.transform.TransformerException 1887 { 1888 1889 if (tokenIs('[')) 1890 { 1891 nextToken(); 1892 PredicateExpr(); 1893 consumeExpected(']'); 1894 } 1895 } 1896 1897 /** 1898 * 1899 * PredicateExpr ::= Expr 1900 * 1901 * 1902 * @throws javax.xml.transform.TransformerException 1903 */ 1904 protected void PredicateExpr() throws javax.xml.transform.TransformerException 1905 { 1906 1907 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 1908 1909 appendOp(2, OpCodes.OP_PREDICATE); 1910 Expr(); 1911 1912 // Terminate for safety. 1913 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP); 1914 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1); 1915 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 1916 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 1917 } 1918 1919 /** 1920 * QName ::= (Prefix ':')? LocalPart 1921 * Prefix ::= NCName 1922 * LocalPart ::= NCName 1923 * 1924 * @throws javax.xml.transform.TransformerException 1925 */ 1926 protected void QName() throws javax.xml.transform.TransformerException 1927 { 1928 // Namespace 1929 if(lookahead(':', 1)) 1930 { 1931 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1); 1932 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1); 1933 1934 nextToken(); 1935 consumeExpected(':'); 1936 } 1937 else 1938 { 1939 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.EMPTY); 1940 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1); 1941 } 1942 1943 // Local name 1944 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1); 1945 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1); 1946 1947 nextToken(); 1948 } 1949 1950 /** 1951 * NCName ::= (Letter | '_') (NCNameChar) 1952 * NCNameChar ::= Letter | Digit | '.' | '-' | '_' | CombiningChar | Extender 1953 */ 1954 protected void NCName() 1955 { 1956 1957 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1); 1958 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1); 1959 1960 nextToken(); 1961 } 1962 1963 /** 1964 * The value of the Literal is the sequence of characters inside 1965 * the " or ' characters>. 1966 * 1967 * Literal ::= '"' [^"]* '"' 1968 * | "'" [^']* "'" 1969 * 1970 * 1971 * @throws javax.xml.transform.TransformerException 1972 */ 1973 protected void Literal() throws javax.xml.transform.TransformerException 1974 { 1975 1976 int last = m_token.length() - 1; 1977 char c0 = m_tokenChar; 1978 char cX = m_token.charAt(last); 1979 1980 if (((c0 == '\"') && (cX == '\"')) || ((c0 == '\'') && (cX == '\''))) 1981 { 1982 1983 // Mutate the token to remove the quotes and have the XString object 1984 // already made. 1985 int tokenQueuePos = m_queueMark - 1; 1986 1987 m_ops.m_tokenQueue.setElementAt(null,tokenQueuePos); 1988 1989 Object obj = new XString(m_token.substring(1, last)); 1990 1991 m_ops.m_tokenQueue.setElementAt(obj,tokenQueuePos); 1992 1993 // lit = m_token.substring(1, last); 1994 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), tokenQueuePos); 1995 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1); 1996 1997 nextToken(); 1998 } 1999 else 2000 { 2001 error(XPATHErrorResources.ER_PATTERN_LITERAL_NEEDS_BE_QUOTED, 2002 new Object[]{ m_token }); //"Pattern literal ("+m_token+") needs to be quoted!"); 2003 } 2004 } 2005 2006 /** 2007 * 2008 * Number ::= [0-9]+('.'[0-9]+)? | '.'[0-9]+ 2009 * 2010 * 2011 * @throws javax.xml.transform.TransformerException 2012 */ 2013 protected void Number() throws javax.xml.transform.TransformerException 2014 { 2015 2016 if (null != m_token) 2017 { 2018 2019 // Mutate the token to remove the quotes and have the XNumber object 2020 // already made. 2021 double num; 2022 2023 try 2024 { 2025 // XPath 1.0 does not support number in exp notation 2026 if ((m_token.indexOf('e') > -1)||(m_token.indexOf('E') > -1)) 2027 throw new NumberFormatException(); 2028 num = Double.valueOf(m_token).doubleValue(); 2029 } 2030 catch (NumberFormatException nfe) 2031 { 2032 num = 0.0; // to shut up compiler. 2033 2034 error(XPATHErrorResources.ER_COULDNOT_BE_FORMATTED_TO_NUMBER, 2035 new Object[]{ m_token }); //m_token+" could not be formatted to a number!"); 2036 } 2037 2038 m_ops.m_tokenQueue.setElementAt(new XNumber(num),m_queueMark - 1); 2039 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1); 2040 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1); 2041 2042 nextToken(); 2043 } 2044 } 2045 2046 // ============= PATTERN FUNCTIONS ================= 2047 2048 /** 2049 * 2050 * Pattern ::= LocationPathPattern 2051 * | Pattern '|' LocationPathPattern 2052 * 2053 * 2054 * @throws javax.xml.transform.TransformerException 2055 */ 2056 protected void Pattern() throws javax.xml.transform.TransformerException 2057 { 2058 2059 while (true) 2060 { 2061 LocationPathPattern(); 2062 2063 if (tokenIs('|')) 2064 { 2065 nextToken(); 2066 } 2067 else 2068 { 2069 break; 2070 } 2071 } 2072 } 2073 2074 /** 2075 * 2076 * 2077 * LocationPathPattern ::= '/' RelativePathPattern? 2078 * | IdKeyPattern (('/' | '//') RelativePathPattern)? 2079 * | '//'? RelativePathPattern 2080 * 2081 * 2082 * @throws javax.xml.transform.TransformerException 2083 */ 2084 protected void LocationPathPattern() throws javax.xml.transform.TransformerException 2085 { 2086 2087 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 2088 2089 final int RELATIVE_PATH_NOT_PERMITTED = 0; 2090 final int RELATIVE_PATH_PERMITTED = 1; 2091 final int RELATIVE_PATH_REQUIRED = 2; 2092 2093 int relativePathStatus = RELATIVE_PATH_NOT_PERMITTED; 2094 2095 appendOp(2, OpCodes.OP_LOCATIONPATHPATTERN); 2096 2097 if (lookahead('(', 1) 2098 && (tokenIs(Keywords.FUNC_ID_STRING) 2099 || tokenIs(Keywords.FUNC_KEY_STRING))) 2100 { 2101 IdKeyPattern(); 2102 2103 if (tokenIs('/')) 2104 { 2105 nextToken(); 2106 2107 if (tokenIs('/')) 2108 { 2109 appendOp(4, OpCodes.MATCH_ANY_ANCESTOR); 2110 2111 nextToken(); 2112 } 2113 else 2114 { 2115 appendOp(4, OpCodes.MATCH_IMMEDIATE_ANCESTOR); 2116 } 2117 2118 // Tell how long the step is without the predicate 2119 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4); 2120 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_FUNCTEST); 2121 2122 relativePathStatus = RELATIVE_PATH_REQUIRED; 2123 } 2124 } 2125 else if (tokenIs('/')) 2126 { 2127 if (lookahead('/', 1)) 2128 { 2129 appendOp(4, OpCodes.MATCH_ANY_ANCESTOR); 2130 2131 // Added this to fix bug reported by Myriam for match="//x/a" 2132 // patterns. If you don't do this, the 'x' step will think it's part 2133 // of a '//' pattern, and so will cause 'a' to be matched when it has 2134 // any ancestor that is 'x'. 2135 nextToken(); 2136 2137 relativePathStatus = RELATIVE_PATH_REQUIRED; 2138 } 2139 else 2140 { 2141 appendOp(4, OpCodes.FROM_ROOT); 2142 2143 relativePathStatus = RELATIVE_PATH_PERMITTED; 2144 } 2145 2146 2147 // Tell how long the step is without the predicate 2148 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 2, 4); 2149 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH) - 1, OpCodes.NODETYPE_ROOT); 2150 2151 nextToken(); 2152 } 2153 else 2154 { 2155 relativePathStatus = RELATIVE_PATH_REQUIRED; 2156 } 2157 2158 if (relativePathStatus != RELATIVE_PATH_NOT_PERMITTED) 2159 { 2160 if (!tokenIs('|') && (null != m_token)) 2161 { 2162 RelativePathPattern(); 2163 } 2164 else if (relativePathStatus == RELATIVE_PATH_REQUIRED) 2165 { 2166 // "A relative path pattern was expected." 2167 error(XPATHErrorResources.ER_EXPECTED_REL_PATH_PATTERN, null); 2168 } 2169 } 2170 2171 // Terminate for safety. 2172 m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), OpCodes.ENDOP); 2173 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1); 2174 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 2175 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 2176 } 2177 2178 /** 2179 * 2180 * IdKeyPattern ::= 'id' '(' Literal ')' 2181 * | 'key' '(' Literal ',' Literal ')' 2182 * (Also handle doc()) 2183 * 2184 * 2185 * @throws javax.xml.transform.TransformerException 2186 */ 2187 protected void IdKeyPattern() throws javax.xml.transform.TransformerException 2188 { 2189 FunctionCall(); 2190 } 2191 2192 /** 2193 * 2194 * RelativePathPattern ::= StepPattern 2195 * | RelativePathPattern '/' StepPattern 2196 * | RelativePathPattern '//' StepPattern 2197 * 2198 * @throws javax.xml.transform.TransformerException 2199 */ 2200 protected void RelativePathPattern() 2201 throws javax.xml.transform.TransformerException 2202 { 2203 2204 // Caller will have consumed any '/' or '//' preceding the 2205 // RelativePathPattern, so let StepPattern know it can't begin with a '/' 2206 boolean trailingSlashConsumed = StepPattern(false); 2207 2208 while (tokenIs('/')) 2209 { 2210 nextToken(); 2211 2212 // StepPattern() may consume first slash of pair in "a//b" while 2213 // processing StepPattern "a". On next iteration, let StepPattern know 2214 // that happened, so it doesn't match ill-formed patterns like "a///b". 2215 trailingSlashConsumed = StepPattern(!trailingSlashConsumed); 2216 } 2217 } 2218 2219 /** 2220 * 2221 * StepPattern ::= AbbreviatedNodeTestStep 2222 * 2223 * @param isLeadingSlashPermitted a boolean indicating whether a slash can 2224 * appear at the start of this step 2225 * 2226 * @return boolean indicating whether a slash following the step was consumed 2227 * 2228 * @throws javax.xml.transform.TransformerException 2229 */ 2230 protected boolean StepPattern(boolean isLeadingSlashPermitted) 2231 throws javax.xml.transform.TransformerException 2232 { 2233 return AbbreviatedNodeTestStep(isLeadingSlashPermitted); 2234 } 2235 2236 /** 2237 * 2238 * AbbreviatedNodeTestStep ::= '@'? NodeTest Predicate 2239 * 2240 * @param isLeadingSlashPermitted a boolean indicating whether a slash can 2241 * appear at the start of this step 2242 * 2243 * @return boolean indicating whether a slash following the step was consumed 2244 * 2245 * @throws javax.xml.transform.TransformerException 2246 */ 2247 protected boolean AbbreviatedNodeTestStep(boolean isLeadingSlashPermitted) 2248 throws javax.xml.transform.TransformerException 2249 { 2250 2251 int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 2252 int axesType; 2253 2254 // The next blocks guarantee that a MATCH_XXX will be added. 2255 int matchTypePos = -1; 2256 2257 if (tokenIs('@')) 2258 { 2259 axesType = OpCodes.MATCH_ATTRIBUTE; 2260 2261 appendOp(2, axesType); 2262 nextToken(); 2263 } 2264 else if (this.lookahead("::", 1)) 2265 { 2266 if (tokenIs("attribute")) 2267 { 2268 axesType = OpCodes.MATCH_ATTRIBUTE; 2269 2270 appendOp(2, axesType); 2271 } 2272 else if (tokenIs("child")) 2273 { 2274 matchTypePos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 2275 axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR; 2276 2277 appendOp(2, axesType); 2278 } 2279 else 2280 { 2281 axesType = -1; 2282 2283 this.error(XPATHErrorResources.ER_AXES_NOT_ALLOWED, 2284 new Object[]{ this.m_token }); 2285 } 2286 2287 nextToken(); 2288 nextToken(); 2289 } 2290 else if (tokenIs('/')) 2291 { 2292 if (!isLeadingSlashPermitted) 2293 { 2294 // "A step was expected in the pattern, but '/' was encountered." 2295 error(XPATHErrorResources.ER_EXPECTED_STEP_PATTERN, null); 2296 } 2297 axesType = OpCodes.MATCH_ANY_ANCESTOR; 2298 2299 appendOp(2, axesType); 2300 nextToken(); 2301 } 2302 else 2303 { 2304 matchTypePos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); 2305 axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR; 2306 2307 appendOp(2, axesType); 2308 } 2309 2310 // Make room for telling how long the step is without the predicate 2311 m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1); 2312 2313 NodeTest(axesType); 2314 2315 // Tell how long the step is without the predicate 2316 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, 2317 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 2318 2319 while (tokenIs('[')) 2320 { 2321 Predicate(); 2322 } 2323 2324 boolean trailingSlashConsumed; 2325 2326 // For "a//b", where "a" is current step, we need to mark operation of 2327 // current step as "MATCH_ANY_ANCESTOR". Then we'll consume the first 2328 // slash and subsequent step will be treated as a MATCH_IMMEDIATE_ANCESTOR 2329 // (unless it too is followed by '//'.) 2330 // 2331 // %REVIEW% Following is what happens today, but I'm not sure that's 2332 // %REVIEW% correct behaviour. Perhaps no valid case could be constructed 2333 // %REVIEW% where it would matter? 2334 // 2335 // If current step is on the attribute axis (e.g., "@x//b"), we won't 2336 // change the current step, and let following step be marked as 2337 // MATCH_ANY_ANCESTOR on next call instead. 2338 if ((matchTypePos > -1) && tokenIs('/') && lookahead('/', 1)) 2339 { 2340 m_ops.setOp(matchTypePos, OpCodes.MATCH_ANY_ANCESTOR); 2341 2342 nextToken(); 2343 2344 trailingSlashConsumed = true; 2345 } 2346 else 2347 { 2348 trailingSlashConsumed = false; 2349 } 2350 2351 // Tell how long the entire step is. 2352 m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 2353 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos); 2354 2355 return trailingSlashConsumed; 2356 } 2357} 2358