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