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