PageRenderTime 54ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/saxonB/net/sf/saxon/query/QueryParser.java

https://bitbucket.org/dmwelch/phdxnat_pipeline
Java | 3715 lines | 2775 code | 279 blank | 661 comment | 744 complexity | f0fe8568f3211e519f536f98bb54b80a MD5 | raw file
  1. package net.sf.saxon.query;
  2. import net.sf.saxon.Configuration;
  3. import net.sf.saxon.trans.Err;
  4. import net.sf.saxon.Platform;
  5. import net.sf.saxon.charcode.UTF16;
  6. import net.sf.saxon.event.PipelineConfiguration;
  7. import net.sf.saxon.expr.*;
  8. import net.sf.saxon.functions.*;
  9. import net.sf.saxon.instruct.*;
  10. import net.sf.saxon.om.*;
  11. import net.sf.saxon.pattern.NodeTest;
  12. import net.sf.saxon.sort.*;
  13. import net.sf.saxon.style.AttributeValueTemplate;
  14. import net.sf.saxon.sxpath.IndependentContext;
  15. import net.sf.saxon.trace.Location;
  16. import net.sf.saxon.trans.XPathException;
  17. import net.sf.saxon.type.*;
  18. import net.sf.saxon.value.*;
  19. import javax.xml.transform.OutputKeys;
  20. import javax.xml.transform.TransformerConfigurationException;
  21. import javax.xml.transform.stream.StreamSource;
  22. import java.net.URI;
  23. import java.net.URISyntaxException;
  24. import java.util.*;
  25. import java.util.regex.Pattern;
  26. import java.io.IOException;
  27. /**
  28. * This class defines extensions to the XPath parser to handle the additional
  29. * syntax supported in XQuery
  30. */
  31. public class QueryParser extends ExpressionParser {
  32. private boolean memoFunction = false;
  33. private boolean disableCycleChecks = false;
  34. private int errorCount = 0;
  35. private XPathException firstError = null;
  36. protected Executable executable;
  37. private boolean foundCopyNamespaces = false;
  38. private boolean foundBoundarySpaceDeclaration = false;
  39. private boolean foundOrderingDeclaration = false;
  40. private boolean foundEmptyOrderingDeclaration = false;
  41. private boolean foundDefaultCollation = false;
  42. private boolean foundConstructionDeclaration = false;
  43. private boolean foundDefaultFunctionNamespace = false;
  44. private boolean foundDefaultElementNamespace = false;
  45. private boolean foundBaseURIDeclaration = false;
  46. private boolean preambleProcessed = false;
  47. public Set importedModules = new HashSet(5);
  48. List namespacesToBeSealed = new ArrayList(10);
  49. List schemaImports = new ArrayList(5);
  50. List moduleImports = new ArrayList(5);
  51. private Expression defaultValue = null;
  52. /**
  53. * Constructor for internal use: this class should be instantiated via the QueryModule
  54. */
  55. public QueryParser() {
  56. }
  57. /**
  58. * Create an XQueryExpression
  59. * @param query the source text of the query
  60. * @param staticContext the static context of the query
  61. * @param config the Saxon configuration
  62. * @return the compiled XQuery expression
  63. */
  64. public XQueryExpression makeXQueryExpression(String query,
  65. QueryModule staticContext,
  66. Configuration config) throws XPathException {
  67. try {
  68. if (config.getXMLVersion() == Configuration.XML10) {
  69. query = normalizeLineEndings10(query);
  70. } else {
  71. query = normalizeLineEndings11(query);
  72. }
  73. Executable exec = staticContext.getExecutable();
  74. if (exec == null) {
  75. exec = new Executable(config);
  76. exec.setHostLanguage(Configuration.XQUERY);
  77. staticContext.setExecutable(exec);
  78. }
  79. Properties outputProps = new Properties();
  80. outputProps.setProperty(OutputKeys.METHOD, "xml");
  81. outputProps.setProperty(OutputKeys.INDENT, "yes");
  82. exec.setDefaultOutputProperties(outputProps);
  83. exec.setLocationMap(new LocationMap());
  84. exec.setFunctionLibrary(new ExecutableFunctionLibrary(config));
  85. // this will be changed later
  86. setExecutable(exec);
  87. Expression exp = parseQuery(query, 0, Token.EOF, staticContext);
  88. int loc = env.getLocationMap().allocateLocationId(env.getSystemId(), 1);
  89. exp.setContainer(new TemporaryContainer(staticContext.getLocationMap(), loc));
  90. //staticContext.bindUnboundFunctionCalls();
  91. exec.fixupQueryModules(staticContext);
  92. // Check for cyclic dependencies among the modules
  93. if (!disableCycleChecks) {
  94. Iterator miter = exec.getQueryLibraryModules();
  95. while (miter.hasNext()) {
  96. QueryModule module = (QueryModule)miter.next();
  97. module.lookForModuleCycles(new Stack(), 1);
  98. }
  99. }
  100. // Make the XQueryexpression object
  101. XQueryExpression queryExp = new XQueryExpression(exp, exec, staticContext, config);
  102. //exp = queryExp.getExpression();
  103. // Make the function library that's available at run-time (e.g. for saxon:evaluate()). This includes
  104. // all user-defined functions regardless of which module they are in
  105. FunctionLibrary userlib = exec.getFunctionLibrary();
  106. FunctionLibraryList lib = new FunctionLibraryList();
  107. lib.addFunctionLibrary(
  108. SystemFunctionLibrary.getSystemFunctionLibrary(SystemFunctionLibrary.XPATH_ONLY));
  109. lib.addFunctionLibrary(config.getVendorFunctionLibrary());
  110. lib.addFunctionLibrary(new ConstructorFunctionLibrary(config));
  111. if (config.isAllowExternalFunctions()) {
  112. Configuration.getPlatform().addFunctionLibraries(lib, config, Configuration.XQUERY);
  113. }
  114. lib.addFunctionLibrary(userlib);
  115. exec.setFunctionLibrary(lib);
  116. return queryExp;
  117. } catch (XPathException e) {
  118. if (!e.hasBeenReported()) {
  119. reportError(e);
  120. }
  121. throw e;
  122. }
  123. }
  124. /**
  125. * Normalize line endings in the source query, according to the XML 1.1 rules.
  126. * @param in the input query
  127. * @return the query with line endings normalized
  128. */
  129. private static String normalizeLineEndings11(String in) {
  130. if (in.indexOf((char)0xa) < 0 && in.indexOf((char)0x85) < 0 && in.indexOf((char)0x2028) < 0) {
  131. return in;
  132. }
  133. FastStringBuffer sb = new FastStringBuffer(in.length());
  134. for (int i = 0; i < in.length(); i++) {
  135. char ch = in.charAt(i);
  136. switch (ch) {
  137. case 0x85:
  138. case 0x2028:
  139. sb.append((char)0xa);
  140. break;
  141. case 0xd:
  142. if (i < in.length() - 1 && (in.charAt(i + 1) == (char)0xa || in.charAt(i + 1) == (char)0x85)) {
  143. sb.append((char)0xa);
  144. i++;
  145. } else {
  146. sb.append((char)0xa);
  147. }
  148. break;
  149. default:
  150. sb.append(ch);
  151. }
  152. }
  153. return sb.toString();
  154. }
  155. /**
  156. * Normalize line endings in the source query, according to the XML 1.0 rules.
  157. * @param in the input query
  158. * @return the query text with line endings normalized
  159. */
  160. private static String normalizeLineEndings10(String in) {
  161. if (in.indexOf((char)0xa) < 0) {
  162. return in;
  163. }
  164. FastStringBuffer sb = new FastStringBuffer(in.length());
  165. for (int i = 0; i < in.length(); i++) {
  166. char ch = in.charAt(i);
  167. switch (ch) {
  168. case 0xd:
  169. if (i < in.length() - 1 && in.charAt(i + 1) == (char)0xa) {
  170. sb.append((char)0xa);
  171. i++;
  172. } else {
  173. sb.append((char)0xa);
  174. }
  175. break;
  176. default:
  177. sb.append(ch);
  178. }
  179. }
  180. return sb.toString();
  181. }
  182. /**
  183. * Get the executable containing this expression.
  184. * @return the executable
  185. */
  186. public Executable getExecutable() {
  187. return executable;
  188. }
  189. /**
  190. * Set the executable used for this query expression
  191. * @param exec the executable
  192. */
  193. public void setExecutable(Executable exec) {
  194. executable = exec;
  195. }
  196. /**
  197. * Disable checks for certain kinds of cycle. This is equivalent to
  198. * <p><code>declare option saxon:allow-cycles "true"</code></p>
  199. * @param disable true if checks for import cycles are to be suppressed, that is,
  200. * if cycles should be allowed
  201. */
  202. public void setDisableCycleChecks(boolean disable) {
  203. disableCycleChecks = disable;
  204. }
  205. /**
  206. * Parse a top-level Query.
  207. * Prolog? Expression
  208. *
  209. * @param queryString The text of the query
  210. * @param start Offset of the start of the query
  211. * @param terminator Token expected to follow the query (usually Token.EOF)
  212. * @param env The static context
  213. * @return the Expression object that results from parsing
  214. * @throws net.sf.saxon.trans.XPathException
  215. * if the expression contains a syntax error
  216. */
  217. private Expression parseQuery(String queryString,
  218. int start,
  219. int terminator,
  220. QueryModule env) throws XPathException {
  221. this.env = env;
  222. nameChecker = env.getConfiguration().getNameChecker();
  223. language = XQUERY;
  224. t = new Tokenizer();
  225. try {
  226. t.tokenize(queryString, start, -1, 1);
  227. } catch (XPathException err) {
  228. grumble(err.getMessage());
  229. }
  230. parseVersionDeclaration();
  231. parseProlog();
  232. processPreamble();
  233. Expression exp = parseExpression();
  234. // Diagnostic code - show the expression before any optimizations
  235. // ExpressionPresenter ep = ExpressionPresenter.make(env.getConfiguration());
  236. // exp.explain(ep);
  237. // ep.close();
  238. // End of diagnostic code
  239. if (t.currentToken != terminator) {
  240. grumble("Unexpected token " + currentTokenDisplay() + " beyond end of query");
  241. }
  242. setLocation(exp);
  243. if (errorCount == 0) {
  244. return exp;
  245. } else {
  246. XPathException err = new XPathException("One or more static errors were reported during query analysis");
  247. err.setHasBeenReported();
  248. err.setErrorCode(firstError.getErrorCodeLocalPart()); // largely for the XQTS test driver
  249. throw err;
  250. }
  251. }
  252. /**
  253. * Parse a library module.
  254. * Prolog? Expression
  255. *
  256. * @param queryString The text of the library module.
  257. * @param env The static context. The result of parsing
  258. * a library module is that the static context is populated with a set of function
  259. * declarations and variable declarations. Each library module must have its own
  260. * static context objext.
  261. * @throws XPathException if the expression contains a syntax error
  262. */
  263. public final void parseLibraryModule(String queryString, QueryModule env)
  264. throws XPathException {
  265. this.env = env;
  266. nameChecker = env.getConfiguration().getNameChecker();
  267. executable = env.getExecutable();
  268. t = new Tokenizer();
  269. try {
  270. t.tokenize(queryString, 0, -1, 1);
  271. } catch (XPathException err) {
  272. grumble(err.getMessage());
  273. }
  274. parseVersionDeclaration();
  275. parseModuleDeclaration();
  276. parseProlog();
  277. processPreamble();
  278. if (t.currentToken != Token.EOF) {
  279. grumble("Unrecognized content found after the variable and function declarations in a library module");
  280. }
  281. if (errorCount != 0) {
  282. XPathException err = new XPathException("Static errors were reported in the imported library module");
  283. err.setErrorCode(firstError.getErrorCodeLocalPart());
  284. throw err;
  285. }
  286. }
  287. /**
  288. * Report a static error
  289. *
  290. * @param message the error message
  291. * @throws XPathException always thrown: an exception containing the
  292. * supplied message
  293. */
  294. protected void grumble(String message, String errorCode) throws XPathException {
  295. String s = t.recentText();
  296. ExpressionLocation loc = makeLocator();
  297. String prefix = getLanguage() +
  298. ("XPST0003".equals(errorCode) ? " syntax error " : " static error ") +
  299. (message.startsWith("...") ? "near" : "in") +
  300. " #" + s + "#:\n ";
  301. XPathException exception = new XPathException(prefix + message);
  302. exception.setErrorCode(errorCode);
  303. exception.setLocator(loc);
  304. reportError(exception);
  305. }
  306. private void reportError(XPathException exception) throws XPathException {
  307. errorCount++;
  308. if (firstError == null) {
  309. firstError = exception;
  310. }
  311. ((QueryModule)env).reportFatalError(exception);
  312. throw exception;
  313. }
  314. /**
  315. * Make a Locator object representing the current parsing location
  316. *
  317. * @return a Locator
  318. */
  319. private ExpressionLocation makeLocator() {
  320. int line = t.getLineNumber();
  321. int column = t.getColumnNumber();
  322. ExpressionLocation loc = new ExpressionLocation();
  323. loc.setSystemId(env.getSystemId());
  324. loc.setLineNumber(line);
  325. loc.setColumnNumber(column);
  326. return loc;
  327. }
  328. private static Pattern encNamePattern = Pattern.compile("^[A-Za-z]([A-Za-z0-9._\\x2D])*$");
  329. /**
  330. * Parse the version declaration if present.
  331. *
  332. * @throws XPathException in the event of a syntax error.
  333. */
  334. private void parseVersionDeclaration() throws XPathException {
  335. if (t.currentToken == Token.XQUERY_VERSION) {
  336. nextToken();
  337. expect(Token.STRING_LITERAL);
  338. if (!("1.0".equals(t.currentTokenValue))) {
  339. grumble("XQuery version must be 1.0", "XQST0031");
  340. }
  341. nextToken();
  342. if ("encoding".equals(t.currentTokenValue)) {
  343. nextToken();
  344. expect(Token.STRING_LITERAL);
  345. if (!encNamePattern.matcher(t.currentTokenValue).matches()) {
  346. grumble("Encoding name contains invalid characters", "XQST0087");
  347. }
  348. // we ignore the encoding now: it was handled earlier, while decoding the byte stream
  349. nextToken();
  350. }
  351. expect(Token.SEMICOLON);
  352. nextToken();
  353. }
  354. }
  355. /**
  356. * In a library module, parse the module declaration
  357. * Syntax: <"module" "namespace"> prefix "=" uri ";"
  358. *
  359. * @throws XPathException in the event of a syntax error.
  360. */
  361. private void parseModuleDeclaration() throws XPathException {
  362. expect(Token.MODULE_NAMESPACE);
  363. nextToken();
  364. expect(Token.NAME);
  365. String prefix = t.currentTokenValue;
  366. nextToken();
  367. expect(Token.EQUALS);
  368. nextToken();
  369. expect(Token.STRING_LITERAL);
  370. String uri = URILiteral(t.currentTokenValue);
  371. checkProhibitedPrefixes(prefix, uri);
  372. if (uri.length()==0) {
  373. grumble("Module namespace cannot be \"\"", "XQST0088");
  374. uri = "http://saxon.fallback.namespace/"; // for error recovery
  375. }
  376. nextToken();
  377. expect(Token.SEMICOLON);
  378. nextToken();
  379. try {
  380. ((QueryModule)env).declarePrologNamespace(prefix, uri);
  381. } catch (XPathException err) {
  382. err.setLocator(makeLocator());
  383. reportError(err);
  384. }
  385. ((QueryModule)env).setModuleNamespace(uri);
  386. }
  387. /**
  388. * Parse the query prolog. This method, and its subordinate methods which handle
  389. * individual declarations in the prolog, cause the static context to be updated
  390. * with relevant context information. On exit, t.currentToken is the first token
  391. * that is not recognized as being part of the prolog.
  392. *
  393. * @throws XPathException in the event of a syntax error.
  394. */
  395. private void parseProlog() throws XPathException {
  396. //boolean allowSetters = true;
  397. boolean allowModuleDecl = true;
  398. boolean allowDeclarations = true;
  399. while (true) {
  400. try {
  401. if (t.currentToken == Token.MODULE_NAMESPACE) {
  402. String uri = ((QueryModule)env).getModuleNamespace();
  403. if (uri == null) {
  404. grumble("Module declaration must not be used in a main module");
  405. } else {
  406. grumble("Module declaration appears more than once");
  407. }
  408. if (!allowModuleDecl) {
  409. grumble("Module declaration must precede other declarations in the query prolog");
  410. }
  411. }
  412. allowModuleDecl = false;
  413. switch (t.currentToken) {
  414. case Token.DECLARE_NAMESPACE:
  415. if (!allowDeclarations) {
  416. grumble("Namespace declarations cannot follow variables, functions, or options");
  417. }
  418. //allowSetters = false;
  419. parseNamespaceDeclaration();
  420. break;
  421. case Token.DECLARE_DEFAULT:
  422. nextToken();
  423. expect(Token.NAME);
  424. if (t.currentTokenValue.equals("element")) {
  425. if (!allowDeclarations) {
  426. grumble("Namespace declarations cannot follow variables, functions, or options");
  427. }
  428. //allowSetters = false;
  429. parseDefaultElementNamespace();
  430. } else if (t.currentTokenValue.equals("function")) {
  431. if (!allowDeclarations) {
  432. grumble("Namespace declarations cannot follow variables, functions, or options");
  433. }
  434. //allowSetters = false;
  435. parseDefaultFunctionNamespace();
  436. } else if (t.currentTokenValue.equals("collation")) {
  437. if (!allowDeclarations) {
  438. grumble("Collation declarations must appear earlier in the prolog");
  439. }
  440. parseDefaultCollation();
  441. } else if (t.currentTokenValue.equals("order")) {
  442. if (!allowDeclarations) {
  443. grumble("Order declarations must appear earlier in the prolog");
  444. }
  445. parseDefaultOrder();
  446. } else {
  447. grumble("After 'declare default', expected 'element', 'function', or 'collation'");
  448. }
  449. break;
  450. case Token.DECLARE_BOUNDARY_SPACE:
  451. if (!allowDeclarations) {
  452. grumble("'declare boundary-space' must appear earlier in the query prolog");
  453. }
  454. parseBoundarySpaceDeclaration();
  455. break;
  456. case Token.DECLARE_ORDERING:
  457. if (!allowDeclarations) {
  458. grumble("'declare ordering' must appear earlier in the query prolog");
  459. }
  460. parseOrderingDeclaration();
  461. break;
  462. case Token.DECLARE_COPY_NAMESPACES:
  463. if (!allowDeclarations) {
  464. grumble("'declare copy-namespaces' must appear earlier in the query prolog");
  465. }
  466. parseCopyNamespacesDeclaration();
  467. break;
  468. case Token.DECLARE_BASEURI:
  469. if (!allowDeclarations) {
  470. grumble("'declare base-uri' must appear earlier in the query prolog");
  471. }
  472. parseBaseURIDeclaration();
  473. break;
  474. case Token.IMPORT_SCHEMA:
  475. //allowSetters = false;
  476. if (!allowDeclarations) {
  477. grumble("Import schema must appear earlier in the prolog");
  478. }
  479. parseSchemaImport();
  480. break;
  481. case Token.IMPORT_MODULE:
  482. //allowSetters = false;
  483. if (!allowDeclarations) {
  484. grumble("Import module must appear earlier in the prolog");
  485. }
  486. parseModuleImport();
  487. break;
  488. case Token.DECLARE_VARIABLE:
  489. //allowSetters = false;
  490. if (allowDeclarations) {
  491. sealNamespaces(namespacesToBeSealed, env.getConfiguration());
  492. allowDeclarations = false;
  493. }
  494. processPreamble();
  495. parseVariableDeclaration();
  496. break;
  497. case Token.DECLARE_FUNCTION:
  498. if (allowDeclarations) {
  499. sealNamespaces(namespacesToBeSealed, env.getConfiguration());
  500. allowDeclarations = false;
  501. }
  502. processPreamble();
  503. parseFunctionDeclaration(false);
  504. break;
  505. case Token.DECLARE_UPDATING:
  506. nextToken();
  507. if (!isKeyword("function")) {
  508. grumble("expected 'function' after 'declare updating");
  509. }
  510. if (allowDeclarations) {
  511. sealNamespaces(namespacesToBeSealed, env.getConfiguration());
  512. allowDeclarations = false;
  513. }
  514. processPreamble();
  515. parseUpdatingFunctionDeclaration();
  516. break;
  517. case Token.DECLARE_OPTION:
  518. if (allowDeclarations) {
  519. sealNamespaces(namespacesToBeSealed, env.getConfiguration());
  520. allowDeclarations = false;
  521. }
  522. parseOptionDeclaration();
  523. break;
  524. case Token.DECLARE_CONSTRUCTION:
  525. if (!allowDeclarations) {
  526. grumble("'declare construction' must appear earlier in the query prolog");
  527. }
  528. parseConstructionDeclaration();
  529. break;
  530. case Token.DECLARE_REVALIDATION:
  531. if (!allowDeclarations) {
  532. grumble("'declare revalidation' must appear earlier in the query prolog");
  533. }
  534. parseRevalidationDeclaration();
  535. break;
  536. default:
  537. return;
  538. }
  539. expect(Token.SEMICOLON);
  540. nextToken();
  541. } catch (XPathException err) {
  542. if (err.getLocator() == null) {
  543. err.setLocator(makeLocator());
  544. }
  545. if (!err.hasBeenReported()) {
  546. errorCount++;
  547. if (firstError == null) {
  548. firstError = err;
  549. }
  550. ((QueryModule)env).reportFatalError(err);
  551. }
  552. // we've reported an error, attempt to recover by skipping to the
  553. // next semicolon
  554. while (t.currentToken != Token.SEMICOLON) {
  555. nextToken();
  556. if (t.currentToken == Token.EOF) {
  557. return;
  558. } else if (t.currentToken == Token.RCURLY) {
  559. t.lookAhead();
  560. } else if (t.currentToken == Token.TAG) {
  561. parsePseudoXML(true);
  562. }
  563. }
  564. nextToken();
  565. }
  566. }
  567. }
  568. private void sealNamespaces(List namespacesToBeSealed, Configuration config) {
  569. for (Iterator iter = namespacesToBeSealed.iterator(); iter.hasNext();) {
  570. String ns = (String)iter.next();
  571. config.sealNamespace(ns);
  572. }
  573. }
  574. /**
  575. * Method called once the setters have been read to do tidying up that can't be done until we've got
  576. * to the end
  577. *
  578. * @throws XPathException
  579. */
  580. private void processPreamble() throws XPathException {
  581. if (preambleProcessed) {
  582. return;
  583. }
  584. preambleProcessed = true;
  585. if (foundDefaultCollation) {
  586. String collationName = env.getDefaultCollationName();
  587. URI collationURI;
  588. try {
  589. collationURI = new URI(collationName);
  590. if (!collationURI.isAbsolute()) {
  591. URI base = new URI(env.getBaseURI());
  592. collationURI = base.resolve(collationURI);
  593. collationName = collationURI.toString();
  594. }
  595. } catch (URISyntaxException err) {
  596. grumble("Default collation name '" + collationName + "' is not a valid URI");
  597. collationName = NamespaceConstant.CODEPOINT_COLLATION_URI;
  598. }
  599. if (env.getCollation(collationName) == null) {
  600. grumble("Default collation name '" + collationName + "' is not a recognized collation", "XQST0038");
  601. collationName = NamespaceConstant.CODEPOINT_COLLATION_URI;
  602. }
  603. ((QueryModule)env).setDefaultCollationName(collationName);
  604. }
  605. for (Iterator iter = schemaImports.iterator(); iter.hasNext();) {
  606. Import imp = (Import)iter.next();
  607. applySchemaImport(imp);
  608. }
  609. for (Iterator iter = moduleImports.iterator(); iter.hasNext();) {
  610. Import imp = (Import)iter.next();
  611. // Check that this import would not create a cycle involving a change of namespace
  612. if (!disableCycleChecks) {
  613. if (!imp.namespaceURI.equals(((QueryModule)env).getModuleNamespace())) {
  614. QueryModule parent = (QueryModule)env;
  615. if (!parent.mayImportModule(imp.namespaceURI)) {
  616. XPathException err = new XPathException(
  617. "A module cannot import itself directly or indirectly, unless all modules in the cycle are in the same namespace");
  618. err.setErrorCode("XQST0073");
  619. err.setIsStaticError(true);
  620. throw err;
  621. }
  622. }
  623. }
  624. applyModuleImport(imp);
  625. }
  626. }
  627. private void parseDefaultCollation() throws XPathException {
  628. // <"default" "collation"> StringLiteral
  629. if (foundDefaultCollation) {
  630. grumble("default collation appears more than once", "XQST0038");
  631. }
  632. foundDefaultCollation = true;
  633. nextToken();
  634. expect(Token.STRING_LITERAL);
  635. String uri = URILiteral(t.currentTokenValue);
  636. ((QueryModule)env).setDefaultCollationName(uri);
  637. nextToken();
  638. }
  639. /**
  640. * parse "declare default order empty (least|greatest)"
  641. */
  642. private void parseDefaultOrder() throws XPathException {
  643. if (foundEmptyOrderingDeclaration) {
  644. grumble("empty ordering declaration appears more than once", "XQST0069");
  645. }
  646. foundEmptyOrderingDeclaration = true;
  647. nextToken();
  648. if (!isKeyword("empty")) {
  649. grumble("After 'declare default order', expected keyword 'empty'");
  650. }
  651. nextToken();
  652. if (isKeyword("least")) {
  653. ((QueryModule)env).setEmptyLeast(true);
  654. } else if (isKeyword("greatest")) {
  655. ((QueryModule)env).setEmptyLeast(false);
  656. } else {
  657. grumble("After 'declare default order empty', expected keyword 'least' or 'greatest'");
  658. }
  659. nextToken();
  660. }
  661. /**
  662. * Parse the "declare xmlspace" declaration.
  663. * Syntax: <"declare" "boundary-space"> ("preserve" | "strip")
  664. *
  665. * @throws XPathException
  666. */
  667. private void parseBoundarySpaceDeclaration() throws XPathException {
  668. if (foundBoundarySpaceDeclaration) {
  669. grumble("'declare boundary-space' appears more than once", "XQST0068");
  670. }
  671. foundBoundarySpaceDeclaration = true;
  672. nextToken();
  673. expect(Token.NAME);
  674. if ("preserve".equals(t.currentTokenValue)) {
  675. ((QueryModule)env).setPreserveBoundarySpace(true);
  676. } else if ("strip".equals(t.currentTokenValue)) {
  677. ((QueryModule)env).setPreserveBoundarySpace(false);
  678. } else {
  679. grumble("boundary-space must be 'preserve' or 'strip'");
  680. }
  681. nextToken();
  682. }
  683. /**
  684. * Parse the "declare ordering" declaration.
  685. * Syntax: <"declare" "ordering"> ("ordered" | "unordered")
  686. *
  687. * @throws XPathException
  688. */
  689. private void parseOrderingDeclaration() throws XPathException {
  690. if (foundOrderingDeclaration) {
  691. grumble("ordering mode declaration appears more than once", "XQST0065");
  692. }
  693. foundOrderingDeclaration = true;
  694. nextToken();
  695. expect(Token.NAME);
  696. if ("ordered".equals(t.currentTokenValue)) {
  697. // no action
  698. } else if ("unordered".equals(t.currentTokenValue)) {
  699. // no action
  700. } else {
  701. grumble("ordering mode must be 'ordered' or 'unordered'");
  702. }
  703. nextToken();
  704. }
  705. /**
  706. * Parse the "declare copy-namespaces" declaration.
  707. * Syntax: <"declare" "copy-namespaces"> ("preserve" | "no-preserve") "," ("inherit" | "no-inherit")
  708. *
  709. * @throws XPathException
  710. */
  711. private void parseCopyNamespacesDeclaration() throws XPathException {
  712. if (foundCopyNamespaces) {
  713. grumble("declare inherit-namespaces appears more than once", "XQST0055");
  714. }
  715. foundCopyNamespaces = true;
  716. nextToken();
  717. expect(Token.NAME);
  718. if ("preserve".equals(t.currentTokenValue)) {
  719. ((QueryModule)env).setPreserveNamespaces(true);
  720. } else if ("no-preserve".equals(t.currentTokenValue)) {
  721. ((QueryModule)env).setPreserveNamespaces(false);
  722. } else {
  723. grumble("copy-namespaces must be followed by 'preserve' or 'no-preserve'");
  724. }
  725. nextToken();
  726. expect(Token.COMMA);
  727. nextToken();
  728. expect(Token.NAME);
  729. if ("inherit".equals(t.currentTokenValue)) {
  730. ((QueryModule)env).setInheritNamespaces(true);
  731. } else if ("no-inherit".equals(t.currentTokenValue)) {
  732. ((QueryModule)env).setInheritNamespaces(false);
  733. } else {
  734. grumble("After the comma in the copy-namespaces declaration, expected 'inherit' or 'no-inherit'");
  735. }
  736. nextToken();
  737. }
  738. /**
  739. * Parse the "declare construction" declaration.
  740. * Syntax: <"declare" "construction"> ("preserve" | "strip")
  741. *
  742. * @throws XPathException
  743. */
  744. private void parseConstructionDeclaration() throws XPathException {
  745. if (foundConstructionDeclaration) {
  746. grumble("declare construction appears more than once", "XQST0067");
  747. }
  748. foundConstructionDeclaration = true;
  749. nextToken();
  750. expect(Token.NAME);
  751. int val;
  752. if ("preserve".equals(t.currentTokenValue)) {
  753. val = Validation.PRESERVE;
  754. } else if ("strip".equals(t.currentTokenValue)) {
  755. val = Validation.STRIP;
  756. } else {
  757. grumble("construction mode must be 'preserve' or 'strip'");
  758. val = Validation.STRIP;
  759. }
  760. ((QueryModule)env).setConstructionMode(val);
  761. nextToken();
  762. }
  763. /**
  764. * Parse the "declare revalidation" declaration.
  765. * Syntax: not allowed unless XQuery update is in use
  766. *
  767. * @throws XPathException
  768. */
  769. protected void parseRevalidationDeclaration() throws XPathException {
  770. grumble("declare revalidation is allowed only in XQuery Update");
  771. }
  772. /**
  773. * Parse (and process) the schema import declaration.
  774. * SchemaImport ::= "import" "schema" SchemaPrefix? URILiteral ("at" URILiteral ("," URILiteral)*)?
  775. * SchemaPrefix ::= ("namespace" NCName "=") | ("default" "element" "namespace")
  776. */
  777. private void parseSchemaImport() throws XPathException {
  778. if (!env.getConfiguration().isSchemaAware(Configuration.XQUERY)) {
  779. grumble("To import a schema, you need the schema-aware version of Saxon", "XQST0009");
  780. }
  781. Import sImport = new Import();
  782. String prefix = null;
  783. sImport.namespaceURI = null;
  784. sImport.locationURIs = new ArrayList(5);
  785. nextToken();
  786. if (isKeyword("namespace")) {
  787. t.setState(Tokenizer.DEFAULT_STATE);
  788. nextToken();
  789. expect(Token.NAME);
  790. prefix = t.currentTokenValue;
  791. nextToken();
  792. expect(Token.EQUALS);
  793. nextToken();
  794. } else if (isKeyword("default")) {
  795. nextToken();
  796. if (!isKeyword("element")) {
  797. grumble("In 'import schema', expected 'element namespace'");
  798. }
  799. nextToken();
  800. if (!isKeyword("namespace")) {
  801. grumble("In 'import schema', expected keyword 'namespace'");
  802. }
  803. nextToken();
  804. prefix = "";
  805. }
  806. if (t.currentToken == Token.STRING_LITERAL) {
  807. String uri = URILiteral(t.currentTokenValue);
  808. checkProhibitedPrefixes(prefix, uri);
  809. sImport.namespaceURI = uri;
  810. nextToken();
  811. if (isKeyword("at")) {
  812. nextToken();
  813. expect(Token.STRING_LITERAL);
  814. sImport.locationURIs.add(URILiteral(t.currentTokenValue));
  815. nextToken();
  816. while (t.currentToken == Token.COMMA) {
  817. nextToken();
  818. expect(Token.STRING_LITERAL);
  819. sImport.locationURIs.add(URILiteral(t.currentTokenValue));
  820. nextToken();
  821. }
  822. } else if (t.currentToken != Token.SEMICOLON) {
  823. grumble("After the target namespace URI, expected 'at' or ';'");
  824. }
  825. } else {
  826. grumble("After 'import schema', expected 'namespace', 'default', or a string-literal");
  827. }
  828. if (prefix != null) {
  829. try {
  830. if (prefix.length() == 0) {
  831. ((QueryModule)env).setDefaultElementNamespace(sImport.namespaceURI);
  832. } else {
  833. if (sImport.namespaceURI == null || "".equals(sImport.namespaceURI)) {
  834. grumble("A prefix cannot be bound to the null namespace", "XQST0057");
  835. }
  836. ((QueryModule)env).declarePrologNamespace(prefix, sImport.namespaceURI);
  837. }
  838. } catch (XPathException err) {
  839. err.setLocator(makeLocator());
  840. reportError(err);
  841. }
  842. }
  843. for (Iterator iter = schemaImports.iterator(); iter.hasNext();) {
  844. Import imp = (Import)iter.next();
  845. if (imp.namespaceURI.equals(sImport.namespaceURI)) {
  846. grumble("Schema namespace '" + sImport.namespaceURI + "' is imported more than once", "XQST0058");
  847. break;
  848. }
  849. }
  850. schemaImports.add(sImport);
  851. }
  852. private void applySchemaImport(Import sImport) throws XPathException {
  853. // Do the importing
  854. Configuration config = env.getConfiguration();
  855. if (!config.isSchemaAvailable(sImport.namespaceURI)) {
  856. if (!sImport.locationURIs.isEmpty()) {
  857. try {
  858. PipelineConfiguration pipe = config.makePipelineConfiguration();
  859. config.readMultipleSchemas(pipe, env.getBaseURI(), sImport.locationURIs, sImport.namespaceURI);
  860. namespacesToBeSealed.add(sImport.namespaceURI);
  861. } catch (TransformerConfigurationException err) {
  862. grumble("Error in schema. " + err.getMessage(), "XQST0059");
  863. }
  864. } else {
  865. grumble("Unable to locate requested schema", "XQST0059");
  866. }
  867. }
  868. ((QueryModule)env).addImportedSchema(sImport.namespaceURI, env.getBaseURI(), sImport.locationURIs);
  869. }
  870. /**
  871. * Parse (and expand) the module import declaration.
  872. * Syntax: <"import" "module" ("namespace" NCName "=")? uri ("at" uri ("," uri)*)? ";"
  873. */
  874. private void parseModuleImport() throws XPathException {
  875. QueryModule thisModule = (QueryModule)env;
  876. Import mImport = new Import();
  877. String prefix = null;
  878. mImport.namespaceURI = null;
  879. mImport.locationURIs = new ArrayList(5);
  880. nextToken();
  881. if (t.currentToken == Token.NAME && t.currentTokenValue.equals("namespace")) {
  882. t.setState(Tokenizer.DEFAULT_STATE);
  883. nextToken();
  884. expect(Token.NAME);
  885. prefix = t.currentTokenValue;
  886. nextToken();
  887. expect(Token.EQUALS);
  888. nextToken();
  889. }
  890. if (t.currentToken == Token.STRING_LITERAL) {
  891. String uri = URILiteral(t.currentTokenValue);
  892. checkProhibitedPrefixes(prefix, uri);
  893. mImport.namespaceURI = uri;
  894. if (mImport.namespaceURI.length() == 0) {
  895. grumble("Imported module namespace cannot be \"\"", "XQST0088");
  896. mImport.namespaceURI = "http://saxon.fallback.namespace/line" + t.getLineNumber(); // for error recovery
  897. }
  898. if (importedModules.contains(mImport.namespaceURI)) {
  899. grumble("Two 'import module' declarations specify the same module namespace", "XQST0047");
  900. }
  901. importedModules.add(mImport.namespaceURI);
  902. ((QueryModule)env).addImportedNamespace(mImport.namespaceURI);
  903. nextToken();
  904. if (isKeyword("at")) {
  905. do {
  906. nextToken();
  907. expect(Token.STRING_LITERAL);
  908. mImport.locationURIs.add(URILiteral(t.currentTokenValue));
  909. nextToken();
  910. } while (t.currentToken == Token.COMMA);
  911. }
  912. } else {
  913. grumble("After 'import module', expected 'namespace' or a string-literal");
  914. }
  915. if (prefix != null) {
  916. try {
  917. thisModule.declarePrologNamespace(prefix, mImport.namespaceURI);
  918. } catch (XPathException err) {
  919. err.setLocator(makeLocator());
  920. reportError(err);
  921. }
  922. }
  923. // // Check that this import would not create a cycle involving a change of namespace
  924. // if (!disableCycleChecks) {
  925. // if (!mImport.namespaceURI.equals(((QueryModule)env).getModuleNamespace())) {
  926. // QueryModule parent = (QueryModule)env;
  927. // if (!parent.mayImport(mImport.namespaceURI)) {
  928. // StaticError err = new StaticError("A module cannot import itself directly or indirectly, unless all modules in the cycle are in the same namespace");
  929. // err.setErrorCode("XQST0073");
  930. // throw err;
  931. // }
  932. // }
  933. // }
  934. moduleImports.add(mImport);
  935. }
  936. public void applyModuleImport(Import mImport) throws XPathException {
  937. boolean foundOne = false;
  938. // resolve the location URIs against the base URI
  939. Platform platform = Configuration.getPlatform();
  940. for (int i=0; i<mImport.locationURIs.size(); i++) {
  941. try {
  942. String uri = (String)mImport.locationURIs.get(i);
  943. URI abs = platform.makeAbsolute(uri, env.getBaseURI());
  944. mImport.locationURIs.set(i, abs);
  945. } catch (URISyntaxException e) {
  946. grumble("Invalid URI " + mImport.locationURIs.get(i) + ": " + e.getMessage());
  947. }
  948. }
  949. // If any of the modules are already loaded, don't re-read them; but do check that none of them
  950. // references the current module namespace directly or indirectly
  951. List existingModules = executable.getQueryLibraryModules(mImport.namespaceURI);
  952. if (existingModules != null) {
  953. //System.err.println("Number of existing modules: " + existingModules.size());
  954. for (int m = 0; m < existingModules.size(); m++) {
  955. QueryModule importedModule = (QueryModule)existingModules.get(m);
  956. //System.err.println("Existing module location URI = " + importedModule.getLocationURI());
  957. if (!importedModule.getLocationURI().equals(((QueryModule)env).getLocationURI())) {
  958. //QueryReader.importModuleContents(importedModule, (QueryModule)env);
  959. foundOne = true;
  960. }
  961. if (!disableCycleChecks &&
  962. ((QueryModule)env).getModuleNamespace() != null &&
  963. !((QueryModule)env).getModuleNamespace().equals(importedModule.getModuleNamespace()) &&
  964. importedModule.importsNamespaceIndirectly(((QueryModule)env).getModuleNamespace())) {
  965. grumble("A cycle exists among the module imports, involving namespaces " +
  966. ((QueryModule)env).getModuleNamespace() + " and " +
  967. importedModule.getModuleNamespace());
  968. }
  969. for (int h = mImport.locationURIs.size() - 1; h >= 0; h--) {
  970. if (mImport.locationURIs.get(h).equals(importedModule.getLocationURI())) {
  971. mImport.locationURIs.remove(h);
  972. }
  973. }
  974. }
  975. }
  976. // If we've found at least one module, and there are no location URIs left, call it a day.
  977. if (mImport.locationURIs.isEmpty() && foundOne) {
  978. return;
  979. }
  980. // Call the module URI resolver to find the remaining modules
  981. ModuleURIResolver resolver = ((QueryModule)env).getUserQueryContext().getModuleURIResolver();
  982. String[] hints = new String[mImport.locationURIs.size()];
  983. for (int h=0; h<hints.length; h++) {
  984. hints[h] = mImport.locationURIs.get(h).toString();
  985. }
  986. StreamSource[] sources = null;
  987. if (resolver != null) {
  988. sources = resolver.resolve(mImport.namespaceURI, env.getBaseURI(), hints);
  989. }
  990. if (sources == null) {
  991. if (hints.length == 0) {
  992. if (existingModules == null) {
  993. grumble("Cannot locate module for namespace " + mImport.namespaceURI, "XQST0059");
  994. }
  995. }
  996. resolver = env.getConfiguration().getStandardModuleURIResolver();
  997. sources = resolver.resolve(mImport.namespaceURI, env.getBaseURI(), hints);
  998. }
  999. for (int m = 0; m < sources.length; m++) {
  1000. StreamSource ss = sources[m];
  1001. String baseURI = ss.getSystemId();
  1002. if (baseURI == null) {
  1003. if (m < hints.length) {
  1004. baseURI = hints[m];
  1005. ss.setSystemId(hints[m]);
  1006. } else {
  1007. grumble("No base URI available for imported module", "XQST0059");
  1008. }
  1009. }
  1010. // Although the module hadn't been loaded when we started, it might have been loaded since, as
  1011. // a result of a reference from another imported module. Note, we are careful here to use URI.equals()
  1012. // rather that String.equals() to compare URIs, as this gives a slightly more intelligent comparison,
  1013. // for example the scheme name is case-independent, and file:///x/y/z matches file:/x/y/z.
  1014. // TODO: use similar logic when loading schema modules
  1015. existingModules = executable.getQueryLibraryModules(mImport.namespaceURI);
  1016. boolean loaded = false;
  1017. if (existingModules != null && m < hints.length) {
  1018. for (int e=0; e<existingModules.size(); e++) {
  1019. if (((QueryModule)existingModules.get(e)).getLocationURI().equals(mImport.locationURIs.get(m))) {
  1020. loaded = true;
  1021. break;
  1022. }
  1023. }
  1024. }
  1025. if (loaded) {
  1026. break;
  1027. }
  1028. try {
  1029. String queryText = QueryReader.readSourceQuery(ss, nameChecker);
  1030. try {
  1031. if (ss.getInputStream() != null) {
  1032. ss.getInputStream().close();
  1033. } else if (ss.getReader() != null) {
  1034. ss.getReader().close();
  1035. }
  1036. } catch (IOException e) {
  1037. throw new XPathException("Failure while closing file for imported query module");
  1038. }
  1039. QueryModule.makeQueryModule(
  1040. baseURI, executable, (QueryModule)env, queryText, mImport.namespaceURI,
  1041. disableCycleChecks);
  1042. } catch (XPathException err) {
  1043. err.maybeSetLocation(makeLocator());
  1044. reportError(err);
  1045. }
  1046. }
  1047. }
  1048. /**
  1049. * Parse the Base URI declaration.
  1050. * Syntax: <"declare" "base-uri"> uri-literal
  1051. *
  1052. * @throws XPathException
  1053. */
  1054. private void parseBaseURIDeclaration() throws XPathException {
  1055. if (foundBaseURIDeclaration) {
  1056. grumble("Base URI Declaration may only appear once", "XQST0032");
  1057. }
  1058. foundBaseURIDeclaration = true;
  1059. nextToken();
  1060. expect(Token.STRING_LITERAL);
  1061. String uri = URILiteral(t.currentTokenValue);
  1062. try {
  1063. // if the supplied URI is relative, try to resolve it
  1064. URI baseURI = new URI(uri);
  1065. if (!baseURI.isAbsolute()) {
  1066. String oldBase = env.getBaseURI();
  1067. URI oldBaseURI = new URI(oldBase);
  1068. uri = oldBaseURI.resolve(uri).toString();
  1069. }
  1070. ((QueryModule)env).setBaseURI(uri);
  1071. } catch (URISyntaxException err) {
  1072. // The spec says this "is not intrinsically an error", but can cause a failure later
  1073. ((QueryModule)env).setBaseURI(uri);
  1074. }
  1075. nextToken();
  1076. }
  1077. /**
  1078. * Parse the "default function namespace" declaration.
  1079. * Syntax: <"declare" "default" "function" "namespace"> StringLiteral
  1080. *
  1081. * @throws XPathException to indicate a syntax error
  1082. */
  1083. private void parseDefaultFunctionNamespace() throws XPathException {
  1084. if (foundDefaultFunctionNamespace) {
  1085. grumble("default function namespace appears more than once", "XQST0066");
  1086. }
  1087. foundDefaultFunctionNamespace = true;
  1088. nextToken();
  1089. expect(Token.NAME);
  1090. if (!"namespace".equals(t.currentTokenValue)) {
  1091. grumble("After 'declare default function', expected 'namespace'");
  1092. }
  1093. nextToken();
  1094. expect(Token.STRING_LITERAL);
  1095. String uri = URILiteral(t.currentTokenValue);
  1096. ((QueryModule)env).setDefaultFunctionNamespace(uri);
  1097. nextToken();
  1098. }
  1099. /**
  1100. * Parse the "default element namespace" declaration.
  1101. * Syntax: <"declare" "default" "element" "namespace"> StringLiteral
  1102. *
  1103. * @throws XPathException to indicate a syntax error
  1104. */
  1105. private void parseDefaultElementNamespace() throws XPathException {
  1106. if (foundDefaultElementNamespace) {
  1107. grumble("default element namespace appears more than once", "XQST0066");
  1108. }
  1109. foundDefaultElementNamespace = true;
  1110. nextToken();
  1111. expect(Token.NAME);
  1112. if (!"namespace".equals(t.currentTokenValue)) {
  1113. grumble("After 'declare default element', expected 'namespace'");
  1114. }
  1115. nextToken();
  1116. expect(Token.STRING_LITERAL);
  1117. String uri = URILiteral(t.currentTokenValue);
  1118. ((QueryModule)env).setDefaultElementNamespace(uri);
  1119. nextToken();
  1120. }
  1121. /**
  1122. * Parse a namespace declaration in the Prolog.
  1123. * Syntax: <"declare" "namespace"> NCName "=" StringLiteral
  1124. *
  1125. * @throws XPathException
  1126. */
  1127. private void parseNamespaceDeclaration() throws XPathException {
  1128. nextToken();
  1129. expect(Token.NAME);
  1130. String prefix = t.currentTokenValue;
  1131. if (!nameChecker.isValidNCName(prefix)) {
  1132. grumble("Invalid namespace prefix " + Err.wrap(prefix));
  1133. }
  1134. nextToken();
  1135. expect(Token.EQUALS);
  1136. nextToken();
  1137. expect(Token.STRING_LITERAL);
  1138. String uri = URILiteral(t.currentTokenValue);
  1139. checkProhibitedPrefixes(prefix, uri);
  1140. try {
  1141. ((QueryModule)env).declarePrologNamespace(prefix, uri);
  1142. } catch (XPathException err) {
  1143. err.setLocator(makeLocator());
  1144. reportError(err);
  1145. }
  1146. nextToken();
  1147. }
  1148. /**
  1149. * Check that a namespace declaration does not use a prohibited prefix or URI (xml or xmlns)
  1150. * @param prefix the prefix to be tested
  1151. * @param uri the URI being declared
  1152. * @throws XPathException if the prefix is prohibited
  1153. */
  1154. private void checkProhibitedPrefixes(String prefix, String uri) throws XPathException {
  1155. if (prefix == null) {
  1156. prefix = "";
  1157. }
  1158. if (uri == null) {
  1159. uri = "";
  1160. }
  1161. if ("xmlns".equals(prefix)) {
  1162. grumble("The namespace prefix 'xmlns' cannot be redeclared", "XQST0070");
  1163. }
  1164. if (uri.equals(NamespaceConstant.XMLNS)) {
  1165. grumble("The xmlns namespace URI is reserved", "XQST0070");
  1166. }
  1167. if (uri.equals(NamespaceConstant.XML) && !prefix.equals("xml")) {
  1168. grumble("The XML namespace cannot be bound to any prefix other than 'xml'", "XQST0070");
  1169. }
  1170. if (prefix.equals("xml") && !uri.equals(NamespaceConstant.XML)) {
  1171. grumble("The prefix 'xml' cannot be bound to any namespace other than " + NamespaceConstant.XML, "XQST0070");
  1172. }
  1173. // TODO: resolution of spec bug 4463 also appears to disallow explicit binding of the xml prefix to the xml namespace
  1174. }
  1175. /**
  1176. * Parse a global variable definition.
  1177. * <"declare" "variable" "$"> VarName TypeDeclaration?
  1178. * ((":=" Expr ) | "external")
  1179. * Currently accept both
  1180. *
  1181. * @throws XPathException
  1182. */
  1183. private void parseVariableDeclaration() throws XPathException {
  1184. int offset = t.currentTokenStartOffset;
  1185. GlobalVariableDefinition var = new GlobalVariableDefinition();
  1186. var.setLineNumber(t.getLineNumber());
  1187. var.setSystemId(env.getSystemId());
  1188. nextToken();
  1189. expect(Token.DOLLAR);
  1190. t.setState(Tokenizer.BARE_NAME_STATE);
  1191. nextToken();
  1192. expect(Token.NAME);
  1193. String varName = t.currentTokenValue;
  1194. StructuredQName varQName = makeStructuredQName(t.currentTokenValue, false);
  1195. var.setVariableQName(varQName);
  1196. String uri = varQName.getNamespaceURI();
  1197. String moduleURI = ((QueryModule)env).getModuleNamespace();
  1198. if (moduleURI != null && !moduleURI.equals(uri)) {
  1199. grumble("A variable declared in a library module must be in the module namespace", "XQST0048");
  1200. }
  1201. nextToken();
  1202. SequenceType requiredType = SequenceType.ANY_SEQUENCE;
  1203. if (t.currentToken == Token.AS) {
  1204. t.setState(Tokenizer.SEQUENCE_TYPE_STATE);
  1205. nextToken();
  1206. requiredType = parseSequenceType();
  1207. }
  1208. var.setRequiredType(requiredType);
  1209. if (t.currentToken == Token.ASSIGN) {
  1210. t.setState(Tokenizer.DEFAULT_STATE);
  1211. nextToken();
  1212. Expression exp = parseExprSingle();
  1213. var.setIsParameter(false);
  1214. var.setValueExpression(makeTracer(offset, exp, StandardNames.XSL_VARIABLE, varQName));
  1215. } else if (t.currentToken == Token.NAME) {
  1216. if ("external".equals(t.currentTokenValue)) {
  1217. var.setIsParameter(true);
  1218. if (defaultValue != null) {
  1219. var.setValueExpression(defaultValue);
  1220. }
  1221. nextToken();
  1222. } else {
  1223. grumble("Variable must either be initialized or be declared as external");
  1224. }
  1225. } else {
  1226. grumble("Expected ':=' or 'external' in variable declaration");
  1227. }
  1228. QueryModule qenv = (QueryModule)env;
  1229. if (qenv.getModuleNamespace() != null &&
  1230. !uri.equals(qenv.getModuleNamespace())) {
  1231. grumble("Variable " + Err.wrap(varName, Err.VARIABLE) + " is not defined in the module namespace");
  1232. }
  1233. try {
  1234. qenv.declareVariable(var);
  1235. } catch (XPathException e) {
  1236. grumble(e.getMessage(), e.getErrorCodeLocalPart());
  1237. }
  1238. }
  1239. /**
  1240. * Parse a function declaration.
  1241. * <p>Syntax:<br/>
  1242. * <"declare" "function"> <QName "("> ParamList? (")" | (<")" "as"> SequenceType))
  1243. * (EnclosedExpr | "external")
  1244. * </p>
  1245. * <p>On entry, the "define function" has already been recognized</p>
  1246. *
  1247. * @throws XPathException if a syntax error is found
  1248. */
  1249. protected void parseFunctionDeclaration(boolean isUpdating) throws XPathException {
  1250. // the next token should be the < QNAME "("> pair
  1251. int offset = t.currentTokenStartOffset;
  1252. nextToken();
  1253. expect(Token.FUNCTION);
  1254. String uri;
  1255. StructuredQName qName;
  1256. if (t.currentTokenValue.indexOf(':') < 0) {
  1257. uri = env.getDefaultFunctionNamespace();
  1258. qName = new StructuredQName("", uri, t.currentTokenValue);
  1259. } else {
  1260. qName = makeStructuredQName(t.currentTokenValue, false);
  1261. uri = qName.getNamespaceURI();
  1262. }
  1263. if (uri.length()==0) {
  1264. grumble("The function must be in a namespace", "XQST0060");
  1265. }
  1266. String moduleURI = ((QueryModule)env).getModuleNamespace();
  1267. if (moduleURI != null && !moduleURI.equals(uri)) {
  1268. grumble("A function in a library module must be in the module namespace", "XQST0048");
  1269. }
  1270. if (NamespaceConstant.isReservedInQuery(uri)) {
  1271. grumble("The function name " + t.currentTokenValue + " is in a reserved namespace", "XQST0045");
  1272. }
  1273. XQueryFunction func = new XQueryFunction();
  1274. func.setFunctionName(qName);
  1275. func.setResultType(SequenceType.ANY_SEQUENCE);
  1276. func.setBody(null);
  1277. func.setLineNumber(t.getLineNumber(offset));
  1278. func.setColumnNumber(t.getColumnNumber(offset));
  1279. func.setSystemId(env.getSystemId());
  1280. func.setStaticContext((QueryModule)env);
  1281. func.setMemoFunction(memoFunction);
  1282. func.setExecutable(getExecutable());
  1283. func.setUpdating(isUpdating);
  1284. nextToken();
  1285. HashSet paramNames = new HashSet(8);
  1286. while (t.currentToken != Token.RPAR) {
  1287. // ParamList ::= Param ("," Param)*
  1288. // Param ::= "$" VarName TypeDeclaration?
  1289. expect(Token.DOLLAR);
  1290. nextToken();
  1291. expect(Token.NAME);
  1292. String argName = t.currentTokenValue;
  1293. StructuredQName argQName = makeStructuredQName(argName, false);
  1294. if (paramNames.contains(argQName)) {
  1295. grumble("Duplicate parameter name " + Err.wrap(t.currentTokenValue, Err.VARIABLE), "XQST0039");
  1296. }
  1297. paramNames.add(argQName);
  1298. SequenceType paramType = SequenceType.ANY_SEQUENCE;
  1299. nextToken();
  1300. if (t.currentToken == Token.AS) {
  1301. nextToken();
  1302. paramType = parseSequenceType();
  1303. }
  1304. UserFunctionParameter arg = new UserFunctionParameter();
  1305. arg.setRequiredType(paramType);
  1306. arg.setVariableQName(argQName);
  1307. func.addArgument(arg);
  1308. declareRangeVariable(arg);
  1309. if (t.currentToken == Token.RPAR) {
  1310. break;
  1311. } else if (t.currentToken == Token.COMMA) {
  1312. nextToken();
  1313. } else {
  1314. grumble("Expected ',' or ')' after function argument, found '" +
  1315. Token.tokens[t.currentToken] + '\'');
  1316. }
  1317. }
  1318. t.setState(Tokenizer.BARE_NAME_STATE);
  1319. nextToken();
  1320. if (t.currentToken == Token.AS) {
  1321. t.setState(Tokenizer.SEQUENCE_TYPE_STATE);
  1322. nextToken();
  1323. func.setResultType(parseSequenceType());
  1324. }
  1325. if (isKeyword("external")) {
  1326. grumble("Saxon does not allow external functions to be declared");
  1327. } else {
  1328. expect(Token.LCURLY);
  1329. t.setState(Tokenizer.DEFAULT_STATE);
  1330. nextToken();
  1331. func.setBody(parseExpression());
  1332. expect(Token.RCURLY);
  1333. lookAhead(); // must be done manually after an RCURLY
  1334. }
  1335. UserFunctionParameter[] params = func.getParameterDefinitions();
  1336. for (int i = 0; i < params.length; i++) {
  1337. undeclareRangeVariable();
  1338. }
  1339. t.setState(Tokenizer.DEFAULT_STATE);
  1340. nextToken();
  1341. QueryModule qenv = (QueryModule)env;
  1342. try {
  1343. qenv.declareFunction(func);
  1344. } catch (XPathException e) {
  1345. grumble(e.getMessage(), e.getErrorCodeLocalPart());
  1346. }
  1347. memoFunction = false;
  1348. }
  1349. /**
  1350. * Parse an updating function declaration (allowed in XQuery Update only)
  1351. */
  1352. protected void parseUpdatingFunctionDeclaration() throws XPathException {
  1353. grumble("Updating functions are allowed only in XQuery Update");
  1354. }
  1355. /**
  1356. * Parse an option declaration.
  1357. * <p>Syntax:<br/>
  1358. * <"declare" "option"> QName "string-literal"
  1359. * </p>
  1360. * <p>On entry, the "declare option" has already been recognized</p>
  1361. *
  1362. * @throws XPathException if a syntax error is found
  1363. */
  1364. private void parseOptionDeclaration() throws XPathException {
  1365. nextToken();
  1366. expect(Token.NAME);
  1367. int varNameCode = makeNameCode(t.currentTokenValue, false);
  1368. String uri = env.getNamePool().getURI(varNameCode);
  1369. if (uri.length() == 0) {
  1370. grumble("The QName identifying an option declaration must be prefixed", "XPST0081");
  1371. return;
  1372. }
  1373. nextToken();
  1374. expect(Token.STRING_LITERAL);
  1375. String value = URILiteral(t.currentTokenValue);
  1376. if (uri.equals(NamespaceConstant.SAXON)) {
  1377. String localName = env.getNamePool().getLocalName(varNameCode);
  1378. if (localName.equals("output")) {
  1379. setOutputProperty(value);
  1380. } else if (localName.equals("default")) {
  1381. defaultValue = setDefaultValue(value);
  1382. } else if (localName.equals("memo-function")) {
  1383. if (value.equals("true")) {
  1384. memoFunction = true;
  1385. } else if (value.equals("false")) {
  1386. memoFunction = false;
  1387. } else {
  1388. warning("Value of saxon:memo-function must be 'true' or 'false'");
  1389. }
  1390. } else if (localName.equals("allow-cycles")) {
  1391. if (value.equals("true")) {
  1392. disableCycleChecks = true;
  1393. } else if (value.equals("false")) {
  1394. disableCycleChecks = false;
  1395. } else {
  1396. warning("Value of saxon:allow-cycles must be 'true' or 'false'");
  1397. }
  1398. } else {
  1399. warning("Unknown Saxon option declaration: " + env.getNamePool().getDisplayName(varNameCode));
  1400. }
  1401. }
  1402. nextToken();
  1403. }
  1404. /**
  1405. * Handle a saxon:output option declaration. Format:
  1406. * declare option saxon:output "indent = yes"
  1407. * @param property a property name=value pair. The name is the name of a serialization
  1408. * property, potentially as a prefixed QName; the value is the value of the property. A warning
  1409. * is output for unrecognized properties or values
  1410. */
  1411. private void setOutputProperty(String property) {
  1412. int equals = property.indexOf("=");
  1413. if (equals < 0) {
  1414. badOutputProperty("no equals sign");
  1415. } else if (equals == 0) {
  1416. badOutputProperty("starts with '=");
  1417. } else if (equals == property.length() - 1) {
  1418. badOutputProperty("ends with '=");
  1419. }
  1420. String keyword = Whitespace.trim(property.substring(0, equals));
  1421. String value = Whitespace.trim(property.substring(equals + 1));
  1422. Properties props = getExecutable().getDefaultOutputProperties();
  1423. try {
  1424. int key = makeNameCode(keyword, false) & NamePool.FP_MASK;
  1425. String lname = env.getNamePool().getLocalName(key);
  1426. String uri = env.getNamePool().getURI(key);
  1427. ResultDocument.setSerializationProperty(props,
  1428. uri, lname,
  1429. value,
  1430. env.getNamespaceResolver(),
  1431. false,
  1432. nameChecker);
  1433. } catch (XPathException e) {
  1434. badOutputProperty(e.getMessage());
  1435. }
  1436. }
  1437. private void badOutputProperty(String s) {
  1438. try {
  1439. warning("Invalid serialization property (" + s + ") - ignored");
  1440. } catch (XPathException staticError) {
  1441. //
  1442. }
  1443. }
  1444. /**
  1445. * Parse the expression (inside a string literal) used to define default values
  1446. * for external variables. This requires instantiating a nested XPath parser.
  1447. * @param exp holds the expression used to define a default value
  1448. * @return the compiled expression that computes the default value
  1449. */
  1450. public Expression setDefaultValue(String exp) {
  1451. try {
  1452. IndependentContext ic = new IndependentContext(env.getConfiguration());
  1453. ic.setNamespaceResolver(env.getNamespaceResolver());
  1454. Expression expr = ExpressionTool.make(exp, ic, 0, Token.EOF, 1, false);
  1455. ItemType contextItemType = Type.ITEM_TYPE;
  1456. ExpressionVisitor visitor = ExpressionVisitor.make(ic);
  1457. expr = visitor.typeCheck(expr, contextItemType);
  1458. expr = visitor.optimize(expr, contextItemType);
  1459. SlotManager stackFrameMap = ic.getStackFrameMap();
  1460. ExpressionTool.allocateSlots(expr, stackFrameMap.getNumberOfVariables(), stackFrameMap);
  1461. return expr;
  1462. } catch (XPathException e) {
  1463. try {
  1464. warning("Invalid expression for default value: " + e.getMessage() + " (ignored)");
  1465. } catch (XPathException staticError) {
  1466. //
  1467. }
  1468. return null;
  1469. }
  1470. }
  1471. /**
  1472. * Parse a FLWOR expression. This replaces the XPath "for" expression.
  1473. * Full syntax:
  1474. * <p/>
  1475. * [41] FLWORExpr ::= (ForClause | LetClause)+
  1476. * WhereClause? OrderByClause?
  1477. * "return" ExprSingle
  1478. * [42] ForClause ::= <"for" "$"> VarName TypeDeclaration? PositionalVar? "in" ExprSingle
  1479. * ("," "$" VarName TypeDeclaration? PositionalVar? "in" ExprSingle)*
  1480. * [43] PositionalVar ::= "at" "$" VarName
  1481. * [44] LetClause ::= <"let" "$"> VarName TypeDeclaration? ":=" ExprSingle
  1482. * ("," "$" VarName TypeDeclaration? ":=" ExprSingle)*
  1483. * [45] WhereClause ::= "where" Expr
  1484. * [46] OrderByClause ::= (<"order" "by"> | <"stable" "order" "by">) OrderSpecList
  1485. * [47] OrderSpecList ::= OrderSpec ("," OrderSpec)*
  1486. * [48] OrderSpec ::= ExprSingle OrderModifier
  1487. * [49] OrderModifier ::= ("ascending" | "descending")?
  1488. * (<"empty" "greatest"> | <"empty" "least">)?
  1489. * ("collation" StringLiteral)?
  1490. * </p>
  1491. *
  1492. * @return the resulting subexpression
  1493. * @throws XPathException if any error is encountered
  1494. */
  1495. protected Expression parseForExpression() throws XPathException {
  1496. int offset = t.currentTokenStartOffset;
  1497. Expression whereCondition = null;
  1498. int whereOffset = -1;
  1499. //boolean stableOrder = false;
  1500. List clauseList = new ArrayList(4);
  1501. while (true) {
  1502. if (t.currentToken == Token.FOR) {
  1503. parseForClause(clauseList);
  1504. } else if (t.currentToken == Token.LET) {
  1505. parseLetClause(clauseList);
  1506. } else {
  1507. break;
  1508. }
  1509. }
  1510. if (t.currentToken == Token.WHERE || isKeyword("where")) {
  1511. whereOffset = t.currentTokenStartOffset;
  1512. nextToken();
  1513. whereCondition = parseExprSingle();
  1514. }
  1515. int orderByOffset = t.currentTokenStartOffset;
  1516. if (isKeyword("stable")) {
  1517. // we read the "stable" keyword but ignore it; Saxon ordering is always stable
  1518. nextToken();
  1519. if (!isKeyword("order")) {
  1520. grumble("'stable' must be followed by 'order by'");
  1521. }
  1522. }
  1523. List sortSpecList = null;
  1524. if (isKeyword("order")) {
  1525. t.setState(Tokenizer.BARE_NAME_STATE);
  1526. nextToken();
  1527. if (!isKeyword("by")) {
  1528. grumble("'order' must be followed by 'by'");
  1529. }
  1530. t.setState(Tokenizer.DEFAULT_STATE);
  1531. nextToken();
  1532. sortSpecList = parseSortDefinition();
  1533. }
  1534. int returnOffset = t.currentTokenStartOffset;
  1535. expect(Token.RETURN);
  1536. t.setState(Tokenizer.DEFAULT_STATE);
  1537. nextToken();
  1538. Expression action = parseExprSingle();
  1539. action = makeTracer(returnOffset, action, Location.RETURN_EXPRESSION, null);
  1540. // If there is an order by clause, we modify the "return" expression so that it
  1541. // returns a tuple containing the actual return value, plus the value of
  1542. // each of the sort keys. We then wrap the entire FLWR expression inside a
  1543. // TupleSorter that sorts the stream of tuples according to the sort keys,
  1544. // discarding the sort keys and returning only the true result. The tuple
  1545. // is implemented as a Java object wrapped inside an ObjectValue, which is
  1546. // a general-purpose wrapper for objects that don't fit in the XPath type system.
  1547. if (sortSpecList != null) {
  1548. TupleExpression exp = new TupleExpression(1 + sortSpecList.size());
  1549. setLocation(exp);
  1550. exp.setExpression(0, action);
  1551. for (int i = 0; i < sortSpecList.size(); i++) {
  1552. try {
  1553. RoleLocator role = new RoleLocator(RoleLocator.ORDER_BY, "FLWR", i);
  1554. //role.setSourceLocator(makeLocator());
  1555. Expression sk =
  1556. TypeChecker.staticTypeCheck(((SortSpec)sortSpecList.get(i)).sortKey,
  1557. SequenceType.OPTIONAL_ATOMIC,
  1558. false,
  1559. role, ExpressionVisitor.make(env));
  1560. exp.setExpression(i + 1, sk);
  1561. } catch (XPathException err) {
  1562. grumble(err.getMessage());
  1563. }
  1564. }
  1565. action = exp;
  1566. }
  1567. // if there is a "where" condition, we implement this by wrapping an if/then/else
  1568. // around the "return" expression. No clever optimization yet!
  1569. if (whereCondition != null) {
  1570. action = Choose.makeConditional(whereCondition, action);
  1571. action = makeTracer(whereOffset, action, Location.WHERE_CLAUSE, null);
  1572. setLocation(action);
  1573. }
  1574. for (int i = clauseList.size() - 1; i >= 0; i--) {
  1575. Object clause = clauseList.get(i);
  1576. if (clause instanceof ExpressionParser.ForClause) {
  1577. ExpressionParser.ForClause fc = (ExpressionParser.ForClause)clause;
  1578. ForExpression exp = (ForExpression)fc.rangeVariable;
  1579. exp.setPositionVariable(fc.positionVariable);
  1580. exp.setLocationId(env.getLocationMap().allocateLocationId(env.getSystemId(), t.getLineNumber(fc.offset)));
  1581. exp.setSequence(fc.sequence);
  1582. exp.setAction(action);
  1583. action = makeTracer(fc.offset, exp, Location.FOR_EXPRESSION, fc.rangeVariable.getVariableQName());
  1584. } else {
  1585. LetClause lc = (LetClause)clause;
  1586. LetExpression exp = lc.variable;
  1587. exp.setLocationId(env.getLocationMap().allocateLocationId(env.getSystemId(), t.getLineNumber(lc.offset)));
  1588. //exp.setSequence(lc.value);
  1589. exp.setAction(action);
  1590. action = makeTracer(lc.offset, exp, Location.LET_EXPRESSION, lc.variable.getVariableQName());
  1591. }
  1592. }
  1593. // Now wrap the whole expression in a TupleSorter if there is a sort specification
  1594. if (sortSpecList != null) {
  1595. SortKeyDefinition[] keys = new SortKeyDefinition[sortSpecList.size()];
  1596. for (int i = 0; i < keys.length; i++) {
  1597. SortSpec spec = (SortSpec)sortSpecList.get(i);
  1598. SortKeyDefinition key = new SortKeyDefinition();
  1599. key.setSortKey(((SortSpec) sortSpecList.get(i)).sortKey);
  1600. key.setOrder(new StringLiteral(spec.ascending ? "ascending" : "descending"));
  1601. key.setEmptyLeast(spec.emptyLeast);
  1602. if (spec.collation != null) {
  1603. final StringCollator comparator = env.getCollation(spec.collation);
  1604. if (comparator == null) {
  1605. grumble("Unknown collation '" + spec.collation + '\'', "XQST0076");
  1606. }
  1607. key.setCollation(comparator);
  1608. }
  1609. keys[i] = key;
  1610. }
  1611. TupleSorter sorter = new TupleSorter(action, keys);
  1612. setLocation(sorter);
  1613. action = makeTracer(orderByOffset, sorter, Location.ORDER_BY_CLAUSE, null);
  1614. }
  1615. // undeclare all the range variables
  1616. for (int i = clauseList.size() - 1; i >= 0; i--) {
  1617. Object clause = clauseList.get(i);
  1618. if ((clause instanceof ExpressionParser.ForClause) &&
  1619. ((ExpressionParser.ForClause)clause).positionVariable != null) {
  1620. // undeclare the "at" variable if it was declared
  1621. undeclareRangeVariable();
  1622. }
  1623. // undeclare the primary variable
  1624. undeclareRangeVariable();
  1625. }
  1626. setLocation(action, offset);
  1627. return action;
  1628. }
  1629. /**
  1630. * Make a LetExpression. This returns an ordinary LetExpression if tracing is off, and an EagerLetExpression
  1631. * if tracing is on. This is so that trace events occur in an order that the user can follow.
  1632. * @return the constructed "let" expression
  1633. */
  1634. private LetExpression makeLetExpression() {
  1635. if (env.getConfiguration().isCompileWithTracing()) {
  1636. return new EagerLetExpression();
  1637. } else {
  1638. return new LetExpression();
  1639. }
  1640. }
  1641. /**
  1642. * Parse a ForClause.
  1643. * <p/>
  1644. * [42] ForClause ::= <"for" "$"> VarName TypeDeclaration? PositionalVar? "in" ExprSingle
  1645. * ("," "$" VarName TypeDeclaration? PositionalVar? "in" ExprSingle)*
  1646. * </p>
  1647. *
  1648. * @param clauseList - the components of the parsed ForClause are appended to the
  1649. * supplied list
  1650. * @throws XPathException
  1651. */
  1652. private void parseForClause(List clauseList) throws XPathException {
  1653. boolean first = true;
  1654. do {
  1655. ExpressionParser.ForClause clause = new ExpressionParser.ForClause();
  1656. if (first) {
  1657. clause.offset = t.currentTokenStartOffset;
  1658. }
  1659. clauseList.add(clause);
  1660. nextToken();
  1661. if (first) {
  1662. first = false;
  1663. } else {
  1664. clause.offset = t.currentTokenStartOffset;
  1665. }
  1666. expect(Token.DOLLAR);
  1667. nextToken();
  1668. expect(Token.NAME);
  1669. String var = t.currentTokenValue;
  1670. ForExpression v = new ForExpression();
  1671. StructuredQName varQName = makeStructuredQName(var, false);
  1672. v.setVariableQName(varQName);
  1673. v.setRequiredType(SequenceType.SINGLE_ITEM);
  1674. clause.rangeVariable = v;
  1675. nextToken();
  1676. if (t.currentToken == Token.AS /*isKeyword("as")*/) {
  1677. nextToken();
  1678. SequenceType type = parseSequenceType();
  1679. if (type.getCardinality() != StaticProperty.EXACTLY_ONE) {
  1680. warning("Occurrence indicator on singleton range variable has no effect");
  1681. type = SequenceType.makeSequenceType(type.getPrimaryType(), StaticProperty.EXACTLY_ONE);
  1682. }
  1683. v.setRequiredType(type);
  1684. }
  1685. clause.positionVariable = null;
  1686. if (isKeyword("at")) {
  1687. nextToken();
  1688. expect(Token.DOLLAR);
  1689. nextToken();
  1690. expect(Token.NAME);
  1691. PositionVariable pos = new PositionVariable();
  1692. StructuredQName posQName = makeStructuredQName(t.currentTokenValue, false);
  1693. if (!scanOnly && posQName.equals(varQName)) {
  1694. grumble("The two variables declared in a single 'for' clause must have different names", "XQST0089");
  1695. }
  1696. pos.setVariableQName(posQName);
  1697. clause.positionVariable = pos;
  1698. nextToken();
  1699. }
  1700. expect(Token.IN);
  1701. nextToken();
  1702. clause.sequence = parseExprSingle();
  1703. declareRangeVariable(clause.rangeVariable);
  1704. if (clause.positionVariable != null) {
  1705. declareRangeVariable(clause.positionVariable);
  1706. }
  1707. } while (t.currentToken == Token.COMMA);
  1708. }
  1709. /**
  1710. * Parse a LetClause.
  1711. * <p/>
  1712. * [44] LetClause ::= <"let" "$"> VarName TypeDeclaration? ":=" ExprSingle
  1713. * ("," "$" VarName TypeDeclaration? ":=" ExprSingle)*
  1714. * </p>
  1715. *
  1716. * @param clauseList - the components of the parsed LetClause are appended to the
  1717. * supplied list
  1718. * @throws XPathException
  1719. */
  1720. private void parseLetClause(List clauseList) throws XPathException {
  1721. boolean first = true;
  1722. do {
  1723. LetClause clause = new LetClause();
  1724. if (first) {
  1725. clause.offset = t.currentTokenStartOffset;
  1726. }
  1727. clauseList.add(clause);
  1728. nextToken();
  1729. if (first) {
  1730. first = false;
  1731. } else {
  1732. clause.offset = t.currentTokenStartOffset;
  1733. }
  1734. expect(Token.DOLLAR);
  1735. nextToken();
  1736. expect(Token.NAME);
  1737. String var = t.currentTokenValue;
  1738. LetExpression v = new LetExpression();
  1739. StructuredQName varQName = makeStructuredQName(var, false);
  1740. v.setRequiredType(SequenceType.ANY_SEQUENCE);
  1741. v.setVariableQName(varQName);
  1742. clause.variable = v;
  1743. nextToken();
  1744. if (t.currentToken == Token.AS) {
  1745. nextToken();
  1746. v.setRequiredType(parseSequenceType());
  1747. }
  1748. expect(Token.ASSIGN);
  1749. nextToken();
  1750. v.setSequence(parseExprSingle());
  1751. declareRangeVariable(v);
  1752. } while (t.currentToken == Token.COMMA);
  1753. }
  1754. /**
  1755. * Make a string-join expression that concatenates the string-values of items in
  1756. * a sequence with intervening spaces. This may be simplified later as a result
  1757. * of type-checking.
  1758. * @param exp the base expression, evaluating to a sequence
  1759. * @param env the static context
  1760. * @return a call on string-join to create a string containing the
  1761. * representations of the items in the sequence separated by spaces.
  1762. */
  1763. public static Expression makeStringJoin(Expression exp, StaticContext env) {
  1764. exp = new Atomizer(exp, env.getConfiguration());
  1765. final TypeHierarchy th = env.getConfiguration().getTypeHierarchy();
  1766. ItemType t = exp.getItemType(th);
  1767. if (!t.equals(BuiltInAtomicType.STRING) && !t.equals(BuiltInAtomicType.UNTYPED_ATOMIC)) {
  1768. exp = new AtomicSequenceConverter(exp, BuiltInAtomicType.STRING);
  1769. }
  1770. StringJoin fn = (StringJoin)SystemFunction.makeSystemFunction(
  1771. "string-join", new Expression[]{exp, new StringLiteral(StringValue.SINGLE_SPACE)});
  1772. ExpressionTool.copyLocationInfo(exp, fn);
  1773. return fn;
  1774. }
  1775. private static class LetClause {
  1776. public LetExpression variable;
  1777. public int offset;
  1778. }
  1779. /**
  1780. * Parse the "order by" clause.
  1781. * [46] OrderByClause ::= (<"order" "by"> | <"stable" "order" "by">) OrderSpecList
  1782. * [47] OrderSpecList ::= OrderSpec ("," OrderSpec)*
  1783. * [48] OrderSpec ::= ExprSingle OrderModifier
  1784. * [49] OrderModifier ::= ("ascending" | "descending")?
  1785. * (<"empty" "greatest"> | <"empty" "least">)?
  1786. * ("collation" StringLiteral)?
  1787. *
  1788. * @return a list of sort specifications (SortSpec), one per sort key
  1789. * @throws XPathException
  1790. */
  1791. private List parseSortDefinition() throws XPathException {
  1792. List sortSpecList = new ArrayList(5);
  1793. while (true) {
  1794. SortSpec sortSpec = new SortSpec();
  1795. sortSpec.sortKey = parseExprSingle();
  1796. sortSpec.ascending = true;
  1797. sortSpec.emptyLeast = ((QueryModule)env).isEmptyLeast();
  1798. sortSpec.collation = env.getDefaultCollationName();
  1799. //t.setState(t.BARE_NAME_STATE);
  1800. if (isKeyword("ascending")) {
  1801. nextToken();
  1802. } else if (isKeyword("descending")) {
  1803. sortSpec.ascending = false;
  1804. nextToken();
  1805. }
  1806. if (isKeyword("empty")) {
  1807. nextToken();
  1808. if (isKeyword("greatest")) {
  1809. sortSpec.emptyLeast = false;
  1810. nextToken();
  1811. } else if (isKeyword("least")) {
  1812. sortSpec.emptyLeast = true;
  1813. nextToken();
  1814. } else {
  1815. grumble("'empty' must be followed by 'greatest' or 'least'");
  1816. }
  1817. }
  1818. if (isKeyword("collation")) {
  1819. nextToken();
  1820. expect(Token.STRING_LITERAL);
  1821. String collationName = URILiteral(t.currentTokenValue);
  1822. URI collationURI;
  1823. try {
  1824. collationURI = new URI(collationName);
  1825. if (!collationURI.isAbsolute()) {
  1826. URI base = new URI(env.getBaseURI());
  1827. collationURI = base.resolve(collationURI);
  1828. collationName = collationURI.toString();
  1829. }
  1830. } catch (URISyntaxException err) {
  1831. grumble("Collation name '" + collationName + "' is not a valid URI", "XQST0046");
  1832. collationName = NamespaceConstant.CODEPOINT_COLLATION_URI;
  1833. }
  1834. sortSpec.collation = collationName;
  1835. nextToken();
  1836. }
  1837. sortSpecList.add(sortSpec);
  1838. if (t.currentToken == Token.COMMA) {
  1839. nextToken();
  1840. } else {
  1841. break;
  1842. }
  1843. }
  1844. return sortSpecList;
  1845. }
  1846. private static class SortSpec {
  1847. public Expression sortKey;
  1848. public boolean ascending;
  1849. public boolean emptyLeast;
  1850. public String collation;
  1851. }
  1852. /**
  1853. * Parse a Typeswitch Expression.
  1854. * This construct is XQuery-only.
  1855. * TypeswitchExpr ::=
  1856. * "typeswitch" "(" Expr ")"
  1857. * CaseClause+
  1858. * "default" ("$" VarName)? "return" ExprSingle
  1859. * CaseClause ::=
  1860. * "case" ("$" VarName "as")? SequenceType "return" ExprSingle
  1861. */
  1862. protected Expression parseTypeswitchExpression() throws XPathException {
  1863. // On entry, the "(" has already been read
  1864. int offset = t.currentTokenStartOffset;
  1865. nextToken();
  1866. Expression operand = parseExpression();
  1867. List types = new ArrayList(10);
  1868. List actions = new ArrayList(10);
  1869. expect(Token.RPAR);
  1870. nextToken();
  1871. // The code generated takes the form:
  1872. // let $zzz := operand return
  1873. // if ($zzz instance of t1) then action1
  1874. // else if ($zzz instance of t2) then action2
  1875. // else default-action
  1876. //
  1877. // If a variable is declared in a case clause or default clause,
  1878. // then "action-n" takes the form
  1879. // let $v as type := $zzz return action-n
  1880. // we were generating "let $v as type := $zzz return action-n" but this gives a compile time error if
  1881. // there's a case clause that specifies an impossible type.
  1882. LetExpression outerLet = makeLetExpression();
  1883. outerLet.setRequiredType(SequenceType.ANY_SEQUENCE);
  1884. outerLet.setVariableQName(new StructuredQName("zz", NamespaceConstant.SAXON, "zz_typeswitchVar"));
  1885. outerLet.setSequence(operand);
  1886. while (t.currentToken == Token.CASE) {
  1887. int caseOffset = t.currentTokenStartOffset;
  1888. SequenceType type;
  1889. Expression action;
  1890. nextToken();
  1891. if (t.currentToken == Token.DOLLAR) {
  1892. nextToken();
  1893. expect(Token.NAME);
  1894. final String var = t.currentTokenValue;
  1895. final StructuredQName varQName = makeStructuredQName(var, false);
  1896. nextToken();
  1897. expect(Token.AS);
  1898. // expect(Token.NAME);
  1899. // if (!"as".equals(t.currentTokenValue)) {
  1900. // grumble("After 'case $" + var + "', expected 'as'");
  1901. // }
  1902. nextToken();
  1903. type = parseSequenceType();
  1904. action = makeTracer(caseOffset,
  1905. parseTypeswitchReturnClause(varQName, outerLet),
  1906. Location.CASE_EXPRESSION,
  1907. varQName);
  1908. if (action instanceof TraceExpression) {
  1909. ((TraceExpression)action).setProperty("type", type.toString());
  1910. }
  1911. } else {
  1912. type = parseSequenceType();
  1913. t.treatCurrentAsOperator();
  1914. expect(Token.RETURN);
  1915. nextToken();
  1916. action = makeTracer(caseOffset, parseExprSingle(), Location.CASE_EXPRESSION, null);
  1917. if (action instanceof TraceExpression) {
  1918. ((TraceExpression)action).setProperty("type", type.toString());
  1919. }
  1920. }
  1921. types.add(type);
  1922. actions.add(action);
  1923. }
  1924. if (types.isEmpty()) {
  1925. grumble("At least one case clause is required in a typeswitch");
  1926. }
  1927. expect(Token.DEFAULT);
  1928. final int defaultOffset = t.currentTokenStartOffset;
  1929. nextToken();
  1930. Expression defaultAction;
  1931. if (t.currentToken == Token.DOLLAR) {
  1932. nextToken();
  1933. expect(Token.NAME);
  1934. final String var = t.currentTokenValue;
  1935. final StructuredQName varQName = makeStructuredQName(var, false);
  1936. nextToken();
  1937. defaultAction = makeTracer(defaultOffset,
  1938. parseTypeswitchReturnClause(varQName, outerLet),
  1939. Location.DEFAULT_EXPRESSION,
  1940. varQName);
  1941. } else {
  1942. t.treatCurrentAsOperator();
  1943. expect(Token.RETURN);
  1944. nextToken();
  1945. defaultAction = makeTracer(defaultOffset, parseExprSingle(), Location.DEFAULT_EXPRESSION, null);
  1946. }
  1947. Expression lastAction = defaultAction;
  1948. // Note, the ragged "choose" later gets flattened into a single-level choose, saving stack space
  1949. for (int i = types.size() - 1; i >= 0; i--) {
  1950. final LocalVariableReference var = new LocalVariableReference(outerLet);
  1951. setLocation(var);
  1952. final InstanceOfExpression ioe =
  1953. new InstanceOfExpression(var, (SequenceType)types.get(i));
  1954. setLocation(ioe);
  1955. final Expression ife =
  1956. Choose.makeConditional(ioe, (Expression)actions.get(i), lastAction);
  1957. setLocation(ife);
  1958. lastAction = ife;
  1959. }
  1960. outerLet.setAction(lastAction);
  1961. return makeTracer(offset, outerLet, Location.TYPESWITCH_EXPRESSION, null);
  1962. }
  1963. private Expression parseTypeswitchReturnClause(StructuredQName varQName, LetExpression outerLet)
  1964. throws XPathException {
  1965. Expression action;
  1966. t.treatCurrentAsOperator();
  1967. expect(Token.RETURN);
  1968. nextToken();
  1969. LetExpression innerLet = makeLetExpression();
  1970. innerLet.setRequiredType(SequenceType.ANY_SEQUENCE);
  1971. innerLet.setVariableQName(varQName);
  1972. innerLet.setSequence(new LocalVariableReference(outerLet));
  1973. declareRangeVariable(innerLet);
  1974. action = parseExprSingle();
  1975. undeclareRangeVariable();
  1976. innerLet.setAction(action);
  1977. return innerLet;
  1978. // if (Literal.isEmptySequence(action)) {
  1979. // // The purpose of simplifying this now is that () is allowed in a branch even in XQuery Update when
  1980. // // other branches of the typeswitch are updating.
  1981. // return action;
  1982. // } else {
  1983. // return innerLet;
  1984. // }
  1985. }
  1986. /**
  1987. * Parse a Validate Expression.
  1988. * This construct is XQuery-only. The syntax allows:
  1989. * validate mode? { Expr }
  1990. * mode ::= "strict" | "lax"
  1991. */
  1992. protected Expression parseValidateExpression() throws XPathException {
  1993. if (!env.getConfiguration().isSchemaAware(Configuration.XQUERY)) {
  1994. grumble("To use a validate expression, you need the schema-aware processor from http://www.saxonica.com/");
  1995. }
  1996. int offset = t.currentTokenStartOffset;
  1997. int mode = Validation.STRICT;
  1998. boolean foundCurly = false;
  1999. switch (t.currentToken) {
  2000. case Token.VALIDATE_STRICT:
  2001. mode = Validation.STRICT;
  2002. nextToken();
  2003. break;
  2004. case Token.VALIDATE_LAX:
  2005. mode = Validation.LAX;
  2006. nextToken();
  2007. break;
  2008. case Token.KEYWORD_CURLY:
  2009. if (t.currentTokenValue.equals("validate")) {
  2010. mode = Validation.STRICT;
  2011. } else {
  2012. throw new AssertionError("shouldn't be parsing a validate expression");
  2013. }
  2014. foundCurly = true;
  2015. }
  2016. if (!foundCurly) {
  2017. expect(Token.LCURLY);
  2018. }
  2019. nextToken();
  2020. Expression exp = parseExpression();
  2021. if (exp instanceof ElementCreator) {
  2022. ((ElementCreator)exp).setValidationMode(mode);
  2023. } else if (exp instanceof DocumentInstr) {
  2024. ((DocumentInstr)exp).setValidationMode(mode);
  2025. } else {
  2026. // the expression must return a single element or document node. The type-
  2027. // checking machinery can't handle a union type, so we just check that it's
  2028. // a node for now. Because we are reusing XSLT copy-of code, we need
  2029. // an ad-hoc check that the node is of the right kind.
  2030. try {
  2031. RoleLocator role = new RoleLocator(RoleLocator.TYPE_OP, "validate", 0);
  2032. role.setErrorCode("XQTY0030");
  2033. //role.setSourceLocator(makeLocator());
  2034. exp = TypeChecker.staticTypeCheck(exp,
  2035. SequenceType.SINGLE_NODE,
  2036. false,
  2037. role, ExpressionVisitor.make(env));
  2038. } catch (XPathException err) {
  2039. grumble(err.getMessage(), err.getErrorCodeLocalPart());
  2040. }
  2041. exp = new CopyOf(exp, true, mode, null, true);
  2042. setLocation(exp);
  2043. ((CopyOf)exp).setRequireDocumentOrElement(true);
  2044. }
  2045. expect(Token.RCURLY);
  2046. t.lookAhead(); // always done manually after an RCURLY
  2047. nextToken();
  2048. return makeTracer(offset, exp, Location.VALIDATE_EXPRESSION, null);
  2049. }
  2050. /**
  2051. * Parse an Extension Expression.
  2052. * Syntax: "(#" QName arbitrary-text "#)")+ "{" expr? "}"
  2053. */
  2054. protected Expression parseExtensionExpression() throws XPathException {
  2055. SchemaType requiredType = null;
  2056. CharSequence trimmed = Whitespace.removeLeadingWhitespace(t.currentTokenValue);
  2057. int c = 0;
  2058. int len = trimmed.length();
  2059. while (c < len && " \t\r\n".indexOf(trimmed.charAt(c)) < 0) {
  2060. c++;
  2061. }
  2062. String qname = trimmed.subSequence(0, c).toString();
  2063. String pragmaContents = "";
  2064. while (c < len && " \t\r\n".indexOf(trimmed.charAt(c)) >= 0) {
  2065. c++;
  2066. }
  2067. if (c < len) {
  2068. pragmaContents = trimmed.subSequence(c, len).toString();
  2069. }
  2070. boolean validateType = false;
  2071. boolean streaming = false;
  2072. if (!nameChecker.isQName(qname)) {
  2073. grumble("First token in pragma must be a valid QName, terminated by whitespace");
  2074. } else {
  2075. int nameCode = makeNameCode(qname, false);
  2076. String uri = env.getNamePool().getURI(nameCode);
  2077. if (uri.equals(NamespaceConstant.SAXON)) {
  2078. String localName = env.getNamePool().getLocalName(nameCode);
  2079. if (localName.equals("validate-type")) {
  2080. if (!env.getConfiguration().isSchemaAware(Configuration.XQUERY)) {
  2081. grumble("To use saxon:validate-type, " +
  2082. "you need the Saxon-SA processor from http://www.saxonica.com/");
  2083. }
  2084. String typeName = Whitespace.trim(pragmaContents);
  2085. if (!nameChecker.isQName(typeName)) {
  2086. grumble("Schema type expected in saxon:validate-type pragma");
  2087. }
  2088. int typeCode = makeNameCode(typeName, true);
  2089. requiredType = env.getConfiguration().getSchemaType(typeCode & NamePool.FP_MASK);
  2090. if (requiredType == null) {
  2091. grumble("Unknown schema type " + typeName);
  2092. }
  2093. validateType = true;
  2094. } else if (localName.equals("stream")) {
  2095. if (!env.getConfiguration().isSchemaAware(Configuration.XQUERY)) {
  2096. grumble("To use saxon:stream, " +
  2097. "you need the Saxon-SA processor from http://www.saxonica.com/");
  2098. }
  2099. streaming = true;
  2100. } else {
  2101. grumble("Unrecognized Saxon pragma " + qname);
  2102. }
  2103. } else if (uri.length() == 0) {
  2104. grumble("The QName identifying an option declaration must be prefixed", "XPST0081");
  2105. }
  2106. }
  2107. nextToken();
  2108. Expression expr;
  2109. if (t.currentToken == Token.PRAGMA) {
  2110. expr = parseExtensionExpression();
  2111. } else {
  2112. expect(Token.LCURLY);
  2113. nextToken();
  2114. if (t.currentToken == Token.RCURLY) {
  2115. t.lookAhead(); // always done manually after an RCURLY
  2116. nextToken();
  2117. grumble("Unrecognized pragma, with no fallback expression", "XQST0079");
  2118. }
  2119. expr = parseExpression();
  2120. expect(Token.RCURLY);
  2121. t.lookAhead(); // always done manually after an RCURLY
  2122. nextToken();
  2123. }
  2124. if (validateType) {
  2125. if (expr instanceof ElementCreator) {
  2126. ((ElementCreator)expr).setSchemaType(requiredType);
  2127. ((ElementCreator)expr).setValidationMode(Validation.BY_TYPE);
  2128. return expr;
  2129. } else if (expr instanceof DocumentInstr) {
  2130. ((DocumentInstr)expr).setSchemaType(requiredType);
  2131. ((DocumentInstr)expr).setValidationMode(Validation.BY_TYPE);
  2132. return expr;
  2133. } else if (expr instanceof AttributeCreator) {
  2134. if (!(requiredType instanceof SimpleType)) {
  2135. grumble("The type used for validating an attribute must be a simple type");
  2136. }
  2137. ((AttributeCreator)expr).setSchemaType((SimpleType)requiredType);
  2138. ((AttributeCreator)expr).setValidationAction(Validation.BY_TYPE);
  2139. return expr;
  2140. } else {
  2141. CopyOf copy = new CopyOf(expr, true, Validation.BY_TYPE, requiredType, true);
  2142. copy.setLocationId(env.getLocationMap().allocateLocationId(env.getSystemId(), t.getLineNumber()));
  2143. return copy;
  2144. }
  2145. } else if (streaming) {
  2146. CopyOf copy = new CopyOf(expr, true, Validation.PRESERVE, null, true);
  2147. copy.setLocationId(env.getLocationMap().allocateLocationId(env.getSystemId(), t.getLineNumber()));
  2148. copy.setReadOnce(true);
  2149. return copy;
  2150. } else {
  2151. return expr;
  2152. }
  2153. }
  2154. /**
  2155. * Parse a node constructor. This is allowed only in XQuery. This method handles
  2156. * both the XML-like "direct" constructors, and the XQuery-based "computed"
  2157. * constructors.
  2158. *
  2159. * @return an Expression for evaluating the parsed constructor
  2160. * @throws XPathException in the event of a syntax error.
  2161. */
  2162. protected Expression parseConstructor() throws XPathException {
  2163. int offset = t.currentTokenStartOffset;
  2164. switch (t.currentToken) {
  2165. case Token.TAG:
  2166. Expression tag = parsePseudoXML(false);
  2167. lookAhead();
  2168. t.setState(Tokenizer.OPERATOR_STATE);
  2169. nextToken();
  2170. return tag;
  2171. case Token.KEYWORD_CURLY:
  2172. String nodeKind = t.currentTokenValue;
  2173. if (nodeKind.equals("validate")) {
  2174. return parseValidateExpression();
  2175. } else if (nodeKind.equals("ordered") || nodeKind.equals("unordered")) {
  2176. // these are currently no-ops in Saxon
  2177. nextToken();
  2178. Expression content = parseExpression();
  2179. expect(Token.RCURLY);
  2180. lookAhead(); // must be done manually after an RCURLY
  2181. nextToken();
  2182. return content;
  2183. } else if (nodeKind.equals("document")) {
  2184. nextToken();
  2185. Expression content = parseExpression();
  2186. expect(Token.RCURLY);
  2187. lookAhead(); // must be done manually after an RCURLY
  2188. nextToken();
  2189. DocumentInstr doc = new DocumentInstr(false, null, env.getBaseURI());
  2190. if (!((QueryModule)env).isPreserveNamespaces()) {
  2191. content = new CopyOf(content, false, Validation.PRESERVE, null, true);
  2192. }
  2193. doc.setValidationMode(((QueryModule)env).getConstructionMode());
  2194. doc.setContentExpression(content);
  2195. setLocation(doc, offset);
  2196. return doc;
  2197. } else if ("element".equals(nodeKind)) {
  2198. nextToken();
  2199. // get the expression that yields the element name
  2200. Expression name = parseExpression();
  2201. expect(Token.RCURLY);
  2202. lookAhead(); // must be done manually after an RCURLY
  2203. nextToken();
  2204. expect(Token.LCURLY);
  2205. t.setState(Tokenizer.DEFAULT_STATE);
  2206. nextToken();
  2207. Expression content = null;
  2208. if (t.currentToken != Token.RCURLY) {
  2209. // get the expression that yields the element content
  2210. content = parseExpression();
  2211. // if the child expression creates another element,
  2212. // suppress validation, as the parent already takes care of it
  2213. if (content instanceof ElementCreator) {
  2214. ((ElementCreator)content).setValidationMode(Validation.PRESERVE);
  2215. }
  2216. expect(Token.RCURLY);
  2217. }
  2218. lookAhead(); // done manually after an RCURLY
  2219. nextToken();
  2220. Instruction inst;
  2221. if (name instanceof Literal) {
  2222. Value vName = ((Literal)name).getValue();
  2223. // if element name is supplied as a literal, treat it like a direct element constructor
  2224. int nameCode;
  2225. if (vName instanceof StringValue && !(vName instanceof AnyURIValue)) {
  2226. String lex = vName.getStringValue();
  2227. try {
  2228. nameCode = makeNameCodeSilently(lex, true);
  2229. } catch (XPathException staticError) {
  2230. String code = staticError.getErrorCodeLocalPart();
  2231. if ("XPST0008".equals(code) || "XPST0081".equals(code)) {
  2232. staticError.setErrorCode("XQDY0074");
  2233. }
  2234. staticError.setLocator(makeLocator());
  2235. throw staticError;
  2236. } catch (QNameException qerr) {
  2237. XPathException e = new XPathException("Invalid QName in element constructor: " + lex);
  2238. e.setErrorCode("XQDY0074");
  2239. e.setLocator(makeLocator());
  2240. throw e;
  2241. }
  2242. } else if (vName instanceof QualifiedNameValue) {
  2243. String uri = ((QualifiedNameValue)vName).getNamespaceURI();
  2244. nameCode = env.getNamePool().allocate("",
  2245. (uri == null ? "" : uri),
  2246. ((QualifiedNameValue)vName).getLocalName());
  2247. } else {
  2248. grumble("Element name must be either a string or a QName", "XPTY0004");
  2249. return null;
  2250. }
  2251. inst = new FixedElement(nameCode,
  2252. ((QueryModule)env).getActiveNamespaceCodes(),
  2253. ((QueryModule)env).isInheritNamespaces(),
  2254. null,
  2255. ((QueryModule)env).getConstructionMode());
  2256. ((FixedElement)inst).setBaseURI(env.getBaseURI());
  2257. if (content == null) {
  2258. content = new Literal(EmptySequence.getInstance());
  2259. }
  2260. ((FixedElement)inst).setContentExpression(content);
  2261. setLocation(inst, offset);
  2262. //makeContentConstructor(content, (InstructionWithChildren) inst, offset);
  2263. return makeTracer(offset, inst, Location.LITERAL_RESULT_ELEMENT,
  2264. new StructuredQName(env.getNamePool(), nameCode));
  2265. } else {
  2266. // it really is a computed element constructor: save the namespace context
  2267. NamespaceResolver ns = new NamespaceResolverForElements(
  2268. env.getNamespaceResolver(),
  2269. env.getDefaultElementNamespace());
  2270. inst = new ComputedElement(name, null, ns, null,
  2271. ((QueryModule)env).getConstructionMode(),
  2272. ((QueryModule)env).isInheritNamespaces(),
  2273. true);
  2274. setLocation(inst);
  2275. if (content == null) {
  2276. content = new Literal(EmptySequence.getInstance());
  2277. }
  2278. ((ComputedElement)inst).setContentExpression(content);
  2279. setLocation(inst, offset);
  2280. //makeContentConstructor(content, (InstructionWithChildren) inst, offset);
  2281. return makeTracer(offset, inst, StandardNames.XSL_ELEMENT, null);
  2282. }
  2283. } else if ("attribute".equals(nodeKind)) {
  2284. nextToken();
  2285. Expression name = parseExpression();
  2286. expect(Token.RCURLY);
  2287. lookAhead(); // must be done manually after an RCURLY
  2288. nextToken();
  2289. expect(Token.LCURLY);
  2290. t.setState(Tokenizer.DEFAULT_STATE);
  2291. nextToken();
  2292. Expression content = null;
  2293. if (t.currentToken != Token.RCURLY) {
  2294. content = parseExpression();
  2295. expect(Token.RCURLY);
  2296. }
  2297. lookAhead(); // after an RCURLY
  2298. nextToken();
  2299. if (name instanceof Literal) {
  2300. Value vName = ((Literal)name).getValue();
  2301. if (vName instanceof StringValue && !(vName instanceof AnyURIValue)) {
  2302. String lex = vName.getStringValue();
  2303. if (lex.equals("xmlns") || lex.startsWith("xmlns:")) {
  2304. grumble("Cannot create a namespace using an attribute constructor", "XQDY0044");
  2305. }
  2306. int nameCode;
  2307. // prevalidate the name before calling makeNameCode, because we want different
  2308. // error behaviour
  2309. // try {
  2310. // nameChecker.getQNameParts(lex);
  2311. // } catch (QNameException err) {
  2312. // XPathException e = new XPathException("Invalid QName in attribute constructor: " + lex);
  2313. // e.setErrorCode("XQDY0074");
  2314. // return new ErrorExpression(e);
  2315. // }
  2316. try {
  2317. nameCode = makeNameCodeSilently(lex, false);
  2318. } catch (XPathException staticError) {
  2319. String code = staticError.getErrorCodeLocalPart();
  2320. staticError.setLocator(makeLocator());
  2321. if ("XPST0008".equals(code) || "XPST0081".equals(code)) {
  2322. staticError.setErrorCode("XQDY0074");
  2323. }
  2324. throw staticError;
  2325. } catch (QNameException err) {
  2326. XPathException e = new XPathException("Invalid QName in attribute constructor: " + lex);
  2327. e.setErrorCode("XQDY0074");
  2328. e.setLocator(makeLocator());
  2329. throw e;
  2330. }
  2331. FixedAttribute fatt = new FixedAttribute(nameCode,
  2332. Validation.STRIP,
  2333. null,
  2334. StandardNames.XS_UNTYPED_ATOMIC);
  2335. fatt.setRejectDuplicates();
  2336. makeSimpleContent(content, fatt, offset);
  2337. return makeTracer(offset, fatt, StandardNames.XSL_ATTRIBUTE, null);
  2338. } else if (vName instanceof QNameValue) {
  2339. QNameValue qnv = (QNameValue)vName;
  2340. int nameCode = env.getNamePool().allocate(
  2341. qnv.getPrefix(), qnv.getNamespaceURI(), qnv.getLocalName());
  2342. FixedAttribute fatt = new FixedAttribute(nameCode,
  2343. Validation.STRIP,
  2344. null,
  2345. StandardNames.XS_UNTYPED_ATOMIC);
  2346. fatt.setRejectDuplicates();
  2347. makeSimpleContent(content, fatt, offset);
  2348. return makeTracer(offset, fatt, StandardNames.XSL_ATTRIBUTE, null);
  2349. }
  2350. }
  2351. ComputedAttribute att = new ComputedAttribute(name,
  2352. null,
  2353. env.getNamespaceResolver(),
  2354. Validation.STRIP,
  2355. null,
  2356. -1,
  2357. true);
  2358. att.setRejectDuplicates();
  2359. makeSimpleContent(content, att, offset);
  2360. return makeTracer(offset, att, StandardNames.XSL_ATTRIBUTE, null);
  2361. } else if ("text".equals(nodeKind)) {
  2362. nextToken();
  2363. Expression value = parseExpression();
  2364. expect(Token.RCURLY);
  2365. lookAhead(); // after an RCURLY
  2366. nextToken();
  2367. Expression select = stringify(value, true);
  2368. ValueOf vof = new ValueOf(select, false, true);
  2369. setLocation(vof, offset);
  2370. return makeTracer(offset, vof, StandardNames.XSL_TEXT, null);
  2371. } else if ("comment".equals(nodeKind)) {
  2372. nextToken();
  2373. Expression value = parseExpression();
  2374. expect(Token.RCURLY);
  2375. lookAhead(); // after an RCURLY
  2376. nextToken();
  2377. Comment com = new Comment();
  2378. makeSimpleContent(value, com, offset);
  2379. return makeTracer(offset, com, StandardNames.XSL_COMMENT, null);
  2380. } else if ("processing-instruction".equals(nodeKind)) {
  2381. nextToken();
  2382. Expression name = parseExpression();
  2383. expect(Token.RCURLY);
  2384. lookAhead(); // must be done manually after an RCURLY
  2385. nextToken();
  2386. expect(Token.LCURLY);
  2387. t.setState(Tokenizer.DEFAULT_STATE);
  2388. nextToken();
  2389. Expression content = null;
  2390. if (t.currentToken != Token.RCURLY) {
  2391. content = parseExpression();
  2392. expect(Token.RCURLY);
  2393. }
  2394. lookAhead(); // after an RCURLY
  2395. nextToken();
  2396. ProcessingInstruction pi = new ProcessingInstruction(name);
  2397. makeSimpleContent(content, pi, offset);
  2398. return makeTracer(offset, pi, StandardNames.XSL_PROCESSING_INSTRUCTION, null);
  2399. } else {
  2400. grumble("Unrecognized node constructor " + t.currentTokenValue + "{}");
  2401. }
  2402. case Token.ELEMENT_QNAME:
  2403. int nameCode = makeNameCode(t.currentTokenValue, true);
  2404. Expression content = null;
  2405. nextToken();
  2406. if (t.currentToken != Token.RCURLY) {
  2407. content = parseExpression();
  2408. expect(Token.RCURLY);
  2409. }
  2410. lookAhead(); // after an RCURLY
  2411. nextToken();
  2412. FixedElement el2 = new FixedElement(nameCode,
  2413. ((QueryModule)env).getActiveNamespaceCodes(),
  2414. ((QueryModule)env).isInheritNamespaces(),
  2415. null,
  2416. ((QueryModule)env).getConstructionMode());
  2417. el2.setBaseURI(env.getBaseURI());
  2418. setLocation(el2, offset);
  2419. if (content == null) {
  2420. content = new Literal(EmptySequence.getInstance());
  2421. }
  2422. el2.setContentExpression(content);
  2423. //makeContentConstructor(content, el2, offset);
  2424. return makeTracer(offset, el2, Location.LITERAL_RESULT_ELEMENT,
  2425. new StructuredQName(env.getNamePool(), nameCode));
  2426. case Token.ATTRIBUTE_QNAME:
  2427. if (t.currentTokenValue.equals("xmlns") || t.currentTokenValue.startsWith("xmlns:")) {
  2428. grumble("Cannot create a namespace using an attribute constructor", "XQDY0044");
  2429. }
  2430. int attNameCode = makeNameCode(t.currentTokenValue, false);
  2431. Expression attContent = null;
  2432. nextToken();
  2433. if (t.currentToken != Token.RCURLY) {
  2434. attContent = parseExpression();
  2435. expect(Token.RCURLY);
  2436. }
  2437. lookAhead(); // after an RCURLY
  2438. nextToken();
  2439. FixedAttribute att2 = new FixedAttribute(attNameCode,
  2440. Validation.STRIP,
  2441. null,
  2442. StandardNames.XS_UNTYPED_ATOMIC);
  2443. att2.setRejectDuplicates();
  2444. makeSimpleContent(attContent, att2, offset);
  2445. return makeTracer(offset, att2, Location.LITERAL_RESULT_ATTRIBUTE,
  2446. new StructuredQName(env.getNamePool(), attNameCode));
  2447. case Token.PI_QNAME:
  2448. String target = t.currentTokenValue;
  2449. if (target.equalsIgnoreCase("xml")) {
  2450. grumble("A processing instruction must not be named 'xml' in any combination of upper and lower case",
  2451. "XQDY0064");
  2452. }
  2453. if (!nameChecker.isValidNCName(target)) {
  2454. grumble("Invalid processing instruction name " + Err.wrap(target));
  2455. }
  2456. Expression piName = new StringLiteral(target);
  2457. Expression piContent = null;
  2458. nextToken();
  2459. if (t.currentToken != Token.RCURLY) {
  2460. piContent = parseExpression();
  2461. expect(Token.RCURLY);
  2462. }
  2463. lookAhead(); // after an RCURLY
  2464. nextToken();
  2465. ProcessingInstruction pi2 = new ProcessingInstruction(piName);
  2466. makeSimpleContent(piContent, pi2, offset);
  2467. return makeTracer(offset, pi2, StandardNames.XSL_PROCESSING_INSTRUCTION, null);
  2468. }
  2469. return null;
  2470. }
  2471. /**
  2472. * Make the instructions for the children of a node with simple content (attribute, text, PI, etc)
  2473. *
  2474. * @param content the expression making up the simple content
  2475. * @param inst the skeletal instruction for creating the node
  2476. * @param offset the character position of this construct within the source query
  2477. */
  2478. private void makeSimpleContent(Expression content, SimpleNodeConstructor inst, int offset) throws XPathException {
  2479. try {
  2480. if (content == null) {
  2481. inst.setSelect(new StringLiteral(StringValue.EMPTY_STRING), env.getConfiguration());
  2482. } else {
  2483. inst.setSelect(stringify(content, false), env.getConfiguration());
  2484. }
  2485. setLocation(inst, offset);
  2486. } catch (XPathException e) {
  2487. grumble(e.getMessage());
  2488. }
  2489. }
  2490. /**
  2491. * Make a sequence of instructions as the children of an element-construction instruction.
  2492. * The idea here is to convert an XPath expression that "pulls" the content into a sequence
  2493. * of XSLT-style instructions that push nodes directly onto the subtree, thus reducing the
  2494. * need for copying of intermediate nodes.
  2495. * @param content The content of the element as an expression
  2496. * @param inst The element-construction instruction (Element or FixedElement)
  2497. * @param offset the character position in the query
  2498. */
  2499. // private void makeContentConstructor(Expression content, InstructionWithChildren inst, int offset) {
  2500. // if (content == null) {
  2501. // inst.setChildren(null);
  2502. // } else if (content instanceof AppendExpression) {
  2503. // List instructions = new ArrayList(10);
  2504. // convertAppendExpression((AppendExpression) content, instructions);
  2505. // inst.setChildren((Expression[]) instructions.toArray(new Expression[instructions.size()]));
  2506. // } else {
  2507. // Expression children[] = {content};
  2508. // inst.setChildren(children);
  2509. // }
  2510. // setLocation(inst, offset);
  2511. // }
  2512. /**
  2513. * Parse pseudo-XML syntax in direct element constructors, comments, CDATA, etc.
  2514. * This is handled by reading single characters from the Tokenizer until the
  2515. * end of the tag (or an enclosed expression) is enountered.
  2516. * This method is also used to read an end tag. Because an end tag is not an
  2517. * expression, the method in this case returns a StringValue containing the
  2518. * contents of the end tag.
  2519. *
  2520. * @param allowEndTag true if the context allows an End Tag to appear here
  2521. * @return an Expression representing the result of parsing the constructor.
  2522. * If an end tag was read, its contents will be returned as a StringValue.
  2523. */
  2524. private Expression parsePseudoXML(boolean allowEndTag) throws XPathException {
  2525. try {
  2526. Expression exp = null;
  2527. int offset = t.inputOffset;
  2528. // we're reading raw characters, so we don't want the currentTokenStartOffset
  2529. char c = t.nextChar();
  2530. switch (c) {
  2531. case '!':
  2532. c = t.nextChar();
  2533. if (c == '-') {
  2534. exp = parseCommentConstructor();
  2535. } else if (c == '[') {
  2536. grumble("A CDATA section is allowed only in element content");
  2537. // if CDATA were allowed here, we would have already read it
  2538. } else {
  2539. grumble("Expected '--' or '[CDATA[' after '<!'");
  2540. }
  2541. break;
  2542. case '?':
  2543. exp = parsePIConstructor();
  2544. break;
  2545. case '/':
  2546. if (allowEndTag) {
  2547. FastStringBuffer sb = new FastStringBuffer(40);
  2548. while (true) {
  2549. c = t.nextChar();
  2550. if (c == '>') {
  2551. break;
  2552. }
  2553. sb.append(c);
  2554. }
  2555. return new StringLiteral(new StringValue(sb));
  2556. }
  2557. grumble("Unmatched XML end tag");
  2558. break;
  2559. default:
  2560. t.unreadChar();
  2561. exp = parseDirectElementConstructor();
  2562. }
  2563. setLocation(exp, offset);
  2564. return exp;
  2565. } catch (StringIndexOutOfBoundsException e) {
  2566. grumble("End of input encountered while parsing direct constructor");
  2567. return null;
  2568. }
  2569. }
  2570. /**
  2571. * Parse a direct element constructor
  2572. *
  2573. * @return the expression representing the constructor
  2574. * @throws XPathException
  2575. * @throws StringIndexOutOfBoundsException if the end of input is encountered prematurely
  2576. */
  2577. private Expression parseDirectElementConstructor() throws XPathException, StringIndexOutOfBoundsException {
  2578. int offset = t.inputOffset - 1;
  2579. // we're reading raw characters, so we don't want the currentTokenStartOffset
  2580. char c;
  2581. FastStringBuffer buff = new FastStringBuffer(40);
  2582. int namespaceCount = 0;
  2583. while (true) {
  2584. c = t.nextChar();
  2585. if (c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '/' || c == '>') {
  2586. break;
  2587. }
  2588. buff.append(c);
  2589. }
  2590. String elname = buff.toString();
  2591. HashMap attributes = new HashMap(10);
  2592. while (true) {
  2593. // loop through the attributes
  2594. // We must process namespace declaration attributes first;
  2595. // their scope applies to all preceding attribute names and values.
  2596. // But finding the delimiting quote of an attribute value requires the
  2597. // XPath expressions to be parsed, because they may contain nested quotes.
  2598. // So we parse in "scanOnly" mode, which ignores any undeclared namespace
  2599. // prefixes, use the result of this parse to determine the length of the
  2600. // attribute value, save the value, and reparse it when all the namespace
  2601. // declarations have been dealt with.
  2602. c = skipSpaces(c);
  2603. if (c == '/' || c == '>') {
  2604. break;
  2605. }
  2606. int attOffset = t.inputOffset - 1;
  2607. buff.setLength(0);
  2608. // read the attribute name
  2609. while (true) {
  2610. buff.append(c);
  2611. c = t.nextChar();
  2612. if (c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '=') {
  2613. break;
  2614. }
  2615. }
  2616. String attName = buff.toString();
  2617. if (!nameChecker.isQName(attName)) {
  2618. grumble("Invalid attribute name " + Err.wrap(attName, Err.ATTRIBUTE));
  2619. }
  2620. c = skipSpaces(c);
  2621. expectChar(c, '=');
  2622. c = t.nextChar();
  2623. c = skipSpaces(c);
  2624. char delim = c;
  2625. boolean isNamespace = ("xmlns".equals(attName) || attName.startsWith("xmlns:"));
  2626. int end;
  2627. if (isNamespace) {
  2628. end = makeNamespaceContent(t.input, t.inputOffset, delim);
  2629. } else {
  2630. Expression avt;
  2631. try {
  2632. avt = makeAttributeContent(t.input, t.inputOffset, delim, true);
  2633. } catch (XPathException err) {
  2634. grumble(err.getMessage());
  2635. return null;
  2636. }
  2637. // by convention, this returns the end position when called with scanOnly set
  2638. end = (int)((Int64Value)((Literal)avt).getValue()).longValue();
  2639. }
  2640. // save the value with its surrounding quotes
  2641. String val = t.input.substring(t.inputOffset - 1, end + 1);
  2642. // and without
  2643. String rval = t.input.substring(t.inputOffset, end);
  2644. t.inputOffset = end + 1;
  2645. // on return, the current character is the closing quote
  2646. c = t.nextChar();
  2647. if (!(c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '/' || c == '>')) {
  2648. grumble("There must be whitespace after every attribute except the last");
  2649. }
  2650. if (isNamespace) {
  2651. // Processing follows the resolution of bug 5083: doubled curly braces represent single
  2652. // curly braces, single curly braces are not allowed.
  2653. FastStringBuffer sb = new FastStringBuffer(rval.length());
  2654. boolean prevDelim = false;
  2655. boolean prevOpenCurly = false;
  2656. boolean prevCloseCurly = false;
  2657. for (int i=0; i<rval.length(); i++) {
  2658. char n = rval.charAt(i);
  2659. if (n == delim) {
  2660. prevDelim = !prevDelim;
  2661. if (prevDelim) {
  2662. continue;
  2663. }
  2664. }
  2665. if (n == '{') {
  2666. prevOpenCurly = !prevOpenCurly;
  2667. if (prevOpenCurly) {
  2668. continue;
  2669. }
  2670. } else if (prevOpenCurly) {
  2671. grumble("Namespace must not contain an unescaped opening brace", "XQST0022");
  2672. }
  2673. if (n == '}') {
  2674. prevCloseCurly = !prevCloseCurly;
  2675. if (prevCloseCurly) {
  2676. continue;
  2677. }
  2678. } else if (prevCloseCurly) {
  2679. grumble("Namespace must not contain an unescaped closing brace", "XQST0003");
  2680. }
  2681. sb.append(n);
  2682. }
  2683. if (prevOpenCurly) {
  2684. grumble("Namespace must not contain an unescaped opening brace", "XQST0022");
  2685. }
  2686. if (prevCloseCurly) {
  2687. grumble("Namespace must not contain an unescaped closing brace", "XQST0003");
  2688. }
  2689. rval = sb.toString();
  2690. String uri = URILiteral(rval);
  2691. if (!AnyURIValue.isValidURI(uri)) {
  2692. grumble("Namespace must be a valid URI value", "XQST0022");
  2693. }
  2694. String prefix;
  2695. if ("xmlns".equals(attName)) {
  2696. prefix = "";
  2697. if (uri.equals(NamespaceConstant.XML)) {
  2698. grumble("Cannot have the XML namespace as the default namespace", "XQST0070");
  2699. }
  2700. } else {
  2701. prefix = attName.substring(6);
  2702. if (prefix.equals("xml") && !uri.equals(NamespaceConstant.XML)) {
  2703. grumble("Cannot bind the prefix 'xml' to a namespace other than the XML namespace", "XQST0070");
  2704. } else if (uri.equals(NamespaceConstant.XML) && !prefix.equals("xml")) {
  2705. grumble("Cannot bind a prefix other than 'xml' to the XML namespace", "XQST0070");
  2706. } else if (prefix.equals("xmlns")) {
  2707. grumble("Cannot use xmlns as a namespace prefix", "XQST0070");
  2708. }
  2709. if (uri.length() == 0) {
  2710. grumble("Namespace URI must not be empty", "XQST0085");
  2711. }
  2712. }
  2713. namespaceCount++;
  2714. ((QueryModule)env).declareActiveNamespace(prefix, uri);
  2715. }
  2716. if (attributes.get(attName) != null) {
  2717. if (isNamespace) {
  2718. grumble("Duplicate namespace declaration " + attName, "XQST0071");
  2719. } else {
  2720. grumble("Duplicate attribute name " + attName, "XQST0040");
  2721. }
  2722. }
  2723. // if (attName.equals("xml:id") && !nameChecker.isValidNCName(rval)) {
  2724. // grumble("Value of xml:id must be a valid NCName", "XQST0082");
  2725. // }
  2726. AttributeDetails a = new AttributeDetails();
  2727. a.value = val;
  2728. a.startOffset = attOffset;
  2729. attributes.put(attName, a);
  2730. }
  2731. String namespace;
  2732. int elNameCode = 0;
  2733. try {
  2734. String[] parts = nameChecker.getQNameParts(elname);
  2735. namespace = ((QueryModule)env).checkURIForPrefix(parts[0]);
  2736. if (namespace == null) {
  2737. grumble("Undeclared prefix in element name " + Err.wrap(elname, Err.ELEMENT), "XPST0081");
  2738. }
  2739. elNameCode = env.getNamePool().allocate(parts[0], namespace, parts[1]);
  2740. } catch (QNameException e) {
  2741. grumble("Invalid element name " + Err.wrap(elname, Err.ELEMENT));
  2742. }
  2743. int validationMode = ((QueryModule)env).getConstructionMode();
  2744. FixedElement elInst = new FixedElement(elNameCode,
  2745. ((QueryModule)env).getActiveNamespaceCodes(),
  2746. ((QueryModule)env).isInheritNamespaces(),
  2747. null,
  2748. validationMode);
  2749. elInst.setBaseURI(env.getBaseURI());
  2750. setLocation(elInst, offset);
  2751. List contents = new ArrayList(10);
  2752. IntHashSet attFingerprints = new IntHashSet(attributes.size());
  2753. // we've checked for duplicate lexical QNames, but not for duplicate expanded-QNames
  2754. for (Iterator iter = attributes.keySet().iterator(); iter.hasNext();) {
  2755. String attName = (String)iter.next();
  2756. AttributeDetails a = (AttributeDetails)attributes.get(attName);
  2757. String attValue = a.value;
  2758. int attOffset = a.startOffset;
  2759. if ("xmlns".equals(attName) || attName.startsWith("xmlns:")) {
  2760. // do nothing
  2761. } else if (scanOnly) {
  2762. // This means we are prescanning an attribute constructor, and we found a nested attribute
  2763. // constructor, which we have prescanned; we now don't want to re-process the nested attribute
  2764. // constructor because it might depend on things like variables declared in the containing
  2765. // attribute constructor, and in any case we're going to come back to it again later.
  2766. // See test qxmp180
  2767. } else {
  2768. int attNameCode = 0;
  2769. String attNamespace;
  2770. try {
  2771. String[] parts = nameChecker.getQNameParts(attName);
  2772. if (parts[0].length() == 0) {
  2773. // attributes don't use the default namespace
  2774. attNamespace = "";
  2775. } else {
  2776. attNamespace = ((QueryModule)env).checkURIForPrefix(parts[0]);
  2777. }
  2778. if (attNamespace == null) {
  2779. grumble("Undeclared prefix in attribute name " +
  2780. Err.wrap(attName, Err.ATTRIBUTE), "XPST0081");
  2781. }
  2782. attNameCode = env.getNamePool().allocate(parts[0], attNamespace, parts[1]);
  2783. int key = (attNameCode & NamePool.FP_MASK);
  2784. if (attFingerprints.contains(key)) {
  2785. grumble("Duplicate expanded attribute name " + attName, "XQST0040");
  2786. }
  2787. attFingerprints.add(key);
  2788. } catch (QNameException e) {
  2789. grumble("Invalid attribute name " + Err.wrap(attName, Err.ATTRIBUTE));
  2790. }
  2791. FixedAttribute attInst =
  2792. new FixedAttribute(attNameCode, Validation.STRIP, null, StandardNames.XS_UNTYPED_ATOMIC);
  2793. setLocation(attInst);
  2794. Expression select;
  2795. try {
  2796. select = makeAttributeContent(attValue, 1, attValue.charAt(0), false);
  2797. } catch (XPathException err) {
  2798. throw err.makeStatic();
  2799. }
  2800. attInst.setSelect(select, env.getConfiguration());
  2801. attInst.setRejectDuplicates();
  2802. setLocation(attInst);
  2803. contents.add(makeTracer(attOffset, attInst, Location.LITERAL_RESULT_ATTRIBUTE,
  2804. new StructuredQName(env.getNamePool(), attNameCode)));
  2805. }
  2806. }
  2807. if (c == '/') {
  2808. // empty element tag
  2809. expectChar(t.nextChar(), '>');
  2810. } else {
  2811. readElementContent(elname, contents);
  2812. }
  2813. Expression[] elk = new Expression[contents.size()];
  2814. for (int i = 0; i < contents.size(); i++) {
  2815. // if the child expression creates another element,
  2816. // suppress validation, as the parent already takes care of it
  2817. if (validationMode != Validation.STRIP) {
  2818. ((Expression)contents.get(i)).suppressValidation(validationMode);
  2819. }
  2820. elk[i] = (Expression)contents.get(i);
  2821. }
  2822. Block block = new Block();
  2823. block.setChildren(elk);
  2824. elInst.setContentExpression(block);
  2825. // reset the in-scope namespaces to what they were before
  2826. for (int n = 0; n < namespaceCount; n++) {
  2827. ((QueryModule)env).undeclareNamespace();
  2828. }
  2829. return makeTracer(offset, elInst, Location.LITERAL_RESULT_ELEMENT,
  2830. new StructuredQName(env.getNamePool(), elNameCode));
  2831. }
  2832. /**
  2833. * Parse the content of an attribute in a direct element constructor. This may contain nested expressions
  2834. * within curly braces. A particular problem is that the namespaces used in the expression may not yet be
  2835. * known. This means we need the ability to parse in "scanOnly" mode, where undeclared namespace prefixes
  2836. * are ignored.
  2837. * <p/>
  2838. * The code is based on the XSLT code in {@link AttributeValueTemplate#make}: the main difference is that
  2839. * character entities and built-in entity references need to be recognized and expanded. Also, whitespace
  2840. * needs to be normalized, mimicking the action of an XML parser
  2841. *
  2842. * @param avt the content of the attribute as written, including variable portions enclosed in curly braces
  2843. * @param start the character position in the attribute value where parsing should start
  2844. * @param terminator a character that is to be taken as marking the end of the expression
  2845. * @param scanOnly if the purpose of this parse is simply to locate the end of the attribute value, and not
  2846. * to report any semantic errors.
  2847. * @return the expression that will evaluate the content of the attribute
  2848. */
  2849. private Expression makeAttributeContent(String avt,
  2850. int start,
  2851. char terminator,
  2852. boolean scanOnly) throws XPathException {
  2853. int lineNumber = t.getLineNumber();
  2854. List components = new ArrayList(10);
  2855. int i0, i1, i2, i8, i9, len, last;
  2856. last = start;
  2857. len = avt.length();
  2858. while (last < len) {
  2859. i2 = avt.indexOf(terminator, last);
  2860. if (i2 < 0) {
  2861. XPathException e = new XPathException("Attribute constructor is not properly terminated");
  2862. e.setIsStaticError(true);
  2863. throw e;
  2864. }
  2865. i0 = avt.indexOf("{", last);
  2866. i1 = avt.indexOf("{{", last);
  2867. i8 = avt.indexOf("}", last);
  2868. i9 = avt.indexOf("}}", last);
  2869. if ((i0 < 0 || i2 < i0) && (i8 < 0 || i2 < i8)) { // found end of string
  2870. addStringComponent(components, avt, last, i2);
  2871. // look for doubled quotes, and skip them (for now)
  2872. if (i2 + 1 < avt.length() && avt.charAt(i2 + 1) == terminator) {
  2873. components.add(new StringLiteral(terminator + ""));
  2874. last = i2 + 2;
  2875. //continue;
  2876. } else {
  2877. last = i2;
  2878. break;
  2879. }
  2880. } else if (i8 >= 0 && (i0 < 0 || i8 < i0)) { // found a "}"
  2881. if (i8 != i9) { // a "}" that isn't a "}}"
  2882. XPathException e = new XPathException(
  2883. "Closing curly brace in attribute value template \"" + avt + "\" must be doubled");
  2884. e.setIsStaticError(true);
  2885. throw e;
  2886. }
  2887. addStringComponent(components, avt, last, i8 + 1);
  2888. last = i8 + 2;
  2889. } else if (i1 >= 0 && i1 == i0) { // found a doubled "{{"
  2890. addStringComponent(components, avt, last, i1 + 1);
  2891. last = i1 + 2;
  2892. } else if (i0 >= 0) { // found a single "{"
  2893. if (i0 > last) {
  2894. addStringComponent(components, avt, last, i0);
  2895. }
  2896. Expression exp;
  2897. ExpressionParser parser;
  2898. parser = new QueryParser();
  2899. parser.setScanOnly(scanOnly);
  2900. if (rangeVariables != null) {
  2901. parser.setRangeVariableStack(rangeVariables);
  2902. }
  2903. exp = parser.parse(avt, i0 + 1, Token.RCURLY, lineNumber, env);
  2904. if (!scanOnly) {
  2905. exp = ExpressionVisitor.make(env).simplify(exp);
  2906. }
  2907. last = parser.getTokenizer().currentTokenStartOffset + 1;
  2908. components.add(makeStringJoin(exp, env));
  2909. } else {
  2910. throw new IllegalStateException("Internal error parsing direct attribute constructor");
  2911. }
  2912. }
  2913. // if this is simply a prescan, return the position of the end of the
  2914. // AVT, so we can parse it properly later
  2915. if (scanOnly) {
  2916. return new Literal(Int64Value.makeIntegerValue(last));
  2917. }
  2918. // is it empty?
  2919. if (components.isEmpty()) {
  2920. return new StringLiteral(StringValue.EMPTY_STRING);
  2921. }
  2922. // is it a single component?
  2923. if (components.size() == 1) {
  2924. return ExpressionVisitor.make(env).simplify((Expression)components.get(0));
  2925. }
  2926. // otherwise, return an expression that concatenates the components
  2927. Expression[] args = new Expression[components.size()];
  2928. components.toArray(args);
  2929. Concat fn = (Concat)SystemFunction.makeSystemFunction("concat", args);
  2930. fn.setLocationId(env.getLocationMap().allocateLocationId(env.getSystemId(), lineNumber));
  2931. return ExpressionVisitor.make(env).simplify(fn);
  2932. }
  2933. private void addStringComponent(List components, String avt, int start, int end)
  2934. throws XPathException {
  2935. // analyze fixed text within the value of a direct attribute constructor.
  2936. if (start < end) {
  2937. FastStringBuffer sb = new FastStringBuffer(end - start);
  2938. for (int i = start; i < end; i++) {
  2939. char c = avt.charAt(i);
  2940. switch (c) {
  2941. case '&':
  2942. {
  2943. int semic = avt.indexOf(';', i);
  2944. if (semic < 0) {
  2945. grumble("No closing ';' found for entity or character reference");
  2946. } else {
  2947. String entity = avt.substring(i + 1, semic);
  2948. sb.append(analyzeEntityReference(entity));
  2949. i = semic;
  2950. }
  2951. break;
  2952. }
  2953. case '<':
  2954. grumble("The < character must not appear in attribute content");
  2955. break;
  2956. case '\n':
  2957. case '\t':
  2958. sb.append(' ');
  2959. break;
  2960. case '\r':
  2961. sb.append(' ');
  2962. if (i + 1 < end && avt.charAt(i + 1) == '\n') {
  2963. i++;
  2964. }
  2965. break;
  2966. default:
  2967. sb.append(c);
  2968. }
  2969. }
  2970. components.add(new StringLiteral(sb.toString()));
  2971. }
  2972. }
  2973. /**
  2974. * Parse the content of an namespace declaration attribute in a direct element constructor. This is simpler
  2975. * than an ordinary attribute because it does not contain nested expressions in curly braces. (But see bug 5083).
  2976. *
  2977. * @param avt the content of the attribute as written, including variable portions enclosed in curly braces
  2978. * @param start the character position in the attribute value where parsing should start
  2979. * @param terminator a character that is to be taken as marking the end of the expression
  2980. * @return the position of the end of the URI value
  2981. */
  2982. private int makeNamespaceContent(String avt, int start, char terminator) throws XPathException {
  2983. int i2, len, last;
  2984. last = start;
  2985. len = avt.length();
  2986. while (last < len) {
  2987. i2 = avt.indexOf(terminator, last);
  2988. if (i2 < 0) {
  2989. XPathException e = new XPathException("Namespace declaration is not properly terminated");
  2990. e.setIsStaticError(true);
  2991. throw e;
  2992. }
  2993. // look for doubled quotes, and skip them (for now)
  2994. if (i2 + 1 < avt.length() && avt.charAt(i2 + 1) == terminator) {
  2995. last = i2 + 2;
  2996. //continue;
  2997. } else {
  2998. last = i2;
  2999. break;
  3000. }
  3001. }
  3002. // return the position of the end of the literal
  3003. return last;
  3004. }
  3005. /**
  3006. * Read the content of a direct element constructor
  3007. *
  3008. * @param startTag the element start tag
  3009. * @param components an empty list, to which the expressions comprising the element contents are added
  3010. * @throws XPathException if any static errors are detected
  3011. */
  3012. private void readElementContent(String startTag, List components) throws XPathException {
  3013. final TypeHierarchy th = env.getConfiguration().getTypeHierarchy();
  3014. try {
  3015. boolean afterEnclosedExpr = false;
  3016. while (true) {
  3017. // read all the components of the element value
  3018. FastStringBuffer text = new FastStringBuffer(256);
  3019. char c;
  3020. boolean containsEntities = false;
  3021. while (true) {
  3022. c = t.nextChar();
  3023. if (c == '<') {
  3024. // See if we've got a CDATA section
  3025. if (t.nextChar() == '!') {
  3026. if (t.nextChar() == '[') {
  3027. readCDATASection(text);
  3028. containsEntities = true;
  3029. continue;
  3030. } else {
  3031. t.unreadChar();
  3032. t.unreadChar();
  3033. }
  3034. } else {
  3035. t.unreadChar();
  3036. }
  3037. break;
  3038. } else if (c == '&') {
  3039. text.append(readEntityReference());
  3040. containsEntities = true;
  3041. } else if (c == '}') {
  3042. c = t.nextChar();
  3043. if (c != '}') {
  3044. grumble("'}' must be written as '}}' within element content");
  3045. }
  3046. text.append(c);
  3047. } else if (c == '{') {
  3048. c = t.nextChar();
  3049. if (c != '{') {
  3050. c = '{';
  3051. break;
  3052. }
  3053. text.append(c);
  3054. } else {
  3055. text.append(c);
  3056. }
  3057. }
  3058. if (text.length() > 0 &&
  3059. (containsEntities |
  3060. ((QueryModule)env).isPreserveBoundarySpace() |
  3061. !Whitespace.isWhite(text))) {
  3062. ValueOf inst = new ValueOf(new StringLiteral(new StringValue(text.condense())), false, false);
  3063. setLocation(inst);
  3064. components.add(inst);
  3065. afterEnclosedExpr = false;
  3066. }
  3067. if (c == '<') {
  3068. Expression exp = parsePseudoXML(true);
  3069. // An end tag can appear here, and is returned as a string value
  3070. if (exp instanceof StringLiteral) {
  3071. String endTag = ((StringLiteral)exp).getStringValue();
  3072. if (Whitespace.isWhitespace(endTag.charAt(0))) {
  3073. grumble("End tag contains whitespace before the name");
  3074. }
  3075. endTag = Whitespace.trim(endTag);
  3076. if (endTag.equals(startTag)) {
  3077. return;
  3078. } else {
  3079. grumble("End tag </" + endTag +
  3080. "> does not match start tag <" + startTag + '>');
  3081. }
  3082. } else {
  3083. components.add(exp);
  3084. }
  3085. } else {
  3086. // we read an '{' indicating an enclosed expression
  3087. if (afterEnclosedExpr) {
  3088. Expression previousComponent = (Expression)components.get(components.size() - 1);
  3089. ItemType previousItemType = previousComponent.getItemType(th);
  3090. if (!(previousItemType instanceof NodeTest)) {
  3091. // Add a zero-length text node, to prevent {"a"}{"b"} generating an intervening space
  3092. // See tests (qxmp132, qxmp261)
  3093. ValueOf inst = new ValueOf(new StringLiteral(StringValue.EMPTY_STRING), false, false);
  3094. setLocation(inst);
  3095. components.add(inst);
  3096. }
  3097. }
  3098. t.unreadChar();
  3099. t.setState(Tokenizer.DEFAULT_STATE);
  3100. lookAhead();
  3101. nextToken();
  3102. Expression exp = parseExpression();
  3103. if (!((QueryModule)env).isPreserveNamespaces()) {
  3104. exp = new CopyOf(exp, false, Validation.PRESERVE, null, true);
  3105. }
  3106. components.add(exp);
  3107. expect(Token.RCURLY);
  3108. afterEnclosedExpr = true;
  3109. }
  3110. }
  3111. } catch (StringIndexOutOfBoundsException err) {
  3112. grumble("No closing end tag found for direct element constructor");
  3113. }
  3114. }
  3115. private Expression parsePIConstructor() throws XPathException {
  3116. try {
  3117. FastStringBuffer pi = new FastStringBuffer(120);
  3118. int firstSpace = -1;
  3119. while (!pi.toString().endsWith("?>")) {
  3120. char c = t.nextChar();
  3121. if (firstSpace < 0 && " \t\r\n".indexOf(c) >= 0) {
  3122. firstSpace = pi.length();
  3123. }
  3124. pi.append(c);
  3125. }
  3126. pi.setLength(pi.length() - 2);
  3127. String target;
  3128. String data = "";
  3129. if (firstSpace < 0) {
  3130. // there is no data part
  3131. target = pi.toString();
  3132. } else {
  3133. // trim leading space from the data part, but not trailing space
  3134. target = pi.toString().substring(0, firstSpace);
  3135. firstSpace++;
  3136. while (firstSpace < pi.length() && " \t\r\n".indexOf(pi.charAt(firstSpace)) >= 0) {
  3137. firstSpace++;
  3138. }
  3139. data = pi.toString().substring(firstSpace);
  3140. }
  3141. if (!nameChecker.isValidNCName(target)) {
  3142. grumble("Invalid processing instruction name " + Err.wrap(target));
  3143. }
  3144. if (target.equalsIgnoreCase("xml")) {
  3145. grumble("A processing instruction must not be named 'xml' in any combination of upper and lower case");
  3146. }
  3147. ProcessingInstruction instruction =
  3148. new ProcessingInstruction(new StringLiteral(target));
  3149. instruction.setSelect(new StringLiteral(data), env.getConfiguration());
  3150. setLocation(instruction);
  3151. return instruction;
  3152. } catch (StringIndexOutOfBoundsException err) {
  3153. grumble("No closing '?>' found for processing instruction");
  3154. return null;
  3155. }
  3156. }
  3157. private void readCDATASection(FastStringBuffer cdata) throws XPathException {
  3158. try {
  3159. char c;
  3160. // CDATA section
  3161. c = t.nextChar();
  3162. expectChar(c, 'C');
  3163. c = t.nextChar();
  3164. expectChar(c, 'D');
  3165. c = t.nextChar();
  3166. expectChar(c, 'A');
  3167. c = t.nextChar();
  3168. expectChar(c, 'T');
  3169. c = t.nextChar();
  3170. expectChar(c, 'A');
  3171. c = t.nextChar();
  3172. expectChar(c, '[');
  3173. while (!cdata.toString().endsWith("]]>")) {
  3174. cdata.append(t.nextChar());
  3175. }
  3176. cdata.setLength(cdata.length() - 3);
  3177. } catch (StringIndexOutOfBoundsException err) {
  3178. grumble("No closing ']]>' found for CDATA section");
  3179. }
  3180. }
  3181. private Expression parseCommentConstructor() throws XPathException {
  3182. try {
  3183. char c = t.nextChar();
  3184. // XML-like comment
  3185. expectChar(c, '-');
  3186. FastStringBuffer comment = new FastStringBuffer(240);
  3187. while (!comment.toString().endsWith("--")) {
  3188. comment.append(t.nextChar());
  3189. }
  3190. if (t.nextChar() != '>') {
  3191. grumble("'--' is not permitted in an XML comment");
  3192. }
  3193. CharSequence commentText = comment.subSequence(0, comment.length() - 2);
  3194. Comment instruction = new Comment();
  3195. instruction.setSelect(new StringLiteral(new StringValue(commentText)), env.getConfiguration());
  3196. setLocation(instruction);
  3197. return instruction;
  3198. } catch (StringIndexOutOfBoundsException err) {
  3199. grumble("No closing '-->' found for comment constructor");
  3200. return null;
  3201. }
  3202. }
  3203. /**
  3204. * Convert an expression so it generates a space-separated sequence of strings
  3205. *
  3206. * @param exp the expression that calculates the content
  3207. * @param noNodeIfEmpty if true, no node is produced when the value of the content
  3208. * expression is an empty sequence. If false, the effect of supplying an empty sequence
  3209. * is that a node is created whose string-value is a zero-length string. Set to true for
  3210. * text node constructors, false for other kinds of node.
  3211. * @return an expression that computes the content and converts the result to a character string
  3212. */
  3213. private Expression stringify(Expression exp, boolean noNodeIfEmpty) throws XPathException {
  3214. return ExpressionVisitor.make(env).simplify(new QuerySimpleContentConstructor(
  3215. exp, new StringLiteral(StringValue.SINGLE_SPACE), noNodeIfEmpty));
  3216. }
  3217. /**
  3218. * Method to make a string literal from a token identified as a string
  3219. * literal. This is trivial in XPath, but in XQuery the method is overridden
  3220. * to identify pseudo-XML character and entity references
  3221. *
  3222. * @param token the string as written (or as returned by the tokenizer)
  3223. * @return The string value of the string literal, after dereferencing entity and
  3224. * character references
  3225. */
  3226. protected Literal makeStringLiteral(String token) throws XPathException {
  3227. StringLiteral lit;
  3228. if (token.indexOf('&') == -1) {
  3229. lit = new StringLiteral(token);
  3230. } else {
  3231. FastStringBuffer sb = unescape(token);
  3232. lit = new StringLiteral(StringValue.makeStringValue(sb));
  3233. }
  3234. setLocation(lit);
  3235. return lit;
  3236. }
  3237. /**
  3238. * Unescape character references and built-in entity references in a string
  3239. *
  3240. * @param token the input string, which may include XML-style character references or built-in
  3241. * entity references
  3242. * @return the string with character references and built-in entity references replaced by their expansion
  3243. * @throws XPathException if a malformed character or entity reference is found
  3244. */
  3245. private FastStringBuffer unescape(String token) throws XPathException {
  3246. FastStringBuffer sb = new FastStringBuffer(80);
  3247. for (int i = 0; i < token.length(); i++) {
  3248. char c = token.charAt(i);
  3249. if (c == '&') {
  3250. int semic = token.indexOf(';', i);
  3251. if (semic < 0) {
  3252. grumble("No closing ';' found for entity or character reference");
  3253. } else {
  3254. String entity = token.substring(i + 1, semic);
  3255. sb.append(analyzeEntityReference(entity));
  3256. i = semic;
  3257. }
  3258. } else {
  3259. sb.append(c);
  3260. }
  3261. }
  3262. return sb;
  3263. }
  3264. /**
  3265. * Read a pseudo-XML character reference or entity reference.
  3266. *
  3267. * @return The character represented by the character or entity reference. Note
  3268. * that this is a string rather than a char because a char only accommodates characters
  3269. * up to 65535.
  3270. * @throws XPathException if the character or entity reference is not well-formed
  3271. */
  3272. private String readEntityReference() throws XPathException {
  3273. try {
  3274. FastStringBuffer sb = new FastStringBuffer(40);
  3275. while (true) {
  3276. char c = t.nextChar();
  3277. if (c == ';') {
  3278. break;
  3279. }
  3280. sb.append(c);
  3281. }
  3282. String entity = sb.toString();
  3283. return analyzeEntityReference(entity);
  3284. } catch (StringIndexOutOfBoundsException err) {
  3285. grumble("No closing ';' found for entity or character reference");
  3286. }
  3287. return null; // to keep the Java compiler happy
  3288. }
  3289. private String analyzeEntityReference(String entity) throws XPathException {
  3290. if ("lt".equals(entity)) {
  3291. return "<";
  3292. } else if ("gt".equals(entity)) {
  3293. return ">";
  3294. } else if ("amp".equals(entity)) {
  3295. return "&";
  3296. } else if ("quot".equals(entity)) {
  3297. return "\"";
  3298. } else if ("apos".equals(entity)) {
  3299. return "'";
  3300. } else if (entity.length() < 2 || entity.charAt(0) != '#') {
  3301. grumble("invalid character reference &" + entity + ';');
  3302. return null;
  3303. } else {
  3304. //entity = entity.toLowerCase();
  3305. return parseCharacterReference(entity);
  3306. }
  3307. }
  3308. private String parseCharacterReference(String entity) throws XPathException {
  3309. int value = 0;
  3310. if (entity.charAt(1) == 'x') {
  3311. if (entity.length() < 3) {
  3312. grumble("No hex digits in hexadecimal character reference");
  3313. }
  3314. entity = entity.toLowerCase();
  3315. for (int i = 2; i < entity.length(); i++) {
  3316. int digit = "0123456789abcdef".indexOf(entity.charAt(i));
  3317. if (digit < 0) {
  3318. grumble("Invalid hex digit '" + entity.charAt(i) + "' in character reference");
  3319. }
  3320. value = (value * 16) + digit;
  3321. if (value > UTF16.NONBMP_MAX) {
  3322. grumble("Character reference exceeds Unicode codepoint limit", "XQST0090");
  3323. }
  3324. }
  3325. } else {
  3326. for (int i = 1; i < entity.length(); i++) {
  3327. int digit = "0123456789".indexOf(entity.charAt(i));
  3328. if (digit < 0) {
  3329. grumble("Invalid digit '" + entity.charAt(i) + "' in decimal character reference");
  3330. }
  3331. value = (value * 10) + digit;
  3332. if (value > UTF16.NONBMP_MAX) {
  3333. grumble("Character reference exceeds Unicode codepoint limit", "XQST0090");
  3334. }
  3335. }
  3336. }
  3337. NameChecker nc = env.getConfiguration().getNameChecker();
  3338. if (!nc.isValidChar(value)) {
  3339. grumble("Invalid XML character reference x"
  3340. + Integer.toHexString(value), "XQST0090");
  3341. }
  3342. // following code borrowed from AElfred
  3343. // Check for surrogates: 00000000 0000xxxx yyyyyyyy zzzzzzzz
  3344. // (1101|10xx|xxyy|yyyy + 1101|11yy|zzzz|zzzz:
  3345. if (value <= 0x0000ffff) {
  3346. // no surrogates needed
  3347. return "" + (char)value;
  3348. } else if (value <= 0x0010ffff) {
  3349. value -= 0x10000;
  3350. // > 16 bits, surrogate needed
  3351. return "" + ((char)(0xd800 | (value >> 10)))
  3352. + ((char)(0xdc00 | (value & 0x0003ff)));
  3353. } else {
  3354. // too big for surrogate
  3355. grumble("Character reference x" + Integer.toHexString(value) + " is too large", "XQST0090");
  3356. }
  3357. return null;
  3358. }
  3359. /**
  3360. * Handle a URI literal. This is whitespace-normalized as well as being unescaped
  3361. * @param in the string as written
  3362. * @return the URI after unescaping of entity and character references
  3363. * followed by whitespace normalization
  3364. */
  3365. private String URILiteral(String in) throws XPathException {
  3366. return Whitespace.applyWhitespaceNormalization(Whitespace.COLLAPSE, unescape(in)).toString();
  3367. }
  3368. /**
  3369. * Lookahead one token, catching any exception thrown by the tokenizer. This
  3370. * method is only called from the query parser when switching from character-at-a-time
  3371. * mode to tokenizing mode
  3372. */
  3373. private void lookAhead() throws XPathException {
  3374. try {
  3375. t.lookAhead();
  3376. } catch (XPathException err) {
  3377. grumble(err.getMessage());
  3378. }
  3379. }
  3380. /**
  3381. * Skip whitespace.
  3382. *
  3383. * @param c the current character
  3384. * @return the first character after any whitespace
  3385. * @throws StringIndexOutOfBoundsException if the end of input is encountered
  3386. */
  3387. private char skipSpaces(char c) throws StringIndexOutOfBoundsException {
  3388. while (c == ' ' || c == '\n' || c == '\r' || c == '\t') {
  3389. c = t.nextChar();
  3390. }
  3391. return c;
  3392. }
  3393. /**
  3394. * Test whether the current character is the expected character.
  3395. *
  3396. * @param actual The character that was read
  3397. * @param expected The character that was expected
  3398. * @throws XPathException if they are different
  3399. */
  3400. private void expectChar(char actual, char expected) throws XPathException {
  3401. if (actual != expected) {
  3402. grumble("Expected '" + expected + "', found '" + actual + '\'');
  3403. }
  3404. }
  3405. /**
  3406. * Get the current language (XPath or XQuery)
  3407. */
  3408. protected String getLanguage() {
  3409. return "XQuery";
  3410. }
  3411. private static class AttributeDetails {
  3412. String value;
  3413. int startOffset;
  3414. }
  3415. private static class Import {
  3416. String namespaceURI;
  3417. List locationURIs;
  3418. }
  3419. }
  3420. //
  3421. // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
  3422. // you may not use this file except in compliance with the License. You may obtain a copy of the
  3423. // License at http://www.mozilla.org/MPL/
  3424. //
  3425. // Software distributed under the License is distributed on an "AS IS" basis,
  3426. // WITHOUT WARRANTY OF ANY KIND, either express or implied.
  3427. // See the License for the specific language governing rights and limitations under the License.
  3428. //
  3429. // The Original Code is: all this file.
  3430. //
  3431. // The Initial Developer of the Original Code is Michael H. Kay
  3432. //
  3433. // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
  3434. //
  3435. // Contributor(s): none.
  3436. //