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