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