All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.saxon.query.XQueryParser Maven / Gradle / Ivy

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2022 Saxonica Limited
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon.query;

import net.sf.saxon.Configuration;
import net.sf.saxon.event.PipelineConfiguration;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.flwor.*;
import net.sf.saxon.expr.instruct.*;
import net.sf.saxon.expr.parser.*;
import net.sf.saxon.expr.sort.GenericAtomicComparer;
import net.sf.saxon.expr.sort.SortKeyDefinition;
import net.sf.saxon.functions.*;
import net.sf.saxon.functions.registry.ConstructorFunctionLibrary;
import net.sf.saxon.lib.*;
import net.sf.saxon.om.*;
import net.sf.saxon.pattern.AnyNodeTest;
import net.sf.saxon.pattern.QNameTest;
import net.sf.saxon.pattern.UnionQNameTest;
import net.sf.saxon.s9api.HostLanguage;
import net.sf.saxon.s9api.Location;
import net.sf.saxon.serialize.CharacterMap;
import net.sf.saxon.serialize.CharacterMapIndex;
import net.sf.saxon.serialize.SerializationParamsHandler;
import net.sf.saxon.serialize.charcode.UTF16CharacterSet;
import net.sf.saxon.str.BMPString;
import net.sf.saxon.str.StringTool;
import net.sf.saxon.str.StringView;
import net.sf.saxon.style.AttributeValueTemplate;
import net.sf.saxon.trans.*;
import net.sf.saxon.tree.util.NamespaceResolverWithDefault;
import net.sf.saxon.type.*;
import net.sf.saxon.value.*;
import net.sf.saxon.z.IntHashSet;
import net.sf.saxon.z.IntPredicateProxy;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.regex.Pattern;

/**
 * This class defines extensions to the XPath parser to handle the additional
 * syntax supported in XQuery
 */
public class XQueryParser extends XPathParser {

    public final static String XQUERY10 = "1.0";
    public final static String XQUERY30 = "3.0";
    public final static String XQUERY31 = "3.1";
    public final static String XQUERY40 = "4.0";

    private boolean memoFunction = false;
    private boolean streaming = false;
    //protected String queryVersion = null;
    private int errorCount = 0;
    /*@Nullable*/ private XPathException firstError = null;
    //private int languageVersion = 31;

    /*@NotNull*/ protected Executable executable;

    private boolean foundCopyNamespaces = false;
    private boolean foundBoundarySpaceDeclaration = false;
    private boolean foundOrderingDeclaration = false;
    private boolean foundEmptyOrderingDeclaration = false;
    private boolean foundDefaultCollation = false;
    private boolean foundConstructionDeclaration = false;
    private boolean foundDefaultFunctionNamespace = false;
    private boolean foundDefaultElementNamespace = false;
    private boolean foundBaseURIDeclaration = false;
    private boolean foundContextItemDeclaration = false;
    private boolean foundDefaultDecimalFormat = false;
    private boolean preambleProcessed = false;

    public final Set importedModules = new HashSet<>(5);
    final List namespacesToBeSealed = new ArrayList<>(10);
    final List schemaImports = new ArrayList<>(5);
    final List moduleImports = new ArrayList<>(5);

    private final Set outputPropertiesSeen = new HashSet<>(4);
    private Properties parameterDocProperties;


    /**
     * Constructor for internal use: this class should be instantiated via the QueryModule
     *
     * @param languageVersion the XQuery language version, for example 31 for XQuery 3.1
     */

    public XQueryParser(int languageVersion) {
        //queryVersion = XQUERY31;
        this.languageVersion = languageVersion;
        setLanguage(ParsedLanguage.XQUERY, languageVersion);
    }

    /**
     * Create a new parser of the same kind
     *
     * @return a new parser of the same kind as this one
     */

    private XQueryParser newParser() {
        XQueryParser qp = new XQueryParser(languageVersion);
        qp.setLanguage(language, languageVersion);
        qp.setParserExtension(parserExtension);
        return qp;
    }

    /**
     * Create an XQueryExpression
     *
     * @param query      the source text of the query
     * @param mainModule the static context of the query
     * @param config     the Saxon configuration
     * @return the compiled XQuery expression
     * @throws XPathException if the expression contains static errors
     */

    /*@NotNull*/
    public XQueryExpression makeXQueryExpression(
            /*@NotNull*/ String query,
            /*@NotNull*/ QueryModule mainModule,
            /*@NotNull*/ Configuration config) throws XPathException {
        try {
            setLanguage(ParsedLanguage.XQUERY, languageVersion);
            if (config.getXMLVersion() == Configuration.XML10) {
                query = normalizeLineEndings10(query);
            } else {
                query = normalizeLineEndings11(query);
            }
            Executable exec = mainModule.getExecutable();
            if (exec == null) {
                exec = new Executable(config);
                exec.setHostLanguage(HostLanguage.XQUERY);
                exec.setTopLevelPackage(mainModule.getPackageData());
                setExecutable(exec);
                //mainModule.setExecutable(exec);
            }
            GlobalContextRequirement requirement = exec.getGlobalContextRequirement();
            if (requirement != null) {
                requirement.addRequiredItemType(mainModule.getRequiredContextItemType());
            } else if (mainModule.getRequiredContextItemType() != null
                    && mainModule.getRequiredContextItemType() != AnyItemType.getInstance()) {
                GlobalContextRequirement req = new GlobalContextRequirement();
                req.setExternal(true);
                req.addRequiredItemType(mainModule.getRequiredContextItemType());
                exec.setGlobalContextRequirement(req);
            }

//            setDefaultContainer(new SimpleContainer(mainModule.getPackageData()));

            Properties outputProps = new Properties(config.getDefaultSerializationProperties());
            if (outputProps.getProperty(OutputKeys.METHOD) == null) {
                outputProps.setProperty(OutputKeys.METHOD, "xml");
            }
            parameterDocProperties = new Properties(outputProps);
            exec.setDefaultOutputProperties(new Properties(parameterDocProperties));

            //exec.setLocationMap(new LocationMap());
            FunctionLibraryList libList = new FunctionLibraryList();
            libList.addFunctionLibrary(new ExecutableFunctionLibrary(config));
            exec.setFunctionLibrary(libList);
            // this will be changed later
            setExecutable(exec);

            setCodeInjector(mainModule.getCodeInjector());

            Expression exp = parseQuery(query, mainModule);

            if (streaming) {
                env.getConfiguration().checkLicensedFeature(Configuration.LicenseFeature.ENTERPRISE_XQUERY, "streaming", -1);
            }

            exec.fixupQueryModules(mainModule);

            // Make the XQueryExpression object

            XQueryExpression queryExp = config.makeXQueryExpression(exp, mainModule, streaming);

            // Make the function library that's available at run-time (e.g. for saxon:evaluate() and function-lookup()).
            // This includes all user-defined functions regardless of which module they are in

            FunctionLibrary userlib = exec.getFunctionLibrary();
            FunctionLibraryList lib = new FunctionLibraryList();
            lib.addFunctionLibrary(mainModule.getBuiltInFunctionSet());
            lib.addFunctionLibrary(config.getBuiltInExtensionLibraryList());
            lib.addFunctionLibrary(new ConstructorFunctionLibrary(config));
            lib.addFunctionLibrary(config.getIntegratedFunctionLibrary());
            lib.addFunctionLibrary(mainModule.getGlobalFunctionLibrary());
            config.addExtensionBinders(lib);
            lib.addFunctionLibrary(userlib);
            exec.setFunctionLibrary(lib);

            return queryExp;
        } catch (XPathException e) {
            if (!e.hasBeenReported()) {
                reportError(e);
            }
            throw e;
        }
    }

    /**
     * Normalize line endings in the source query, according to the XML 1.1 rules.
     *
     * @param in the input query
     * @return the query with line endings normalized
     */

    private static String normalizeLineEndings11(/*@NotNull*/ String in) {
        if (in.indexOf((char) 0xd) < 0 && in.indexOf((char) 0x85) < 0 && in.indexOf((char) 0x2028) < 0) {
            return in;
        }
        StringBuilder sb = new StringBuilder(in.length());
        for (int i = 0; i < in.length(); i++) {
            char ch = in.charAt(i);
            switch (ch) {
                case (char) 0x85:
                case (char) 0x2028:
                    sb.append((char) 0xa);
                    break;
                case (char) 0xd:
                    if (i < in.length() - 1 && (in.charAt(i + 1) == (char) 0xa || in.charAt(i + 1) == (char) 0x85)) {
                        sb.append((char) 0xa);
                        i++;
                    } else {
                        sb.append((char) 0xa);
                    }
                    break;
                default:
                    sb.append(ch);
                    break;
            }
        }
        return sb.toString();
    }

    /**
     * Normalize line endings in the source query, according to the XML 1.0 rules.
     *
     * @param in the input query
     * @return the query text with line endings normalized
     */

    private static String normalizeLineEndings10(/*@NotNull*/ String in) {
        if (in.indexOf((char) 0xd) < 0) {
            return in;
        }
        StringBuilder sb = new StringBuilder(in.length());
        for (int i = 0; i < in.length(); i++) {
            char ch = in.charAt(i);
            if (ch == 0xd) {
                if (i < in.length() - 1 && in.charAt(i + 1) == (char) 0xa) {
                    sb.append((char) 0xa);
                    i++;
                } else {
                    sb.append((char) 0xa);
                }
            } else {
                sb.append(ch);
            }
        }
        return sb.toString();
    }


    /**
     * Get the executable containing this expression.
     *
     * @return the executable
     */

    /*@NotNull*/
    public Executable getExecutable() {
        return executable;
    }

    /**
     * Set the executable used for this query expression
     *
     * @param exec the executable
     */

    public void setExecutable(/*@NotNull*/ Executable exec) {
        executable = exec;
    }


    /**
     * Callback to tailor the tokenizer
     */

    @Override
    protected void customizeTokenizer(Tokenizer t) {
        t.isXQuery = true;
    }


    /**
     * Say whether the query should be compiled and evaluated to use streaming.
     * This affects subsequent calls on the parseQuery() method. This option requires
     * Saxon-EE.
     *
     * @param option if true, the compiler will attempt to compile a query to be
     *               capable of executing in streaming mode. If the query cannot be streamed,
     *               a compile-time exception is reported. In streaming mode, the source
     *               document is supplied as a stream, and no tree is built in memory. The default
     *               is false.
     * @since 9.6
     */

    public void setStreaming(boolean option) {
        streaming = option;
    }

    /**
     * Ask whether the streaming option has been set, that is, whether
     * subsequent calls on parseQuery() will compile queries to be capable
     * of executing in streaming mode.
     *
     * @return true if the streaming option has been set.
     * @since 9.6
     */

    public boolean isStreaming() {
        return streaming;
    }


    /**
     * Parse a top-level Query.
     * Prolog? Expression
     *
     * @param queryString The text of the query
     * @param env         The static context
     * @return the Expression object that results from parsing
     * @throws net.sf.saxon.trans.XPathException if the expression contains a syntax error
     */

    /*@NotNull*/
    private Expression parseQuery(String queryString, QueryModule env) throws XPathException {
        this.env = Objects.requireNonNull(env);
        charChecker = env.getConfiguration().getValidCharacterChecker();
//        if (defaultContainer == null) {
//            defaultContainer = new TemporaryContainer(env.getConfiguration(), env.getLocationMap(), 1);
//        }
        language = ParsedLanguage.XQUERY;
        t = new Tokenizer();
        t.languageLevel = languageVersion;
        t.isXQuery = true;
        try {
            t.tokenize(Objects.requireNonNull(queryString), 0, -1);
        } catch (XPathException err) {
            grumble(err.getMessage());
        }
        parseVersionDeclaration();
        allowXPath40Syntax =
                t.allowSaxonExtensions =
                        allowXPath40Syntax || env.getConfiguration().getBooleanProperty(Feature.ALLOW_SYNTAX_EXTENSIONS);

        QNameParser qp = new QNameParser(env.getLiveNamespaceResolver())
                .withAcceptEQName(true)
                .withUnescaper(new Unescaper(env.getConfiguration().getValidCharacterChecker()));

        setQNameParser(qp);

        parseProlog();
        processPreamble();

        Expression exp = parseExpression();
        exp = makeTracer(exp, null);

        // Diagnostic code - show the expression before any optimizations
//        ExpressionPresenter ep = ExpressionPresenter.make(env.getConfiguration());
//        exp.explain(ep);
//        ep.close();
        // End of diagnostic code

        if (t.currentToken != Token.EOF) {
            grumble("Unexpected token " + currentTokenDisplay() + " beyond end of query");
        }
        setLocation(exp);
        ExpressionTool.setDeepRetainedStaticContext(exp, env.makeRetainedStaticContext());
        if (errorCount == 0) {
            return exp;
        } else {
            XPathException err = new XPathException("One or more static errors were reported during query analysis");
            err.setHasBeenReported(true);
            err.setErrorCodeQName(firstError.getErrorCodeQName());   // largely for the XQTS test driver
            throw err;
        }
    }

    /**
     * Parse a library module.
     * Prolog? Expression
     *
     * @param queryString The text of the library module.
     * @param env         The static context. The result of parsing
     *                    a library module is that the static context is populated with a set of function
     *                    declarations and variable declarations. Each library module must have its own
     *                    static context objext.
     * @throws XPathException if the expression contains a syntax error
     */

    public final void parseLibraryModule(String queryString, /*@NotNull*/ QueryModule env)
            throws XPathException {
        this.env = env;
        final Configuration config = env.getConfiguration();
        charChecker = config.getValidCharacterChecker();
        if (config.getXMLVersion() == Configuration.XML10) {
            queryString = normalizeLineEndings10(queryString);
        } else {
            queryString = normalizeLineEndings11(queryString);
        }
        Executable exec = env.getExecutable();
        if (exec == null) {
            throw new IllegalStateException("Query library module has no associated Executable");
        }
        executable = exec;
//        defaultContainer = new SimpleContainer(env.getPackageData());
        t = new Tokenizer();
        t.languageLevel = languageVersion;
        t.isXQuery = true;
        QNameParser qp = new QNameParser(env.getLiveNamespaceResolver())
                .withAcceptEQName(true)
                .withUnescaper(new Unescaper(config.getValidCharacterChecker()));
        setQNameParser(qp);
        try {
            t.tokenize(queryString, 0, -1);
        } catch (XPathException err) {
            grumble(err.getMessage());
        }
        parseVersionDeclaration();
        allowXPath40Syntax =
                t.allowSaxonExtensions =
                        allowXPath40Syntax || env.getConfiguration().getBooleanProperty(Feature.ALLOW_SYNTAX_EXTENSIONS);

        //t.setAllowExpandedQNameSyntax("3.0".equals(queryVersion));
        if (t.currentToken != Token.MODULE_NAMESPACE) {
            if (t.currentToken == Token.EOF) {
                grumble("The file imported for module " + env.getModuleNamespace() +
                                (queryString.trim().length() == 0 ? " is empty" : " has no significant content"));
            } else {
                grumble("The file imported for module " + env.getModuleNamespace() + " is not a valid XQuery library module. " +
                                "The content starts: " + Err.truncate30(StringView.of(queryString.substring(t.currentTokenStartOffset))));
            }
        }
        parseModuleDeclaration();
        parseProlog();
        processPreamble();
        if (t.currentToken != Token.EOF) {
            grumble("Unrecognized content found after the variable and function declarations in a library module");
        }
        if (errorCount != 0) {
            XPathException err = new XPathException("Static errors were reported in the imported library module");
            err.setErrorCodeQName(firstError.getErrorCodeQName());
            throw err;
        }
    }


    private void reportError(/*@NotNull*/ XPathException exception) throws XPathException {
        errorCount++;
        if (firstError == null) {
            firstError = exception;
        }
        ((QueryModule) env).reportStaticError(exception);
        throw exception;
    }

    private static final Pattern encNamePattern = Pattern.compile("^[A-Za-z]([A-Za-z0-9._\\x2D])*$");

    /**
     * Parse the version declaration if present.
     *
     * @throws XPathException in the event of a syntax error.
     */
    private void parseVersionDeclaration() throws XPathException {
        if (t.currentToken == Token.XQUERY_VERSION) {
            nextToken();
            expect(Token.STRING_LITERAL);
            String queryVersion = unescape(t.currentTokenValue).toString();
            String[] allowedVersions = new String[]{"1.0", "3.0", "3.1", "4.0"};
            if (Arrays.binarySearch(allowedVersions, queryVersion) < 0) {
//            if (!queryVersion.trim().matches("[0-9]+\\.[0-9]+")) {
                grumble("Invalid XQuery version " + queryVersion, "XQST0031");
            }
            if (queryVersion.equals("4.0")) {
                allowXPath40Syntax = true;
            }
//            if (XQUERY10.equals(queryVersion)) {
//                // no action
//            } else if (XQUERY30.equals(queryVersion) || "1.1".equals(queryVersion)) {
//                queryVersion = XQUERY30;
//                if (((QueryModule) env).getLanguageVersion() != 30 && ((QueryModule) env).getLanguageVersion() != 31) {
//                    grumble("XQuery 3.0 was not enabled when invoking Saxon", "XQST0031");
//                    queryVersion = XQUERY10;
//                }
//            } else if(XQUERY31.equals(queryVersion)) {
//                queryVersion = XQUERY31;
//                if (((QueryModule) env).getLanguageVersion() != 31) {
//                    grumble("XQuery 3.1 was not enabled when invoking Saxon", "XQST0031");
//                    queryVersion = XQUERY30;
//                }
//            } else {
//                grumble("Unsupported XQuery version " + queryVersion, "XQST0031");
//                queryVersion = XQUERY10;
//            }
            nextToken();
            if ("encoding".equals(t.currentTokenValue)) {
                nextToken();
                expect(Token.STRING_LITERAL);
                if (!encNamePattern.matcher(unescape(t.currentTokenValue)).matches()) {
                    grumble("Encoding name contains invalid characters", "XQST0087");
                }
                // we ignore the encoding now: it was handled earlier, while decoding the byte stream
                nextToken();
            }
            expect(Token.SEMICOLON);
            nextToken();
        } else {
//            if (((QueryModule) env).getLanguageVersion()== 30) {
//                queryVersion = XQUERY30;
//            } else if (((QueryModule) env).getLanguageVersion() == 31) {
//                queryVersion = XQUERY31;
//            } else {
//                queryVersion = XQUERY10;
//            }
            if (t.currentToken == Token.XQUERY_ENCODING) {
                nextToken();
                expect(Token.STRING_LITERAL);
                if (!encNamePattern.matcher(t.currentTokenValue).matches()) {
                    grumble("Encoding name contains invalid characters", "XQST0087");
                }
                // we ignore the encoding now: it was handled earlier, while decoding the byte stream
                nextToken();
                expect(Token.SEMICOLON);
                nextToken();
            }
        }
    }

    /**
     * In a library module, parse the module declaration
     * Syntax: <"module" "namespace"> prefix "=" uri ";"
     *
     * @throws XPathException in the event of a syntax error.
     */

    private void parseModuleDeclaration() throws XPathException {
        expect(Token.MODULE_NAMESPACE);
        nextToken();
        expect(Token.NAME);
        String prefix = t.currentTokenValue;
        nextToken();
        expect(Token.EQUALS);
        nextToken();
        expect(Token.STRING_LITERAL);
        String uri = uriLiteral(t.currentTokenValue);
        checkProhibitedPrefixes(prefix, uri);
        if (uri.isEmpty()) {
            grumble("Module namespace cannot be \"\"", "XQST0088");
            uri = "http://saxon.fallback.namespace/";   // for error recovery
        }
        nextToken();
        expect(Token.SEMICOLON);
        nextToken();
        try {
            ((QueryModule) env).setModuleNamespace(uri);
            ((QueryModule) env).declarePrologNamespace(prefix, uri);
            executable.addQueryLibraryModule((QueryModule) env);
        } catch (XPathException err) {
            err.setLocator(makeLocation());
            reportError(err);
        }
    }

    /**
     * Parse the query prolog. This method, and its subordinate methods which handle
     * individual declarations in the prolog, cause the static context to be updated
     * with relevant context information. On exit, t.currentToken is the first token
     * that is not recognized as being part of the prolog.
     *
     * @throws XPathException in the event of a syntax error.
     */

    private void parseProlog() throws XPathException {
        //boolean allowSetters = true;
        boolean allowModuleDecl = true;
        boolean allowDeclarations = true;

        while (true) {
            try {
                if (t.currentToken == Token.MODULE_NAMESPACE) {
                    String uri = ((QueryModule) env).getModuleNamespace();
                    if (uri == null) {
                        grumble("Module declaration must not be used in a main module");
                    } else {
                        grumble("Module declaration appears more than once");
                    }
                    if (!allowModuleDecl) {
                        grumble("Module declaration must precede other declarations in the query prolog");
                    }
                }
                allowModuleDecl = false;
                switch (t.currentToken) {
                    case Token.DECLARE_NAMESPACE:
                        if (!allowDeclarations) {
                            grumble("Namespace declarations cannot follow variables, functions, or options");
                        }
                        //allowSetters = false;
                        parseNamespaceDeclaration();
                        break;
                    case Token.DECLARE_ANNOTATED:
                        // we have read "declare %"
                        processPreamble();
                        if (allowDeclarations) {
                            sealNamespaces(namespacesToBeSealed, env.getConfiguration());
                            allowDeclarations = false;
                        }
                        nextToken();
                        expect(Token.PERCENT);
                        AnnotationList annotationList = parseAnnotationsList();
                        if (isKeyword("function")) {
                            annotationList.check(env.getConfiguration(), "DF");
                            parseFunctionDeclaration(annotationList);
                        } else if (isKeyword("variable")) {
                            annotationList.check(env.getConfiguration(), "DV");
                            parseVariableDeclaration(annotationList);
                        } else {
                            grumble("Annotations can appear only in 'declare variable' and 'declare function'");
                        }
                        break;
                    case Token.DECLARE_DEFAULT:
                        nextToken();
                        expect(Token.NAME);
                        switch (t.currentTokenValue) {
                            case "element":
                                if (!allowDeclarations) {
                                    grumble("Namespace declarations cannot follow variables, functions, or options");
                                }
                                //allowSetters = false;
                                parseDefaultElementNamespace();
                                break;
                            case "function":
                                if (!allowDeclarations) {
                                    grumble("Namespace declarations cannot follow variables, functions, or options");
                                }
                                //allowSetters = false;
                                parseDefaultFunctionNamespace();
                                break;
                            case "collation":
                                if (!allowDeclarations) {
                                    grumble("Collation declarations must appear earlier in the prolog");
                                }
                                parseDefaultCollation();
                                break;
                            case "order":
                                if (!allowDeclarations) {
                                    grumble("Order declarations must appear earlier in the prolog");
                                }
                                parseDefaultOrder();
                                break;
                            case "decimal-format":
                                nextToken();
                                parseDefaultDecimalFormat();
                                break;
                            default:
                                grumble("After 'declare default', expected 'element', 'function', or 'collation'");
                                break;
                        }
                        break;
                    case Token.DECLARE_BOUNDARY_SPACE:
                        if (!allowDeclarations) {
                            grumble("'declare boundary-space' must appear earlier in the query prolog");
                        }
                        parseBoundarySpaceDeclaration();
                        break;
                    case Token.DECLARE_ORDERING:
                        if (!allowDeclarations) {
                            grumble("'declare ordering' must appear earlier in the query prolog");
                        }
                        parseOrderingDeclaration();
                        break;
                    case Token.DECLARE_COPY_NAMESPACES:
                        if (!allowDeclarations) {
                            grumble("'declare copy-namespaces' must appear earlier in the query prolog");
                        }
                        parseCopyNamespacesDeclaration();
                        break;
                    case Token.DECLARE_BASEURI:
                        if (!allowDeclarations) {
                            grumble("'declare base-uri' must appear earlier in the query prolog");
                        }
                        parseBaseURIDeclaration();
                        break;
                    case Token.DECLARE_DECIMAL_FORMAT:
                        if (!allowDeclarations) {
                            grumble("'declare decimal-format' must appear earlier in the query prolog");
                        }
                        parseDecimalFormatDeclaration();
                        break;
                    case Token.IMPORT_SCHEMA:
                        //allowSetters = false;
                        if (!allowDeclarations) {
                            grumble("Import schema must appear earlier in the prolog");
                        }
                        parseSchemaImport();
                        break;
                    case Token.IMPORT_MODULE:
                        //allowSetters = false;
                        if (!allowDeclarations) {
                            grumble("Import module must appear earlier in the prolog");
                        }
                        parseModuleImport();
                        break;
                    case Token.DECLARE_VARIABLE:
                        //allowSetters = false;
                        if (allowDeclarations) {
                            sealNamespaces(namespacesToBeSealed, env.getConfiguration());
                            allowDeclarations = false;
                        }
                        processPreamble();
                        parseVariableDeclaration(AnnotationList.EMPTY);
                        break;
                    case Token.DECLARE_CONTEXT:
                        //allowSetters = false;
                        if (allowDeclarations) {
                            sealNamespaces(namespacesToBeSealed, env.getConfiguration());
                            allowDeclarations = false;
                        }
                        processPreamble();
                        parseContextItemDeclaration();
                        break;
                    case Token.DECLARE_FUNCTION:
                        if (allowDeclarations) {
                            sealNamespaces(namespacesToBeSealed, env.getConfiguration());
                            allowDeclarations = false;
                        }
                        processPreamble();
                        parseFunctionDeclaration(AnnotationList.EMPTY);
                        break;
                    case Token.DECLARE_UPDATING:
                        nextToken();
                        if (!isKeyword("function")) {
                            grumble("expected 'function' after 'declare updating");
                        }
                        if (allowDeclarations) {
                            sealNamespaces(namespacesToBeSealed, env.getConfiguration());
                            allowDeclarations = false;
                        }
                        processPreamble();
                        parserExtension.parseUpdatingFunctionDeclaration(this);
                        break;
                    case Token.DECLARE_OPTION:
                        if (allowDeclarations) {
                            sealNamespaces(namespacesToBeSealed, env.getConfiguration());
                            allowDeclarations = false;
                        }
                        parseOptionDeclaration();
                        break;
                    case Token.DECLARE_TYPE:
                        checkSyntaxExtensions("declare type");
                        if (allowDeclarations) {
                            sealNamespaces(namespacesToBeSealed, env.getConfiguration());
                            allowDeclarations = false;
                        }
                        parseTypeAliasDeclaration();
                        break;
                    case Token.DECLARE_CONSTRUCTION:
                        if (!allowDeclarations) {
                            grumble("'declare construction' must appear earlier in the query prolog");
                        }
                        parseConstructionDeclaration();
                        break;
                    case Token.DECLARE_REVALIDATION:
                        if (!allowDeclarations) {
                            grumble("'declare revalidation' must appear earlier in the query prolog");
                        }
                        parserExtension.parseRevalidationDeclaration(this);
                        break;
                    case Token.EOF:
                        String uri = ((QueryModule) env).getModuleNamespace();
                        if (uri == null) {
                            grumble("The main module must contain a query expression after any declarations in the prolog");
                        } else {
                            return;
                        }
                        break;
                    default:
                        return;
                }
                expect(Token.SEMICOLON);
                nextToken();
            } catch (XPathException err) {
                if (err.getLocator() == null) {
                    err.setLocator(makeLocation());
                }
                if (!err.hasBeenReported()) {
                    errorCount++;
                    if (firstError == null) {
                        firstError = err;
                    }
                    reportError(err);
                }
                // we've reported an error, attempt to recover by skipping to the
                // next semicolon
                while (t.currentToken != Token.SEMICOLON) {
                    nextToken();
                    if (t.currentToken == Token.EOF) {
                        return;
                    } else if (t.currentToken == Token.RCURLY) {
                        t.lookAhead();
                    } else if (t.currentToken == Token.TAG) {
                        parsePseudoXML(true);
                    }
                }
                nextToken();
            }
        }
    }

    /**
     * Parse the annotations that can appear in a variable or function declaration
     *
     * @return the annotations as a list
     * @throws XPathException in the event of a syntax error
     */

    @Override
    protected AnnotationList parseAnnotationsList() throws XPathException {
        // we have read "declare" and have seen "%" as lookahead
        ArrayList annotations = new ArrayList<>();
        int options = 0;
        while (true) {
            t.setState(Tokenizer.BARE_NAME_STATE);
            nextToken();
            expect(Token.NAME);
            t.setState(Tokenizer.DEFAULT_STATE);
            StructuredQName qName;
            String uri;
            if (t.currentTokenValue.indexOf(':') < 0) {
                uri = NamespaceConstant.XQUERY;
                qName = new StructuredQName("", uri, t.currentTokenValue);
            } else {
                qName = makeStructuredQName(t.currentTokenValue, "");
                assert qName != null;
                uri = qName.getURI();
            }
            Annotation annotation = new Annotation(qName);
            if (uri.equals(NamespaceConstant.XQUERY)) {
                if (!qName.equals(Annotation.PRIVATE) && !qName.equals(Annotation.PUBLIC) &&
                        !qName.equals(Annotation.UPDATING) && !qName.equals(Annotation.SIMPLE)) {
                    grumble("Unrecognized variable or function annotation " + qName.getDisplayName(), "XQST0045");
                }
                annotation.addAnnotationParameter(new Int64Value(options));
            } else if (isReservedInQuery(uri)) {
                grumble("The annotation " + t.currentTokenValue + " is in a reserved namespace", "XQST0045");
            } else if (uri.equals("")) {
                grumble("The annotation " + t.currentTokenValue + " is in no namespace", "XQST0045");
            } else {
                // no action - ignore namespaced annotations
            }
            nextToken();
            if (t.currentToken == Token.LPAR) {
                nextToken();
                if (t.currentToken == Token.RPAR) {
                    grumble("Annotation parameter list cannot be empty");
                }
                while (true) {
                    // nextToken();
                    Literal arg;
                    switch (t.currentToken) {
                        case Token.STRING_LITERAL:
                            arg = (Literal) parseStringLiteral(false);
                            break;

                        case Token.NUMBER:
                            arg = (Literal) parseNumericLiteral(false);
                            break;

                        default:
                            grumble("Annotation parameter must be a literal");
                            return null;
                    }
                    GroundedValue val = arg.getGroundedValue();
                    if (val instanceof StringValue || val instanceof NumericValue) {
                        annotation.addAnnotationParameter((AtomicValue) val);
                    } else {
                        grumble("Annotation parameter must be a string or number");
                    }

                    if (t.currentToken == Token.RPAR) {
                        nextToken();
                        break;
                    }
                    expect(Token.COMMA);
                    nextToken();
                }
            }
            annotations.add(annotation);
            if (t.currentToken != Token.PERCENT) {
                return new AnnotationList(annotations);
            }
        }
    }


    private void sealNamespaces(/*@NotNull*/ List namespacesToBeSealed, /*@NotNull*/ Configuration config) {
        for (String ns : namespacesToBeSealed) {
            config.sealNamespace(ns);
        }
    }

    /**
     * Method called once the setters have been read to do tidying up that can't be done until we've got
     * to the end
     *
     * @throws XPathException if parsing fails
     */

    private void processPreamble() throws XPathException {
        if (preambleProcessed) {
            return;
        }
        preambleProcessed = true;
        if (foundDefaultCollation) {
            String collationName = env.getDefaultCollationName();
            URI collationURI;
            try {
                collationURI = new URI(collationName);
                if (!collationURI.isAbsolute()) {
                    URI base = new URI(env.getStaticBaseURI());
                    collationURI = base.resolve(collationURI);
                    collationName = collationURI.toString();
                }
            } catch (URISyntaxException err) {
                grumble("Default collation name '" + collationName + "' is not a valid URI", "XQST0046");
                collationName = NamespaceConstant.CODEPOINT_COLLATION_URI;
            }
            if (env.getConfiguration().getCollation(collationName) == null) {
                grumble("Default collation name '" + collationName + "' is not a recognized collation", "XQST0038");
                collationName = NamespaceConstant.CODEPOINT_COLLATION_URI;
            }
            ((QueryModule) env).setDefaultCollationName(collationName);
        }
        for (Import imp : schemaImports) {
            try {
                applySchemaImport(imp);
            } catch (XPathException err) {
                if (!err.hasBeenReported()) {
                    err.maybeSetLocation(makeLocation(imp.offset));
                    throw err;
                }
            }
        }
        for (Import imp : moduleImports) {
            try {
                applyModuleImport(imp);
            } catch (XPathException err) {
                if (!err.hasBeenReported()) {
                    err.maybeSetLocation(makeLocation(imp.offset));
                    throw err;
                }
            }
        }
    }

    private void parseDefaultCollation() throws XPathException {
        // <"default" "collation"> StringLiteral
        if (foundDefaultCollation) {
            grumble("default collation appears more than once", "XQST0038");
        }
        foundDefaultCollation = true;
        nextToken();
        expect(Token.STRING_LITERAL);
        String uri = uriLiteral(t.currentTokenValue);
        ((QueryModule) env).setDefaultCollationName(uri);
        nextToken();
    }

    /**
     * Parse "declare default order empty (least|greatest)"
     *
     * @throws XPathException if parsing fails
     */
    private void parseDefaultOrder() throws XPathException {
        if (foundEmptyOrderingDeclaration) {
            grumble("empty ordering declaration appears more than once", "XQST0069");
        }
        foundEmptyOrderingDeclaration = true;
        nextToken();
        if (!isKeyword("empty")) {
            grumble("After 'declare default order', expected keyword 'empty'");
        }
        nextToken();
        if (isKeyword("least")) {
            ((QueryModule) env).setEmptyLeast(true);
        } else if (isKeyword("greatest")) {
            ((QueryModule) env).setEmptyLeast(false);
        } else {
            grumble("After 'declare default order empty', expected keyword 'least' or 'greatest'");
        }
        nextToken();
    }

    /**
     * Parse the "declare xmlspace" declaration.
     * Syntax: <"declare" "boundary-space"> ("preserve" | "strip")
     *
     * @throws XPathException if a static error is encountered
     */

    private void parseBoundarySpaceDeclaration() throws XPathException {
        if (foundBoundarySpaceDeclaration) {
            grumble("'declare boundary-space' appears more than once", "XQST0068");
        }
        foundBoundarySpaceDeclaration = true;
        nextToken();
        expect(Token.NAME);
        if ("preserve".equals(t.currentTokenValue)) {
            ((QueryModule) env).setPreserveBoundarySpace(true);
        } else if ("strip".equals(t.currentTokenValue)) {
            ((QueryModule) env).setPreserveBoundarySpace(false);
        } else {
            grumble("boundary-space must be 'preserve' or 'strip'");
        }
        nextToken();
    }

    /**
     * Parse the "declare ordering" declaration.
     * Syntax: <"declare" "ordering"> ("ordered" | "unordered")
     *
     * @throws XPathException if parsing fails
     */

    private void parseOrderingDeclaration() throws XPathException {
        if (foundOrderingDeclaration) {
            grumble("ordering mode declaration appears more than once", "XQST0065");
        }
        foundOrderingDeclaration = true;
        nextToken();
        expect(Token.NAME);
        if ("ordered".equals(t.currentTokenValue)) {
            // no action
        } else if ("unordered".equals(t.currentTokenValue)) {
            // no action
        } else {
            grumble("ordering mode must be 'ordered' or 'unordered'");
        }
        nextToken();
    }

    /**
     * Parse the "declare copy-namespaces" declaration.
     * Syntax: <"declare" "copy-namespaces"> ("preserve" | "no-preserve") "," ("inherit" | "no-inherit")
     *
     * @throws XPathException if a static error is encountered
     */

    private void parseCopyNamespacesDeclaration() throws XPathException {
        if (foundCopyNamespaces) {
            grumble("declare copy-namespaces appears more than once", "XQST0055");
        }
        foundCopyNamespaces = true;
        nextToken();
        expect(Token.NAME);
        if ("preserve".equals(t.currentTokenValue)) {
            ((QueryModule) env).setPreserveNamespaces(true);
        } else if ("no-preserve".equals(t.currentTokenValue)) {
            ((QueryModule) env).setPreserveNamespaces(false);
        } else {
            grumble("copy-namespaces must be followed by 'preserve' or 'no-preserve'");
        }
        nextToken();
        expect(Token.COMMA);
        nextToken();
        expect(Token.NAME);
        if ("inherit".equals(t.currentTokenValue)) {
            ((QueryModule) env).setInheritNamespaces(true);
        } else if ("no-inherit".equals(t.currentTokenValue)) {
            ((QueryModule) env).setInheritNamespaces(false);
        } else {
            grumble("After the comma in the copy-namespaces declaration, expected 'inherit' or 'no-inherit'");
        }
        nextToken();
    }


    /**
     * Parse the "declare construction" declaration.
     * Syntax: <"declare" "construction"> ("preserve" | "strip")
     *
     * @throws XPathException if parsing fails
     */

    private void parseConstructionDeclaration() throws XPathException {
        if (foundConstructionDeclaration) {
            grumble("declare construction appears more than once", "XQST0067");
        }
        foundConstructionDeclaration = true;
        nextToken();
        expect(Token.NAME);
        int val;
        if ("preserve".equals(t.currentTokenValue)) {
            val = Validation.PRESERVE;
//            if (!env.getExecutable().isSchemaAware()) {
//                grumble("construction mode preserve is allowed only with a schema-aware query");
//            }
        } else if ("strip".equals(t.currentTokenValue)) {
            val = Validation.STRIP;
        } else {
            grumble("construction mode must be 'preserve' or 'strip'");
            val = Validation.STRIP;
        }
        ((QueryModule) env).setConstructionMode(val);
        nextToken();
    }

    /**
     * Parse the "declare revalidation" declaration.
     * Syntax: not allowed unless XQuery update is in use
     *
     * @throws XPathException if the syntax is incorrect, or is not allowed in this XQuery processor
     */

    protected void parseRevalidationDeclaration() throws XPathException {
        grumble("declare revalidation is allowed only in XQuery Update");
    }

    /**
     * Parse (and process) the schema import declaration.
     * SchemaImport ::=	"import" "schema" SchemaPrefix? URILiteral ("at" URILiteral ("," URILiteral)*)?
     * SchemaPrefix ::=	("namespace" NCName "=") | ("default" "element" "namespace")
     *
     * @throws XPathException if parsing fails
     */

    private void parseSchemaImport() throws XPathException {
        ensureSchemaAware("import schema");
        Import sImport = new Import();
        String prefix = null;
        sImport.namespaceURI = null;
        sImport.locationURIs = new ArrayList<>(5);
        sImport.offset = t.currentTokenStartOffset;
        nextToken();
        if (isKeyword("namespace")) {
            prefix = readNamespaceBinding();
        } else if (isKeyword("default")) {
            nextToken();
            if (!isKeyword("element")) {
                grumble("In 'import schema', expected 'element namespace'");
            }
            nextToken();
            if (!isKeyword("namespace")) {
                grumble("In 'import schema', expected keyword 'namespace'");
            }
            nextToken();
            prefix = "";
        }
        if (t.currentToken == Token.STRING_LITERAL) {
            String uri = uriLiteral(t.currentTokenValue);
            checkProhibitedPrefixes(prefix, uri);
            sImport.namespaceURI = uri;
            nextToken();
            if (isKeyword("at")) {
                nextToken();
                expect(Token.STRING_LITERAL);
                sImport.locationURIs.add(uriLiteral(t.currentTokenValue));
                nextToken();
                while (t.currentToken == Token.COMMA) {
                    nextToken();
                    expect(Token.STRING_LITERAL);
                    sImport.locationURIs.add(uriLiteral(t.currentTokenValue));
                    nextToken();
                }
            } else if (t.currentToken != Token.SEMICOLON) {
                grumble("After the target namespace URI, expected 'at' or ';'");
            }
        } else {
            grumble("After 'import schema', expected 'namespace', 'default', or a string-literal");
        }
        if (prefix != null) {
            try {
                if (prefix.isEmpty()) {
                    ((QueryModule) env).setDefaultElementNamespace(sImport.namespaceURI);
                } else {
                    if (sImport.namespaceURI == null || "".equals(sImport.namespaceURI)) {
                        grumble("A prefix cannot be bound to the null namespace", "XQST0057");
                    }
                    ((QueryModule) env).declarePrologNamespace(prefix, sImport.namespaceURI);
                }
            } catch (XPathException err) {
                err.setLocator(makeLocation());
                reportError(err);
            }
        }
        for (Object schemaImport : schemaImports) {
            Import imp = (Import) schemaImport;
            if (imp.namespaceURI.equals(sImport.namespaceURI)) {
                grumble("Schema namespace '" + sImport.namespaceURI + "' is imported more than once", "XQST0058");
                break;
            }
        }

        schemaImports.add(sImport);

    }

    private String readNamespaceBinding() throws XPathException {
        t.setState(Tokenizer.DEFAULT_STATE);
        nextToken();
        expect(Token.NAME);
        String prefix = t.currentTokenValue;
        nextToken();
        expect(Token.EQUALS);
        nextToken();
        return prefix;
    }

    protected void ensureSchemaAware(String featureName) throws XPathException {
        if (!env.getConfiguration().isLicensedFeature(Configuration.LicenseFeature.ENTERPRISE_XQUERY)) {
            throw new XPathException("This Saxon version and license does not allow use of '" + featureName + "'", "XQST0009");
        }
        env.getConfiguration().checkLicensedFeature(Configuration.LicenseFeature.ENTERPRISE_XQUERY, featureName, -1);
        getExecutable().setSchemaAware(true);
        getStaticContext().getPackageData().setSchemaAware(true);
    }

    private void applySchemaImport(/*@NotNull*/ Import sImport) throws XPathException {

        // Do the importing

        Configuration config = env.getConfiguration();
        //noinspection SynchronizationOnLocalVariableOrMethodParameter
        synchronized(config) {
            if (!config.isSchemaAvailable(sImport.namespaceURI)) {
                if (!sImport.locationURIs.isEmpty()) {
                    try {
                        PipelineConfiguration pipe = config.makePipelineConfiguration();
                        config.readMultipleSchemas(pipe, env.getStaticBaseURI(), sImport.locationURIs, sImport.namespaceURI);
                        namespacesToBeSealed.add(sImport.namespaceURI);
                    } catch (SchemaException err) {
                        grumble("Error in schema " + sImport.namespaceURI + ": " + err.getMessage(), "XQST0059", sImport.offset);
                    }
                } else if (sImport.namespaceURI.equals(NamespaceConstant.XML) ||
                        sImport.namespaceURI.equals(NamespaceConstant.FN) ||
                        sImport.namespaceURI.equals(NamespaceConstant.SCHEMA_INSTANCE)) {
                    config.addSchemaForBuiltInNamespace(sImport.namespaceURI);
                } else {
                    grumble("Unable to locate requested schema " + sImport.namespaceURI, "XQST0059", sImport.offset);
                }
            }
            ((QueryModule) env).addImportedSchema(sImport.namespaceURI, env.getStaticBaseURI(), sImport.locationURIs);
        }
    }

    /**
     * Parse (and expand) the module import declaration.
     * Syntax: "import" "module" ("namespace" NCName "=")? uri ("at" uri ("," uri)*)? ";"
     *
     * @throws net.sf.saxon.trans.XPathException if a static error is encountered
     */

    private void parseModuleImport() throws XPathException {
        QueryModule thisModule = (QueryModule) env;
        Import mImport = new Import();
        String prefix = null;
        mImport.namespaceURI = null;
        mImport.locationURIs = new ArrayList<>(5);
        mImport.offset = t.currentTokenStartOffset;
        nextToken();
        if (t.currentToken == Token.NAME && t.currentTokenValue.equals("namespace")) {
            prefix = readNamespaceBinding();
        }
        if (t.currentToken == Token.STRING_LITERAL) {
            String uri = uriLiteral(t.currentTokenValue);
            checkProhibitedPrefixes(prefix, uri);
            mImport.namespaceURI = uri;
            if (mImport.namespaceURI.isEmpty()) {
                grumble("Imported module namespace cannot be \"\"", "XQST0088");
                mImport.namespaceURI = "http://saxon.fallback.namespace/line" + t.getLineNumber();   // for error recovery
            }
            if (importedModules.contains(mImport.namespaceURI)) {
                grumble("Two 'import module' declarations specify the same module namespace", "XQST0047");
            }
            importedModules.add(mImport.namespaceURI);
            ((QueryModule) env).addImportedNamespace(mImport.namespaceURI);
            nextToken();
            if (isKeyword("at")) {
                do {
                    nextToken();
                    expect(Token.STRING_LITERAL);
                    mImport.locationURIs.add(uriLiteral(t.currentTokenValue));
                    nextToken();
                } while (t.currentToken == Token.COMMA);
            }
        } else {
            grumble("After 'import module', expected 'namespace' or a string-literal");
        }
        if (prefix != null) {
            try {
                if (mImport.namespaceURI.equals(thisModule.getModuleNamespace()) &&
                        mImport.namespaceURI.equals(thisModule.checkURIForPrefix(prefix))) {
                    // then do nothing: a duplicate declaration in this situation is not an error
                } else {
                    thisModule.declarePrologNamespace(prefix, mImport.namespaceURI);
                }
            } catch (XPathException err) {
                err.setLocator(makeLocation());
                reportError(err);
            }
        }

//        // Check that this import would not create a cycle involving a change of namespace
//        if (!disableCycleChecks) {
//            if (!mImport.namespaceURI.equals(((QueryModule)env).getModuleNamespace())) {
//                QueryModule parent = (QueryModule)env;
//                if (!parent.mayImport(mImport.namespaceURI)) {
//                    StaticError err = new StaticError("A module cannot import itself directly or indirectly, unless all modules in the cycle are in the same namespace");
//                    err.setErrorCode("XQST0073");
//                    throw err;
//                }
//            }
//        }

        moduleImports.add(mImport);
    }

    private void applyModuleImport(/*@NotNull*/ Import mImport) throws XPathException {
        List existingModules;

        // resolve the location URIs against the base URI
        for (int i = 0; i < mImport.locationURIs.size(); i++) {
            try {
                String uri = mImport.locationURIs.get(i);
                URI abs = ResolveURI.makeAbsolute(uri, env.getStaticBaseURI());
                mImport.locationURIs.set(i, abs.toString());
            } catch (URISyntaxException e) {
                grumble("Invalid URI " + mImport.locationURIs.get(i) + ": " + e.getMessage(), "XQST0046", mImport.offset);
            }
        }

        // See if the URI is that of a separately-compiled query library
        QueryLibrary lib = ((QueryModule) env).getUserQueryContext().getCompiledLibrary(mImport.namespaceURI);
        if (lib != null) {
            executable.addQueryLibraryModule(lib);
            existingModules = new ArrayList<>();
            existingModules.add(lib);
            lib.link((QueryModule) env);

        } else if (!env.getConfiguration().getBooleanProperty(Feature.XQUERY_MULTIPLE_MODULE_IMPORTS)) {
            // Unless this configuration option is set, if we already know a module with the right module URI, then we
            // use it irrespective of its location URI.
            List list = executable.getQueryLibraryModules(mImport.namespaceURI);
            if (list != null && !list.isEmpty()) {
                return;
            }
        } else {

            for (int h = mImport.locationURIs.size() - 1; h >= 0; h--) {
                if (executable.isQueryLocationHintProcessed(mImport.locationURIs.get(h))) {
                    mImport.locationURIs.remove(h);
                }
            }

        }

        // If there are no location URIs left, and we already know a module with the right module URI.

        if (mImport.locationURIs.isEmpty()) {
            List list = executable.getQueryLibraryModules(mImport.namespaceURI);
            if (list != null && !list.isEmpty()) {
                return;
            }
        }

        // Call the module URI resolver to find the remaining modules

        ModuleURIResolver resolver = ((QueryModule) env).getUserQueryContext().getModuleURIResolver();

        String[] hints = new String[mImport.locationURIs.size()];
        for (int h = 0; h < hints.length; h++) {
            hints[h] = mImport.locationURIs.get(h);
        }
        StreamSource[] sources = null;
        if (resolver != null) {
            try {
                sources = resolver.resolve(mImport.namespaceURI, env.getStaticBaseURI(), hints);
            } catch (XPathException err) {
                grumble("Failed to resolve URI of imported module: " + err.getMessage(), "XQST0059", mImport.offset);
            }
        }
        if (sources == null) {
            resolver = env.getConfiguration().getStandardModuleURIResolver();
            sources = resolver.resolve(mImport.namespaceURI, env.getStaticBaseURI(), hints);
        }

        for (String hint : mImport.locationURIs) {
            executable.addQueryLocationHintProcessed(hint);
        }

        for (int m = 0; m < sources.length; m++) {
            StreamSource ss = sources[m];
            String baseURI = ss.getSystemId();
            if (baseURI == null) {
                if (m < hints.length) {
                    baseURI = hints[m];
                } else {
                    baseURI = env.getStaticBaseURI();
                    //grumble("No base URI available for imported module", "XQST0059", mImport.offset);
                }
                ss.setSystemId(baseURI);
            }

            // Although the module hadn't been loaded when we started, it might have been loaded since, as
            // a result of a reference from another imported module.
            // TODO: use similar logic when loading schema modules
            existingModules = executable.getQueryLibraryModules(mImport.namespaceURI);
            boolean loaded = false;
            if (existingModules != null && m < hints.length) {
                for (QueryModule existingModule : existingModules) {
                    URI uri = existingModule.getLocationURI();
                    if (uri != null && uri.toString().equals(mImport.locationURIs.get(m))) {
                        loaded = true;
                        break;
                    }
                }
            }
            if (loaded) {
                break;
            }

            try {
                String queryText = QueryReader.readSourceQuery(ss, charChecker);
                try {
                    if (ss.getInputStream() != null) {
                        ss.getInputStream().close();
                    } else if (ss.getReader() != null) {
                        ss.getReader().close();
                    }
                } catch (IOException e) {
                    throw new XPathException("Failure while closing file for imported query module");
                }
                QueryModule.makeQueryModule(
                        baseURI, executable, (QueryModule) env, queryText, mImport.namespaceURI
                );
            } catch (XPathException err) {
                err.maybeSetLocation(makeLocation());
                reportError(err);
            }
        }
    }

    /**
     * Parse the Base URI declaration.
     * Syntax: <"declare" "base-uri"> uri-literal
     *
     * @throws XPathException if a static error is found
     */

    private void parseBaseURIDeclaration() throws XPathException {
        if (foundBaseURIDeclaration) {
            grumble("Base URI Declaration may only appear once", "XQST0032");
        }
        foundBaseURIDeclaration = true;
        nextToken();
        expect(Token.STRING_LITERAL);
        String uri = uriLiteral(t.currentTokenValue);
        try {
            // if the supplied URI is relative, try to resolve it
            URI baseURI = new URI(uri);
            if (!baseURI.isAbsolute()) {
                String oldBase = env.getStaticBaseURI();
                uri = ResolveURI.makeAbsolute(uri, oldBase).toString();
            }
            ((QueryModule) env).setBaseURI(uri);
        } catch (URISyntaxException err) {
            // The spec says this "is not intrinsically an error", but can cause a failure later
            ((QueryModule) env).setBaseURI(uri);
        }
        nextToken();
    }


    /**
     * Parse a named decimal format declaration.
     * "declare" "decimal-format" QName (property "=" string-literal)*
     *
     * @throws XPathException if parsing fails
     */

    private void parseDecimalFormatDeclaration() throws XPathException {
        nextToken();
        expect(Token.NAME);
        StructuredQName formatName = makeStructuredQName(t.currentTokenValue, "");
        if (env.getDecimalFormatManager().getNamedDecimalFormat(formatName) != null) {
            grumble("Duplicate declaration of decimal-format " + formatName.getDisplayName(), "XQST0111");
        }
        nextToken();
        parseDecimalFormatProperties(formatName);
    }

    /**
     * Parse a default decimal format declaration
     * "declare" "default" "decimal-format" (property "=" string-literal)*
     *
     * @throws XPathException if parsing fails
     */

    private void parseDefaultDecimalFormat() throws XPathException {
        if (foundDefaultDecimalFormat) {
            grumble("Duplicate declaration of default decimal-format", "XQST0111");
        }
        foundDefaultDecimalFormat = true;
        parseDecimalFormatProperties(null);
    }

    private void parseDecimalFormatProperties(/*@Nullable*/ StructuredQName formatName) throws XPathException {
        int outerOffset = t.currentTokenStartOffset;
        DecimalFormatManager dfm = env.getDecimalFormatManager();
        DecimalSymbols dfs = formatName == null ? dfm.getDefaultDecimalFormat() : dfm.obtainNamedDecimalFormat(formatName);
        dfs.setHostLanguage(HostLanguage.XQUERY, 31);
        Set propertyNames = new HashSet<>(10);
        while (t.currentToken != Token.SEMICOLON) {
            int offset = t.currentTokenStartOffset;
            String propertyName = t.currentTokenValue;
            if (propertyNames.contains(propertyName)) {
                grumble("Property name " + propertyName + " is defined more than once", "XQST0114", offset);
            }
            nextToken();
            expect(Token.EQUALS);
            nextToken();
            expect(Token.STRING_LITERAL);
            String propertyValue = unescape(t.currentTokenValue).toString();
            nextToken();
            propertyNames.add(propertyName);
            switch (propertyName) {
                case "decimal-separator":
                    dfs.setDecimalSeparator(propertyValue);
                    break;
                case "grouping-separator":
                    dfs.setGroupingSeparator(propertyValue);
                    break;
                case "infinity":
                    dfs.setInfinity(propertyValue);
                    break;
                case "minus-sign":
                    dfs.setMinusSign(propertyValue);
                    break;
                case "NaN":
                    dfs.setNaN(propertyValue);
                    break;
                case "percent":
                    dfs.setPercent(propertyValue);
                    break;
                case "per-mille":
                    dfs.setPerMille(propertyValue);
                    break;
                case "zero-digit":
                    try {
                        dfs.setZeroDigit(propertyValue);
                    } catch (XPathException err) {
                        err.setErrorCode("XQST0097");
                        throw err;
                    }
                    break;
                case "digit":
                    dfs.setDigit(propertyValue);
                    break;
                case "pattern-separator":
                    dfs.setPatternSeparator(propertyValue);
                    break;
                case "exponent-separator":
                    dfs.setExponentSeparator(propertyValue);
                    break;
                default:
                    grumble("Unknown decimal-format property: " + propertyName, "XPST0003", offset);
                    break;
            }
        }


        try {
            dfs.checkConsistency(formatName);
        } catch (XPathException err) {
            grumble(err.getMessage(), "XQST0098", outerOffset);
        }

    }

    /**
     * Parse the "default function namespace" declaration.
     * Syntax: <"declare" "default" "function" "namespace"> StringLiteral
     *
     * @throws XPathException to indicate a syntax error
     */

    private void parseDefaultFunctionNamespace() throws XPathException {
        if (foundDefaultFunctionNamespace) {
            grumble("default function namespace appears more than once", "XQST0066");
        }
        foundDefaultFunctionNamespace = true;
        nextToken();
        expect(Token.NAME);
        if (!"namespace".equals(t.currentTokenValue)) {
            grumble("After 'declare default function', expected 'namespace'");
        }
        nextToken();
        expect(Token.STRING_LITERAL);
        String uri = uriLiteral(t.currentTokenValue);
        if (uri.equals(NamespaceConstant.XML) || uri.equals(NamespaceConstant.XMLNS)) {
            grumble("Reserved namespace used as default element/type namespace", "XQST0070");
        }
        ((QueryModule) env).setDefaultFunctionNamespace(uri);
        nextToken();
    }

    /**
     * Parse the "default element namespace" declaration.
     * Syntax: <"declare" "default" "element" "namespace"> StringLiteral
     *
     * @throws XPathException to indicate a syntax error
     */

    private void parseDefaultElementNamespace() throws XPathException {
        if (foundDefaultElementNamespace) {
            grumble("default element namespace appears more than once", "XQST0066");
        }
        foundDefaultElementNamespace = true;
        nextToken();
        expect(Token.NAME);
        if (!"namespace".equals(t.currentTokenValue)) {
            grumble("After 'declare default element', expected 'namespace'");
        }
        nextToken();
        expect(Token.STRING_LITERAL);
        String uri = uriLiteral(t.currentTokenValue);
        if (uri.equals(NamespaceConstant.XML) || uri.equals(NamespaceConstant.XMLNS)) {
            grumble("Reserved namespace used as default element/type namespace", "XQST0070");
        }
        ((QueryModule) env).setDefaultElementNamespace(uri);
        nextToken();
    }

    /**
     * Parse a namespace declaration in the Prolog.
     * Syntax: <"declare" "namespace"> NCName "=" StringLiteral
     *
     * @throws XPathException if parsing fails or a static error is found
     */

    private void parseNamespaceDeclaration() throws XPathException {
        nextToken();
        expect(Token.NAME);
        String prefix = t.currentTokenValue;
        if (!NameChecker.isValidNCName(prefix)) {
            grumble("Invalid namespace prefix " + Err.wrap(prefix));
        }
        nextToken();
        expect(Token.EQUALS);
        nextToken();
        expect(Token.STRING_LITERAL);
        String uri = uriLiteral(t.currentTokenValue);
        checkProhibitedPrefixes(prefix, uri);
        if ("xml".equals(prefix)) {
            // disallowed here even if bound to the correct namespace - erratum XQ.E19
            grumble("Namespace prefix 'xml' cannot be declared", "XQST0070");
        }
        try {
            ((QueryModule) env).declarePrologNamespace(prefix, uri);
        } catch (XPathException err) {
            err.setLocator(makeLocation());
            reportError(err);
        }
        nextToken();
    }

    /**
     * Check that a namespace declaration does not use a prohibited prefix or URI (xml or xmlns)
     *
     * @param prefix the prefix to be tested
     * @param uri    the URI being declared
     * @throws XPathException if the prefix is prohibited
     */

    private void checkProhibitedPrefixes(/*@Nullable*/ String prefix, /*@Nullable*/ String uri) throws XPathException {
        if (prefix != null && !prefix.isEmpty() && !NameChecker.isValidNCName(prefix)) {
            grumble("The namespace prefix " + Err.wrap(prefix) + " is not a valid NCName");
        }
        if (prefix == null) {
            prefix = "";
        }
        if (uri == null) {
            uri = "";
        }
        if ("xmlns".equals(prefix)) {
            grumble("The namespace prefix 'xmlns' cannot be redeclared", "XQST0070");
        }
        if (uri.equals(NamespaceConstant.XMLNS)) {
            grumble("The xmlns namespace URI is reserved", "XQST0070");
        }
        if (uri.equals(NamespaceConstant.XML) && !prefix.equals("xml")) {
            grumble("The XML namespace cannot be bound to any prefix other than 'xml'", "XQST0070");
        }
        if (prefix.equals("xml") && !uri.equals(NamespaceConstant.XML)) {
            grumble("The prefix 'xml' cannot be bound to any namespace other than " + NamespaceConstant.XML, "XQST0070");
        }
    }

    /**
     * Parse a global variable definition.
     * <"declare" "variable" "$"> VarName TypeDeclaration?
     * ((":=" ExprSingle ) | "external")
     * XQuery 3.0 allows "external := ExprSingle"
     *
     * @param annotations derived from any %-annotations present in XQuery 3.0
     * @throws XPathException if a static error is found
     */

    private void parseVariableDeclaration(AnnotationList annotations) throws XPathException {
        int offset = t.currentTokenStartOffset;
        GlobalVariable var = new GlobalVariable();
        var.setPackageData(env.getPackageData());
        var.setLineNumber(t.getLineNumber() + 1);
        var.setColumnNumber(t.getColumnNumber() + 1);
        var.setSystemId(env.getSystemId());
        if (annotations != null) {
            var.setPrivate(annotations.includes(Annotation.PRIVATE));
        }
        nextToken();
        expect(Token.DOLLAR);
        t.setState(Tokenizer.BARE_NAME_STATE);
        nextToken();
        expect(Token.NAME);
        String varName = t.currentTokenValue;
        StructuredQName varQName = makeStructuredQName(t.currentTokenValue, "");
        assert varQName != null;
        var.setVariableQName(varQName);

        String uri = varQName.getURI();
        String moduleURI = ((QueryModule) env).getModuleNamespace();
        if (moduleURI != null && !moduleURI.equals(uri)) {
            grumble("A variable declared in a library module must be in the module namespace", "XQST0048", offset);
        }

        nextToken();
        SequenceType requiredType = SequenceType.ANY_SEQUENCE;
        if (t.currentToken == Token.AS) {
            t.setState(Tokenizer.SEQUENCE_TYPE_STATE);
            nextToken();
            requiredType = parseSequenceType();
        }
        var.setRequiredType(requiredType);

        if (t.currentToken == Token.ASSIGN) {
            t.setState(Tokenizer.DEFAULT_STATE);
            nextToken();
            Expression exp = parseExprSingle();
            var.setBody(makeTracer(exp, varQName));
        } else if (t.currentToken == Token.NAME) {
            if ("external".equals(t.currentTokenValue)) {
                GlobalParam par = new GlobalParam();
                par.setPackageData(env.getPackageData());
                //par.setExecutable(var.getExecutable());
                par.setLineNumber(var.getLineNumber());
                par.setColumnNumber(var.getColumnNumber());
                par.setSystemId(var.getSystemId());
                par.setVariableQName(var.getVariableQName());
                par.setRequiredType(var.getRequiredType());
                var = par;
                nextToken();
                if (t.currentToken == Token.ASSIGN) {
                    t.setState(Tokenizer.DEFAULT_STATE);
                    nextToken();
                    Expression exp = parseExprSingle();
                    var.setBody(makeTracer(exp, varQName));
                }

            } else {
                grumble("Variable must either be initialized or be declared as external");
            }
        } else {
            grumble("Expected ':=' or 'external' in variable declaration");
        }

        QueryModule qenv = (QueryModule) env;
        RetainedStaticContext rsc = env.makeRetainedStaticContext();
        var.setRetainedStaticContext(rsc);
        if (var.getBody() != null) {
            ExpressionTool.setDeepRetainedStaticContext(var.getBody(), rsc);
        }
        if (qenv.getModuleNamespace() != null &&
                !uri.equals(qenv.getModuleNamespace())) {
            grumble("Variable " + Err.wrap(varName, Err.VARIABLE) + " is not defined in the module namespace");
        }
        try {
            qenv.declareVariable(var);
        } catch (XPathException e) {
            grumble(e.getMessage(), e.getErrorCodeQName(), -1);
        }
    }


    /**
     * Parse a context item declaration.
     * "declare" "context" "item"  TypeDeclaration?
     * ((":=" ExprSingle ) | ("external" (":=" ExprSingle ))
     *
     * @throws XPathException if parsing fails
     */

    private void parseContextItemDeclaration() throws XPathException {
        int offset = t.currentTokenStartOffset;
        nextToken();
        if (!isKeyword("item")) {
            grumble("After 'declare context', expected 'item'");
        }
        if (foundContextItemDeclaration) {
            grumble("More than one context item declaration found", "XQST0099", offset);
        }
        foundContextItemDeclaration = true;

        GlobalContextRequirement req = new GlobalContextRequirement();
        req.setAbsentFocus(false);

        t.setState(Tokenizer.BARE_NAME_STATE);

        nextToken();
        ItemType requiredType = AnyItemType.getInstance();
        if (t.currentToken == Token.AS) {
            t.setState(Tokenizer.SEQUENCE_TYPE_STATE);
            nextToken();
            requiredType = parseItemType();
        }
        req.addRequiredItemType(requiredType);

        if (t.currentToken == Token.ASSIGN) {
            if (!((QueryModule) env).isMainModule()) {
                grumble("The context item must not be initialized in a library module", "XQST0113");
            }
            t.setState(Tokenizer.DEFAULT_STATE);
            nextToken();
            Expression exp = parseExprSingle();
            exp.setRetainedStaticContext(env.makeRetainedStaticContext());
            RoleDiagnostic role = new RoleDiagnostic(RoleDiagnostic.CONTEXT_ITEM, "context item declaration", 0);
            exp = CardinalityChecker.makeCardinalityChecker(exp, StaticProperty.EXACTLY_ONE, role);
            ExpressionVisitor visitor = ExpressionVisitor.make(env);
            exp = exp.simplify();
            ContextItemStaticInfo info = env.getConfiguration().makeContextItemStaticInfo(AnyItemType.getInstance(), true);
            exp.setRetainedStaticContext(env.makeRetainedStaticContext());
            exp = exp.typeCheck(visitor, info);
            req.setDefaultValue(exp);
            req.setExternal(false);
        } else if (t.currentToken == Token.NAME && "external".equals(t.currentTokenValue)) {
            req.setAbsentFocus(false);
            req.setExternal(true);
            nextToken();
            if (t.currentToken == Token.ASSIGN) {
                if (!((QueryModule) env).isMainModule()) {
                    grumble("The context item must not be initialized in a library module", "XQST0113");
                }
                t.setState(Tokenizer.DEFAULT_STATE);
                nextToken();
                Expression exp = parseExprSingle();
                RoleDiagnostic role = new RoleDiagnostic(RoleDiagnostic.CONTEXT_ITEM, "context item declaration", 0);
                exp = CardinalityChecker.makeCardinalityChecker(exp, StaticProperty.EXACTLY_ONE, role);
                exp.setRetainedStaticContext(env.makeRetainedStaticContext());
                req.setDefaultValue(exp);
            }
        } else {
            grumble("Expected ':=' or 'external' in context item declaration");
        }

        Executable exec = getExecutable();
        if (exec.getGlobalContextRequirement() != null) {
            // the context item is already declared in another module. Compare the required types
            GlobalContextRequirement gcr = exec.getGlobalContextRequirement();
            if (gcr.getDefaultValue() == null && req.getDefaultValue() != null) {
                gcr.setDefaultValue(req.getDefaultValue());
            }
            for (ItemType otherType : gcr.getRequiredItemTypes()) {
                if (otherType != AnyItemType.getInstance()) {
                    TypeHierarchy th = env.getConfiguration().getTypeHierarchy();
                    Affinity rel = th.relationship(requiredType, otherType);
                    if (rel == Affinity.DISJOINT) {
                        // the two types are incompatible: fail now
                        grumble("Different modules specify incompatible requirements for the type of the initial context item", "XPTY0004");
                    }
                }
            }
            gcr.addRequiredItemType(requiredType);
        } else {
            exec.setGlobalContextRequirement(req);
        }
    }

    /**
     * Parse a function declaration.
     * 

Syntax:
* <"declare" "function"> QName "(" ParamList? ")" ("as" SequenceType)? * (EnclosedExpr | "external") *

*

On entry, the "declare function" has already been recognized

* * @param annotations the list of annotations that have been encountered for this function declaration * @throws XPathException if a syntax error is found */ public void parseFunctionDeclaration(AnnotationList annotations) throws XPathException { if (annotations.includes(SAXON_MEMO_FUNCTION)) { if (env.getConfiguration().getEditionCode().equals("HE")) { warning("saxon:memo-function option is ignored under Saxon-HE"); } else { memoFunction = true; } } // the next token should be the < QNAME "("> pair int offset = t.currentTokenStartOffset; t.setState(Tokenizer.DEFAULT_STATE); nextToken(); expect(Token.FUNCTION); String uri; StructuredQName qName; if (t.currentTokenValue.indexOf(':') < 0) { uri = env.getDefaultFunctionNamespace(); qName = new StructuredQName("", uri, t.currentTokenValue); } else { qName = makeStructuredQName(t.currentTokenValue, ""); uri = qName.getURI(); } if (uri.isEmpty()) { grumble("The function must be in a namespace", "XQST0060"); } String moduleURI = ((QueryModule) env).getModuleNamespace(); if (moduleURI != null && !moduleURI.equals(uri)) { grumble("A function in a library module must be in the module namespace", "XQST0048"); } if (isReservedInQuery(uri)) { grumble("The function name " + t.currentTokenValue + " is in a reserved namespace", "XQST0045"); } XQueryFunction func = new XQueryFunction(); func.setFunctionName(qName); func.setResultType(SequenceType.ANY_SEQUENCE); func.setBody(null); Location loc = makeNestedLocation(env.getContainingLocation(), t.getLineNumber(offset), t.getColumnNumber(offset), null); func.setLocation(loc); func.setStaticContext((QueryModule) env); func.setMemoFunction(memoFunction); func.setUpdating(annotations.includes(Annotation.UPDATING)); func.setAnnotations(annotations); nextToken(); HashSet paramNames = new HashSet<>(8); boolean external = false; if (t.currentToken != Token.RPAR) { while (true) { // ParamList ::= Param ("," Param)* // Param ::= "$" VarName TypeDeclaration? expect(Token.DOLLAR); nextToken(); expect(Token.NAME); StructuredQName argQName = makeStructuredQName(t.currentTokenValue, ""); if (paramNames.contains(argQName)) { grumble("Duplicate parameter name " + Err.wrap(t.currentTokenValue, Err.VARIABLE), "XQST0039"); } paramNames.add(argQName); SequenceType paramType = SequenceType.ANY_SEQUENCE; nextToken(); if (t.currentToken == Token.AS) { nextToken(); paramType = parseSequenceType(); } UserFunctionParameter arg = new UserFunctionParameter(); arg.setRequiredType(paramType); arg.setVariableQName(argQName); func.addArgument(arg); declareRangeVariable(arg); if (t.currentToken == Token.RPAR) { break; } else if (t.currentToken == Token.COMMA) { nextToken(); } else { grumble("Expected ',' or ')' after function argument, found '" + Token.tokens[t.currentToken] + '\''); } } } t.setState(Tokenizer.BARE_NAME_STATE); nextToken(); if (t.currentToken == Token.AS) { if (func.isUpdating()) { grumble("Cannot specify a return type for an updating function", "XUST0028"); } t.setState(Tokenizer.SEQUENCE_TYPE_STATE); nextToken(); func.setResultType(parseSequenceType()); } if (isKeyword("external")) { external = true; } else { expect(Token.LCURLY); t.setState(Tokenizer.DEFAULT_STATE); nextToken(); if (t.currentToken == Token.RCURLY) { Expression body = Literal.makeEmptySequence(); body.setRetainedStaticContext(env.makeRetainedStaticContext()); setLocation(body); func.setBody(body); } else { Expression body = parseExpression(); func.setBody(body); ExpressionTool.setDeepRetainedStaticContext(body, env.makeRetainedStaticContext()); } expect(Token.RCURLY); lookAhead(); // must be done manually after an RCURLY } UserFunctionParameter[] params = func.getParameterDefinitions(); //noinspection UnusedDeclaration for (UserFunctionParameter param : params) { undeclareRangeVariable(); } t.setState(Tokenizer.DEFAULT_STATE); nextToken(); QueryModule qenv = (QueryModule) env; if (external) { parserExtension.handleExternalFunctionDeclaration(this, func); } else { try { qenv.declareFunction(func); } catch (XPathException e) { grumble(e.getMessage(), e.getErrorCodeQName(), -1); } } memoFunction = false; } public static final StructuredQName SAXON_MEMO_FUNCTION = new StructuredQName("saxon", NamespaceConstant.SAXON, "memo-function"); /** * Parse a type alias declaration. Allowed only in Saxon-PE and higher * * @throws XPathException if parsing fails */ protected void parseTypeAliasDeclaration() throws XPathException { parserExtension.parseTypeAliasDeclaration(this); } /** * Parse an option declaration. *

Syntax: * <"declare" "option"> QName "string-literal" *

*

On entry, the "declare option" has already been recognized

* * @throws XPathException if a syntax error is found */ private void parseOptionDeclaration() throws XPathException { nextToken(); expect(Token.NAME); String defaultUri = NamespaceConstant.XQUERY; StructuredQName varName = makeStructuredQName(t.currentTokenValue, defaultUri); assert varName != null; String uri = varName.getURI(); if (uri.isEmpty()) { grumble("The QName identifying an option declaration must be prefixed", "XPST0081"); return; } nextToken(); expect(Token.STRING_LITERAL); //String value = URILiteral(t.currentTokenValue).trim(); String value = unescape(t.currentTokenValue).toString(); if (uri.equals(NamespaceConstant.OUTPUT)) { parseOutputDeclaration(varName, value); } else if (uri.equals(NamespaceConstant.SAXON)) { String localName = varName.getLocalPart(); switch (localName) { case "output": setOutputProperty(value); break; case "memo-function": value = value.trim(); switch (value) { case "true": memoFunction = true; if (env.getConfiguration().getEditionCode().equals("HE")) { warning("saxon:memo-function option is ignored under Saxon-HE"); } break; case "false": memoFunction = false; break; default: warning("Value of saxon:memo-function must be 'true' or 'false'"); break; } break; case "allow-cycles": warning("Value of saxon:allow-cycles is ignored"); break; default: warning("Unknown Saxon option declaration: " + varName.getDisplayName()); break; } } nextToken(); } protected void parseOutputDeclaration(StructuredQName varName, String value) throws XPathException { if (!((QueryModule) env).isMainModule()) { grumble("Output declarations must not appear in a library module", "XQST0108"); } String localName = varName.getLocalPart(); if (outputPropertiesSeen.contains(varName)) { grumble("Duplicate output declaration (" + varName + ")", "XQST0110"); } outputPropertiesSeen.add(varName); switch (localName) { case "parameter-document": { Configuration config = env.getConfiguration(); ResourceRequest rr = new ResourceRequest(); rr.relativeUri = value; rr.baseUri = env.getStaticBaseURI(); try { rr.uri = ResolveURI.makeAbsolute(value, env.getStaticBaseURI()).toString(); } catch (URISyntaxException err) { throw XPathException.makeXPathException(err); } rr.nature = NamespaceConstant.OUTPUT; rr.purpose = "serialization"; Source source = rr.resolve(config.getResourceResolver(), new DirectResourceResolver(config)); ParseOptions options = new ParseOptions(); options.setSchemaValidationMode(Validation.LAX); options.setDTDValidationMode(Validation.SKIP); TreeInfo doc = config.buildDocumentTree(source); SerializationParamsHandler ph = new SerializationParamsHandler(parameterDocProperties); ph.setSerializationParams(doc.getRootNode()); CharacterMap characterMap = ph.getCharacterMap(); if (characterMap != null) { CharacterMapIndex index = new CharacterMapIndex(); index.putCharacterMap(characterMap.getName(), characterMap); getExecutable().setCharacterMapIndex(index); parameterDocProperties.setProperty(SaxonOutputKeys.USE_CHARACTER_MAPS, characterMap.getName().getClarkName()); } break; } case "use-character-maps": grumble("Output declaration use-character-maps cannot appear except in a parameter file", "XQST0109"); break; default: { Properties props = getExecutable().getPrimarySerializationProperties().getProperties(); ResultDocument.setSerializationProperty(props, "", localName, value, env.getNamespaceResolver(), false, env.getConfiguration()); break; } } } /** * Handle a saxon:output option declaration. Format: * declare option saxon:output "indent = yes" * * @param property a property name=value pair. The name is the name of a serialization * property, potentially as a prefixed QName; the value is the value of the property. A warning * is output for unrecognized properties or values */ private void setOutputProperty(/*@NotNull*/ String property) { int equals = property.indexOf("="); if (equals < 0) { badOutputProperty("no equals sign"); } else if (equals == 0) { badOutputProperty("starts with '="); } String keyword = Whitespace.trim(property.substring(0, equals)); String value = equals == property.length() - 1 ? "" : Whitespace.trim(property.substring(equals + 1)); Properties props = getExecutable().getPrimarySerializationProperties().getProperties(); try { StructuredQName name = makeStructuredQName(keyword, ""); String lname = name.getLocalPart(); String uri = name.getURI(); ResultDocument.setSerializationProperty(props, uri, lname, value, env.getNamespaceResolver(), false, env.getConfiguration()); } catch (XPathException e) { badOutputProperty(e.getMessage()); } } private void badOutputProperty(String s) { warning("Invalid serialization property (" + s + ")"); } /** * Parse a FLWOR expression. This replaces the XPath "for" expression. * Full syntax: *

* [41] FLWORExpr ::= (ForClause | LetClause)+ * WhereClause? OrderByClause? * "return" ExprSingle
* [42] ForClause ::= <"for" "$"> VarName TypeDeclaration? PositionalVar? "in" ExprSingle * ("," "$" VarName TypeDeclaration? PositionalVar? "in" ExprSingle)*
* [43] PositionalVar ::= "at" "$" VarName
* [44] LetClause ::= <"let" "$"> VarName TypeDeclaration? ":=" ExprSingle * ("," "$" VarName TypeDeclaration? ":=" ExprSingle)*
* [45] WhereClause ::= "where" Expr
* [46] OrderByClause ::= (<"order" "by"> | <"stable" "order" "by">) OrderSpecList
* [47] OrderSpecList ::= OrderSpec ("," OrderSpec)*
* [48] OrderSpec ::= ExprSingle OrderModifier
* [49] OrderModifier ::= ("ascending" | "descending")? * (<"empty" "greatest"> | <"empty" "least">)? * ("collation" StringLiteral)? *

* * @return the resulting subexpression * @throws XPathException if any error is encountered */ /*@Nullable*/ @Override protected Expression parseFLWORExpression() throws XPathException { FLWORExpression flwor = new FLWORExpression(); int exprOffset = t.currentTokenStartOffset; List clauseList = new ArrayList<>(4); while (true) { int offset = t.currentTokenStartOffset; if (t.currentToken == Token.FOR || t.currentToken == Token.FOR_MEMBER) { parseForClause(flwor, clauseList); } else if (t.currentToken == Token.LET) { parseLetClause(flwor, clauseList); } else if (t.currentToken == Token.COUNT) { parseCountClause(clauseList); } else if (t.currentToken == Token.GROUP_BY) { parseGroupByClause(flwor, clauseList); } else if (t.currentToken == Token.FOR_TUMBLING || t.currentToken == Token.FOR_SLIDING) { parseWindowClause(flwor, clauseList); } else if (t.currentToken == Token.WHERE || isKeyword("where")) { nextToken(); Expression condition = parseExprSingle(); WhereClause clause = new WhereClause(flwor, condition); setLocation(clause, t.currentTokenStartOffset); clause.setRepeated(containsLoopingClause(clauseList)); clauseList.add(clause); } else if (isKeyword("trace")) { parseTraceClause(flwor, clauseList); } else if (isKeyword("stable") || isKeyword("order")) { // we read the "stable" keyword but ignore it; Saxon ordering is always stable if (isKeyword("stable")) { nextToken(); if (!isKeyword("order")) { grumble("'stable' must be followed by 'order by'"); } } TupleExpression tupleExpression = new TupleExpression(); List vars = new ArrayList<>(); for (Clause c : clauseList) { for (LocalVariableBinding b : c.getRangeVariables()) { vars.add(new LocalVariableReference(b)); } } tupleExpression.setVariables(vars); List sortSpecList; t.setState(Tokenizer.BARE_NAME_STATE); nextToken(); if (!(t.currentToken == Token.BY || isKeyword("by"))) { grumble("'order' must be followed by 'by'"); } t.setState(Tokenizer.DEFAULT_STATE); nextToken(); sortSpecList = parseSortDefinition(); SortKeyDefinition[] keys = new SortKeyDefinition[sortSpecList.size()]; for (int i = 0; i < keys.length; i++) { SortSpec spec = sortSpecList.get(i); SortKeyDefinition key = new SortKeyDefinition(); key.setSortKey(sortSpecList.get(i).sortKey, false); String str = spec.ascending ? "ascending" : "descending"; key.setOrder(new StringLiteral(BMPString.of(str))); key.setEmptyLeast(spec.emptyLeast); if (spec.collation != null) { final StringCollator comparator = env.getConfiguration().getCollation(spec.collation); if (comparator == null) { grumble("Unknown collation '" + spec.collation + '\'', "XQST0076"); } key.setCollation(comparator); } keys[i] = key; } OrderByClause clause = new OrderByClause(flwor, keys, tupleExpression); clause.setRepeated(containsLoopingClause(clauseList)); clauseList.add(clause); } else { break; } setLocation(clauseList.get(clauseList.size() - 1), offset); } int returnOffset = t.currentTokenStartOffset; expect(Token.RETURN); t.setState(Tokenizer.DEFAULT_STATE); nextToken(); Expression returnExpression = parseExprSingle(); returnExpression = makeTracer(returnExpression, null); // undeclare all the range variables for (int i = clauseList.size() - 1; i >= 0; i--) { Clause clause = clauseList.get(i); for (int n = 0; n < clause.getRangeVariables().length; n++) { undeclareRangeVariable(); } } // if (codeInjector != null) { // List expandedList = new ArrayList<>(clauseList.size() * 2); // expandedList.add(clauseList.get(0)); // for (int i = 1; i < clauseList.size(); i++) { // Clause extra = codeInjector.injectClause( // clauseList.get(i - 1), // env // ); // if (extra != null) { // expandedList.add(extra); // } // expandedList.add(clauseList.get(i)); // } // Clause extra = codeInjector.injectClause( // clauseList.get(clauseList.size() - 1), env); // if (extra != null) { // expandedList.add(extra); // } // clauseList = expandedList; // } flwor.init(clauseList, returnExpression); setLocation(flwor, exprOffset); return flwor; } /** * Make a LetExpression. This returns an ordinary LetExpression if tracing is off, and an EagerLetExpression * if tracing is on. This is so that trace events occur in an order that the user can follow. * * @return the constructed "let" expression */ /*@NotNull*/ protected LetExpression makeLetExpression() { if (((QueryModule) env).getUserQueryContext().isCompileWithTracing()) { return new EagerLetExpression(); } else { return new LetExpression(); } } protected static boolean containsLoopingClause(List clauseList) { for (Clause c : clauseList) { if (FLWORExpression.isLoopingClause(c)) { return true; } } return false; } /** * Parse a ForClause. *

* [42] ForClause ::= "for" ForBinding ("," ForBinding)*
* [42a] ForBinding ::= "member"? "$" VarName TypeDeclaration? ("allowing" "empty")? PositionalVar? "in" ExprSingle *

* * @param clauseList - the components of the parsed ForClause are appended to the * supplied list * @throws XPathException if parsing fails */ private void parseForClause(FLWORExpression flwor, List clauseList) throws XPathException { boolean first = true; boolean forMember = t.currentToken == Token.FOR_MEMBER; if (forMember && !allowXPath40Syntax) { grumble("The 'for member' syntax requires XQuery 4.0 to be enabled"); } do { nextToken(); int offset = t.currentTokenStartOffset; ForClause clause = forMember ? new ForMemberClause() : new ForClause(); clause.setRepeated(!first || containsLoopingClause(clauseList)); if (first) { first = false; } setLocation(clause, offset); clauseList.add(clause); expect(Token.DOLLAR); nextToken(); expect(Token.NAME); StructuredQName varQName = makeStructuredQName(t.currentTokenValue, ""); SequenceType type = forMember ? SequenceType.ANY_SEQUENCE : SequenceType.SINGLE_ITEM; nextToken(); boolean explicitType = false; if (t.currentToken == Token.AS) { explicitType = true; nextToken(); type = parseSequenceType(); } boolean allowingEmpty = false; if (isKeyword("allowing")) { if (forMember) { grumble("'allowing empty' cannot appear in a 'for member' clause"); } allowingEmpty = true; clause.setAllowingEmpty(true); if (!explicitType) { type = forMember ? SequenceType.ANY_SEQUENCE : SequenceType.OPTIONAL_ITEM; } nextToken(); if (!isKeyword("empty")) { grumble("After 'allowing', expected 'empty'"); } nextToken(); } if (explicitType && !allowingEmpty && !forMember && type.getCardinality() != StaticProperty.EXACTLY_ONE) { warning("Occurrence indicator on singleton range variable has no effect"); type = SequenceType.makeSequenceType(type.getPrimaryType(), StaticProperty.EXACTLY_ONE); } LocalVariableBinding binding = new LocalVariableBinding(varQName, type); clause.setRangeVariable(binding); if (isKeyword("at")) { nextToken(); expect(Token.DOLLAR); nextToken(); expect(Token.NAME); StructuredQName posQName = makeStructuredQName(t.currentTokenValue, ""); if (!scanOnly && posQName.equals(varQName)) { grumble("The two variables declared in a single 'for' clause must have different names", "XQST0089"); } LocalVariableBinding pos = new LocalVariableBinding(posQName, SequenceType.SINGLE_INTEGER); clause.setPositionVariable(pos); nextToken(); } expect(Token.IN); nextToken(); clause.initSequence(flwor, parseExprSingle()); declareRangeVariable(clause.getRangeVariable()); if (clause.getPositionVariable() != null) { declareRangeVariable(clause.getPositionVariable()); } if (allowingEmpty) { checkForClauseAllowingEmpty(flwor, clause); } } while (t.currentToken == Token.COMMA); } /** * Check a ForClause for an "outer for" * * @throws net.sf.saxon.trans.XPathException if invalid */ private void checkForClauseAllowingEmpty(FLWORExpression flwor, ForClause clause) throws XPathException { if (!allowXPath30Syntax) { grumble("The 'allowing empty' option requires XQuery 3.0"); } SequenceType type = clause.getRangeVariable().getRequiredType(); if (!Cardinality.allowsZero(type.getCardinality())) { warning("When 'allowing empty' is specified, the occurrence indicator on the range variable type should be '?'"); } } /** * Parse a LetClause. *

* [44] LetClause ::= <"let" "$"> VarName TypeDeclaration? ":=" ExprSingle * ("," "$" VarName TypeDeclaration? ":=" ExprSingle)* *

* * @param clauseList - the components of the parsed LetClause are appended to the * supplied list * @throws XPathException if a static error is found */ private void parseLetClause(FLWORExpression flwor, List clauseList) throws XPathException { boolean first = true; do { LetClause clause = new LetClause(); setLocation(clause, t.currentTokenStartOffset); clause.setRepeated(containsLoopingClause(clauseList)); if (first) { //clause.offset = t.currentTokenStartOffset; } clauseList.add(clause); nextToken(); if (first) { first = false; } else { //clause.offset = t.currentTokenStartOffset; } expect(Token.DOLLAR); nextToken(); expect(Token.NAME); String var = t.currentTokenValue; StructuredQName varQName = makeStructuredQName(var, ""); SequenceType type = SequenceType.ANY_SEQUENCE; nextToken(); if (t.currentToken == Token.AS) { nextToken(); type = parseSequenceType(); } LocalVariableBinding v = new LocalVariableBinding(varQName, type); expect(Token.ASSIGN); nextToken(); clause.initSequence(flwor, parseExprSingle()); clause.setRangeVariable(v); declareRangeVariable(v); } while (t.currentToken == Token.COMMA); } /** * Parse a CountClause. *

* [44] CountClause ::= <"count" "$"> VarName *

* * @param clauseList - the components of the parsed CountClause are appended to the * supplied list * @throws XPathException in the event of a syntax error */ private void parseCountClause(List clauseList) throws XPathException { CountClause clause = new CountClause(); setLocation(clause, t.currentTokenStartOffset); clause.setRepeated(containsLoopingClause(clauseList)); clauseList.add(clause); nextToken(); expect(Token.DOLLAR); nextToken(); expect(Token.NAME); String var = t.currentTokenValue; StructuredQName varQName = makeStructuredQName(var, ""); SequenceType type = SequenceType.ANY_SEQUENCE; nextToken(); LocalVariableBinding v = new LocalVariableBinding(varQName, type); clause.setRangeVariable(v); declareRangeVariable(v); } /** * Parse a TraceClause. This is a Saxon extension *

* [44] TraceClause ::= "trace" Expr *

* * @param clauseList - the components of the parsed TraceClause are appended to the * supplied list * @throws XPathException in the event of a syntax error */ private void parseTraceClause(FLWORExpression flwor, List clauseList) throws XPathException { DiagnosticClause clause = new DiagnosticClause(); setLocation(clause, t.currentTokenStartOffset); clause.setRepeated(containsLoopingClause(clauseList)); clauseList.add(clause); nextToken(); clause.initSequence(flwor, parseExpression()); } /** * Parse a Group By clause. * Handles the full XQuery 3.0 syntax: * "group by" ($varname ["collation" URILiteral]) [,...] * * @param clauseList the list of clauses of the FLWOR expression, to which this clause is added * @throws XPathException if there is a syntax error */ private void parseGroupByClause(FLWORExpression flwor, List clauseList) throws XPathException { GroupByClause clause = new GroupByClause(env.getConfiguration()); setLocation(clause, t.currentTokenStartOffset); clause.setRepeated(containsLoopingClause(clauseList)); List variableNames = new ArrayList<>(); List collations = new ArrayList<>(); nextToken(); while (true) { SequenceType type = SequenceType.ANY_SEQUENCE; StructuredQName varQName = readVariableName(); if (t.currentToken == Token.AS) { nextToken(); type = parseSequenceType(); if (t.currentToken != Token.ASSIGN) { grumble("In group by, if the type is declared then it must be followed by ':= value'"); } } if (t.currentToken == Token.ASSIGN) { LetClause letClause = new LetClause(); clauseList.add(letClause); nextToken(); LocalVariableBinding v = new LocalVariableBinding(varQName, type); Expression value = parseExprSingle(); RoleDiagnostic role = new RoleDiagnostic(RoleDiagnostic.MISC, "grouping key", 0); Expression atomizedValue = Atomizer.makeAtomizer(value, role); letClause.initSequence(flwor, atomizedValue); letClause.setRangeVariable(v); declareRangeVariable(v); } variableNames.add(varQName); if (isKeyword("collation")) { nextToken(); expect(Token.STRING_LITERAL); collations.add(t.currentTokenValue); nextToken(); } else { collations.add(env.getDefaultCollationName()); } if (t.currentToken == Token.COMMA) { nextToken(); } else { break; } } // Each of the variable names acts both as a variable reference (for a variable in the pre-grouping stream) // and a variable declaration (for a variable in the post-grouping stream). TupleExpression groupingTupleExpr = new TupleExpression(); TupleExpression retainedTupleExpr = new TupleExpression(); List groupingRefs = new ArrayList<>(); List retainedRefs = new ArrayList<>(); List groupedBindings = new ArrayList<>(); for (StructuredQName q : variableNames) { boolean found = locateDeclaration(clauseList, groupingRefs, groupedBindings, q); if (!found) { grumble("The grouping variable " + q.getDisplayName() + " must be the name of a variable bound earlier in the FLWOR expression", "XQST0094"); } } groupingTupleExpr.setVariables(groupingRefs); clause.initGroupingTupleExpression(flwor, groupingTupleExpr); List ungroupedBindings = new ArrayList<>(); for (int i = clauseList.size() - 1; i >= 0; i--) { for (LocalVariableBinding b : clauseList.get(i).getRangeVariables()) { if (!groupedBindings.contains(b)) { ungroupedBindings.add(b); retainedRefs.add(new LocalVariableReference(b)); } } } retainedTupleExpr.setVariables(retainedRefs); clause.initRetainedTupleExpression(flwor, retainedTupleExpr); LocalVariableBinding[] bindings = new LocalVariableBinding[groupedBindings.size() + ungroupedBindings.size()]; int k = 0; for (LocalVariableBinding b : groupedBindings) { bindings[k] = new LocalVariableBinding(b.getVariableQName(), b.getRequiredType()); //declareRangeVariable(bindings[k]); k++; } for (LocalVariableBinding b : ungroupedBindings) { ItemType itemType = b.getRequiredType().getPrimaryType(); bindings[k] = new LocalVariableBinding(b.getVariableQName(), SequenceType.makeSequenceType(itemType, StaticProperty.ALLOWS_ZERO_OR_MORE)); //declareRangeVariable(bindings[k]); k++; } for (int z = groupedBindings.size(); z < bindings.length; z++) { declareRangeVariable(bindings[z]); } for (int z = 0; z < groupedBindings.size(); z++) { declareRangeVariable(bindings[z]); } clause.setVariableBindings(bindings); GenericAtomicComparer[] comparers = new GenericAtomicComparer[collations.size()]; XPathContext context = env.makeEarlyEvaluationContext(); for (int i = 0; i < comparers.length; i++) { StringCollator coll = env.getConfiguration().getCollation(collations.get(i)); comparers[i] = (GenericAtomicComparer) GenericAtomicComparer.makeAtomicComparer( BuiltInAtomicType.ANY_ATOMIC, BuiltInAtomicType.ANY_ATOMIC, coll, context); } clause.setComparers(comparers); clauseList.add(clause); } private boolean locateDeclaration(List clauseList, List groupingRefs, List groupedBindings, StructuredQName q) { for (int i = clauseList.size() - 1; i >= 0; i--) { for (LocalVariableBinding b : clauseList.get(i).getRangeVariables()) { if (q.equals(b.getVariableQName())) { groupedBindings.add(b); groupingRefs.add(new LocalVariableReference(b)); return true; } } } return false; } private StructuredQName readVariableName() throws XPathException { expect(Token.DOLLAR); nextToken(); expect(Token.NAME); String name = t.currentTokenValue; nextToken(); return makeStructuredQName(name, ""); } /** * Parse a tumbling or sliding window clause. * * @param clauseList the list of clauses of the FLWOR expression, to which this clause is aded * @throws XPathException if there is a syntax error */ private void parseWindowClause(FLWORExpression flwor, List clauseList) throws XPathException { WindowClause clause = new WindowClause(); setLocation(clause, t.currentTokenStartOffset); clause.setRepeated(containsLoopingClause(clauseList)); clause.setIsSlidingWindow(t.currentToken == Token.FOR_SLIDING); nextToken(); if (!isKeyword("window")) { grumble("after 'sliding' or 'tumbling', expected 'window', but found " + currentTokenDisplay()); } nextToken(); StructuredQName windowVarName = readVariableName(); SequenceType windowType = SequenceType.ANY_SEQUENCE; if (t.currentToken == Token.AS) { nextToken(); windowType = parseSequenceType(); } LocalVariableBinding windowVar = new LocalVariableBinding(windowVarName, windowType); clause.setVariableBinding(WindowClause.WINDOW_VAR, windowVar); // We can't assume that all the items in the input sequence belong to the item type of the windows: test case SlidingWindowExpr507 SequenceType windowItemTypeMandatory = SequenceType.SINGLE_ITEM; SequenceType windowItemTypeOptional = SequenceType.OPTIONAL_ITEM; expect(Token.IN); nextToken(); clause.initSequence(flwor, parseExprSingle()); if (!isKeyword("start")) { grumble("in window clause, expected 'start', but found " + currentTokenDisplay()); } t.setState(Tokenizer.BARE_NAME_STATE); nextToken(); if (t.currentToken == Token.DOLLAR) { LocalVariableBinding startItemVar = new LocalVariableBinding(readVariableName(), windowItemTypeMandatory); clause.setVariableBinding(WindowClause.START_ITEM, startItemVar); declareRangeVariable(startItemVar); } if (isKeyword("at")) { nextToken(); LocalVariableBinding startPositionVar = new LocalVariableBinding(readVariableName(), SequenceType.SINGLE_INTEGER); clause.setVariableBinding(WindowClause.START_ITEM_POSITION, startPositionVar); declareRangeVariable(startPositionVar); } if (isKeyword("previous")) { nextToken(); LocalVariableBinding startPreviousItemVar = new LocalVariableBinding(readVariableName(), windowItemTypeOptional); clause.setVariableBinding(WindowClause.START_PREVIOUS_ITEM, startPreviousItemVar); declareRangeVariable(startPreviousItemVar); } if (isKeyword("next")) { nextToken(); LocalVariableBinding startNextItemVar = new LocalVariableBinding(readVariableName(), windowItemTypeOptional); clause.setVariableBinding(WindowClause.START_NEXT_ITEM, startNextItemVar); declareRangeVariable(startNextItemVar); } if (!isKeyword("when")) { grumble("Expected 'when' condition for window start, but found " + currentTokenDisplay()); } t.setState(Tokenizer.DEFAULT_STATE); nextToken(); clause.initStartCondition(flwor, parseExprSingle()); if (isKeyword("only")) { clause.setIncludeUnclosedWindows(false); nextToken(); } if (isKeyword("end")) { t.setState(Tokenizer.BARE_NAME_STATE); nextToken(); if (t.currentToken == Token.DOLLAR) { LocalVariableBinding endItemVar = new LocalVariableBinding(readVariableName(), windowItemTypeMandatory); clause.setVariableBinding(WindowClause.END_ITEM, endItemVar); declareRangeVariable(endItemVar); } if (isKeyword("at")) { nextToken(); LocalVariableBinding endPositionVar = new LocalVariableBinding(readVariableName(), SequenceType.SINGLE_INTEGER); clause.setVariableBinding(WindowClause.END_ITEM_POSITION, endPositionVar); declareRangeVariable(endPositionVar); } if (isKeyword("previous")) { nextToken(); LocalVariableBinding endPreviousItemVar = new LocalVariableBinding(readVariableName(), windowItemTypeOptional); clause.setVariableBinding(WindowClause.END_PREVIOUS_ITEM, endPreviousItemVar); declareRangeVariable(endPreviousItemVar); } if (isKeyword("next")) { nextToken(); LocalVariableBinding endNextItemVar = new LocalVariableBinding(readVariableName(), windowItemTypeOptional); clause.setVariableBinding(WindowClause.END_NEXT_ITEM, endNextItemVar); declareRangeVariable(endNextItemVar); } if (!isKeyword("when")) { grumble("Expected 'when' condition for window end, but found " + currentTokenDisplay()); } t.setState(Tokenizer.DEFAULT_STATE); nextToken(); clause.initEndCondition(flwor, parseExprSingle()); } else { // no "end" condition found if (clause.isSlidingWindow()) { grumble("A sliding window requires an end condition"); } } declareRangeVariable(windowVar); clauseList.add(clause); } /** * Make a string-join expression that concatenates the string-values of items in * a sequence with intervening spaces. This may be simplified later as a result * of type-checking. * * @param exp the base expression, evaluating to a sequence * @param env the static context * @return a call on string-join to create a string containing the * representations of the items in the sequence separated by spaces. */ /*@Nullable*/ public static Expression makeStringJoin(Expression exp, /*@NotNull*/ StaticContext env) { exp = Atomizer.makeAtomizer(exp, null); ItemType t = exp.getItemType(); if (!t.equals(BuiltInAtomicType.STRING) && !t.equals(BuiltInAtomicType.UNTYPED_ATOMIC)) { exp = new AtomicSequenceConverter(exp, BuiltInAtomicType.STRING); ((AtomicSequenceConverter) exp).allocateConverterStatically(env.getConfiguration(), false); } if (exp.getCardinality() == StaticProperty.EXACTLY_ONE) { return exp; } else { RetainedStaticContext rsc = new RetainedStaticContext(env); Expression fn = SystemFunction.makeCall("string-join", rsc, exp, new StringLiteral(StringValue.SINGLE_SPACE)); ExpressionTool.copyLocationInfo(exp, fn); return fn; } } /** * Parse the "order by" clause. *

[46] OrderByClause ::= (<"order" "by"> | <"stable" "order" "by">) OrderSpecList
* [47] OrderSpecList ::= OrderSpec ("," OrderSpec)*
* [48] OrderSpec ::= ExprSingle OrderModifier
* [49] OrderModifier ::= ("ascending" | "descending")? * (<"empty" "greatest"> | <"empty" "least">)? * ("collation" StringLiteral)? *

* * @return a list of sort specifications (SortSpec), one per sort key * @throws XPathException if parsing fails */ /*@NotNull*/ private List parseSortDefinition() throws XPathException { List sortSpecList = new ArrayList<>(5); while (true) { SortSpec sortSpec = new SortSpec(); sortSpec.sortKey = parseExprSingle(); sortSpec.ascending = true; sortSpec.emptyLeast = ((QueryModule) env).isEmptyLeast(); sortSpec.collation = env.getDefaultCollationName(); //t.setState(t.BARE_NAME_STATE); if (isKeyword("ascending")) { nextToken(); } else if (isKeyword("descending")) { sortSpec.ascending = false; nextToken(); } if (isKeyword("empty")) { nextToken(); if (isKeyword("greatest")) { sortSpec.emptyLeast = false; nextToken(); } else if (isKeyword("least")) { sortSpec.emptyLeast = true; nextToken(); } else { grumble("'empty' must be followed by 'greatest' or 'least'"); } } if (isKeyword("collation")) { sortSpec.collation = readCollationName(); } sortSpecList.add(sortSpec); if (t.currentToken == Token.COMMA) { nextToken(); } else { break; } } return sortSpecList; } protected String readCollationName() throws XPathException { nextToken(); expect(Token.STRING_LITERAL); String collationName = uriLiteral(t.currentTokenValue); URI collationURI; try { collationURI = new URI(collationName); if (!collationURI.isAbsolute()) { URI base = new URI(env.getStaticBaseURI()); collationURI = base.resolve(collationURI); collationName = collationURI.toString(); } } catch (URISyntaxException err) { grumble("Collation name '" + collationName + "' is not a valid URI", "XQST0046"); collationName = NamespaceConstant.CODEPOINT_COLLATION_URI; } nextToken(); return collationName; } private static class SortSpec { /*@Nullable*/ public Expression sortKey; public boolean ascending; public boolean emptyLeast; public String collation; } /** * Parse a Typeswitch Expression. * This construct is XQuery-only. * TypeswitchExpr ::= * "typeswitch" "(" Expr ")" * CaseClause+ * "default" ("$" VarName)? "return" ExprSingle * CaseClause ::= * "case" ("$" VarName "as")? SequenceType "return" ExprSingle * * @throws XPathException if parsing fails */ /*@NotNull*/ @Override protected Expression parseTypeswitchExpression() throws XPathException { // On entry, the "(" has already been read int offset = t.currentTokenStartOffset; nextToken(); Expression operand = parseExpression(); List> types = new ArrayList<>(10); List actions = new ArrayList<>(10); expect(Token.RPAR); nextToken(); // The code generated takes the form: // let $zzz := operand return // if ($zzz instance of t1) then action1 // else if ($zzz instance of t2) then action2 // else default-action // // If a variable is declared in a case clause or default clause, // then "action-n" takes the form // let $v as type := $zzz return action-n // we were generating "let $v as type := $zzz return action-n" but this gives a compile time error if // there's a case clause that specifies an impossible type. LetExpression outerLet = makeLetExpression(); outerLet.setRequiredType(SequenceType.ANY_SEQUENCE); outerLet.setVariableQName(new StructuredQName("zz", NamespaceConstant.SAXON, "zz_typeswitchVar")); outerLet.setSequence(operand); while (t.currentToken == Token.CASE) { int caseOffset = t.currentTokenStartOffset; List typeList; Expression action; nextToken(); if (t.currentToken == Token.DOLLAR) { nextToken(); expect(Token.NAME); final String var = t.currentTokenValue; final StructuredQName varQName = makeStructuredQName(var, ""); nextToken(); expect(Token.AS); nextToken(); typeList = parseSequenceTypeList(); action = makeTracer( parseTypeswitchReturnClause(varQName, outerLet), varQName); if (action instanceof TraceExpression) { ((TraceExpression) action).setProperty("type", typeList.get(0).toString()); } } else { typeList = parseSequenceTypeList(); action = makeTracer(parseExprSingle(), null); if (action instanceof TraceExpression) { ((TraceExpression) action).setProperty("type", typeList.get(0).toString()); } } types.add(typeList); actions.add(action); } if (types.isEmpty()) { grumble("At least one case clause is required in a typeswitch"); } expect(Token.DEFAULT); final int defaultOffset = t.currentTokenStartOffset; nextToken(); Expression defaultAction; if (t.currentToken == Token.DOLLAR) { nextToken(); expect(Token.NAME); final String var = t.currentTokenValue; final StructuredQName varQName = makeStructuredQName(var, ""); nextToken(); expect(Token.RETURN); nextToken(); defaultAction = makeTracer( parseTypeswitchReturnClause(varQName, outerLet), varQName); } else { t.treatCurrentAsOperator(); expect(Token.RETURN); nextToken(); defaultAction = makeTracer(parseExprSingle(), null); } Expression lastAction = defaultAction; // Note, the ragged "choose" later gets flattened into a single-level choose, saving stack space for (int i = types.size() - 1; i >= 0; i--) { final LocalVariableReference var = new LocalVariableReference(outerLet); setLocation(var); Expression ioe = new InstanceOfExpression(var, types.get(i).get(0)); for (int j = 1; j < types.get(i).size(); j++) { ioe = new OrExpression(ioe, new InstanceOfExpression(var.copy(new RebindingMap()), types.get(i).get(j))); } setLocation(ioe); final Expression ife = Choose.makeConditional(ioe, actions.get(i), lastAction); setLocation(ife); lastAction = ife; } outerLet.setAction(lastAction); return makeTracer(outerLet, null); } /*@NotNull*/ private List parseSequenceTypeList() throws XPathException { List typeList = new ArrayList<>(); while (true) { SequenceType type = parseSequenceType(); typeList.add(type); t.treatCurrentAsOperator(); if (t.currentToken == Token.UNION) { nextToken(); } else { break; } } expect(Token.RETURN); nextToken(); return typeList; } /*@NotNull*/ private Expression parseTypeswitchReturnClause(StructuredQName varQName, LetExpression outerLet) throws XPathException { Expression action; // t.treatCurrentAsOperator(); // expect(Token.RETURN); // nextToken(); LetExpression innerLet = makeLetExpression(); innerLet.setRequiredType(SequenceType.ANY_SEQUENCE); innerLet.setVariableQName(varQName); innerLet.setSequence(new LocalVariableReference(outerLet)); declareRangeVariable(innerLet); action = parseExprSingle(); undeclareRangeVariable(); innerLet.setAction(action); return innerLet; // if (Literal.isEmptySequence(action)) { // // The purpose of simplifying this now is that () is allowed in a branch even in XQuery Update when // // other branches of the typeswitch are updating. // return action; // } else { // return innerLet; // } } /** * Parse a Switch Expression. * This construct is XQuery-3.0-only. * SwitchExpr ::= "switch" "(" Expr ")" SwitchCaseClause+ "default" "return" ExprSingle * SwitchCaseClause ::= ("case" ExprSingle)+ "return" ExprSingle */ /*@NotNull*/ @Override protected Expression parseSwitchExpression() throws XPathException { // On entry, the "(" has already been read int offset = t.currentTokenStartOffset; nextToken(); Expression operand = parseExpression(); expect(Token.RPAR); nextToken(); List conditions = new ArrayList<>(10); List actions = new ArrayList<>(10); // The code generated takes the form: // let $zzz := zero-or-one(atomize(operand)) return // choose // when ($zzz eq t1) then action1 // when ($zzz eq t2) then action2 // when (true) default-action // // We rely on the optimizer to convert this to a SwitchExpression in the case where all the case clauses // are literal constants. LetExpression outerLet = makeLetExpression(); outerLet.setRequiredType(SequenceType.OPTIONAL_ATOMIC); outerLet.setVariableQName(new StructuredQName("zz", NamespaceConstant.SAXON, "zz_switchVar")); outerLet.setSequence(Atomizer.makeAtomizer(operand, null)); do { List caseExpressions = new ArrayList<>(4); expect(Token.CASE); do { nextToken(); Expression c = parseExprSingle(); caseExpressions.add(c); } while (t.currentToken == Token.CASE); expect(Token.RETURN); nextToken(); Expression action = parseExprSingle(); for (int i = 0; i < caseExpressions.size(); i++) { EquivalenceComparison vc = new EquivalenceComparison( new LocalVariableReference(outerLet), Token.FEQ, caseExpressions.get(i)); if (i == 0) { conditions.add(vc); actions.add(action); } else { OrExpression orExpr = new OrExpression(conditions.remove(conditions.size() - 1), vc); conditions.add(orExpr); } //actions.add((i==0 ? action : action.copy())); } } while (t.currentToken == Token.CASE); expect(Token.DEFAULT); nextToken(); expect(Token.RETURN); nextToken(); Expression defaultExpr = parseExprSingle(); conditions.add(Literal.makeLiteral(BooleanValue.TRUE)); actions.add(defaultExpr); Choose choice = new Choose( conditions.toArray(new Expression[0]), actions.toArray(new Expression[conditions.size()])); outerLet.setAction(choice); return makeTracer(outerLet, null); } /** * Parse a Validate Expression. * This construct is XQuery-only. The syntax allows: * validate mode? { Expr } * mode ::= "strict" | "lax" * * @throws XPathException if parsing fails */ /*@NotNull*/ @Override protected Expression parseValidateExpression() throws XPathException { int offset = t.currentTokenStartOffset; int mode = Validation.STRICT; boolean foundCurly = false; SchemaType requiredType = null; ensureSchemaAware("validate expression"); switch (t.currentToken) { case Token.VALIDATE_STRICT: mode = Validation.STRICT; nextToken(); break; case Token.VALIDATE_LAX: mode = Validation.LAX; nextToken(); break; case Token.VALIDATE_TYPE: // if (XQUERY10.equals(queryVersion)) { // grumble("validate-as-type requires XQuery 3.0"); // } mode = Validation.BY_TYPE; nextToken(); expect(Token.KEYWORD_CURLY); if (!NameChecker.isQName(StringTool.codePoints(t.currentTokenValue))) { grumble("Schema type name expected after 'validate type"); } requiredType = env.getConfiguration().getSchemaType( makeStructuredQName(t.currentTokenValue, env.getDefaultElementNamespace())); if (requiredType == null) { grumble("Unknown schema type " + t.currentTokenValue, "XQST0104"); } foundCurly = true; break; case Token.KEYWORD_CURLY: if (t.currentTokenValue.equals("validate")) { mode = Validation.STRICT; } else { throw new AssertionError("shouldn't be parsing a validate expression"); } foundCurly = true; break; } if (!foundCurly) { expect(Token.LCURLY); } nextToken(); Expression exp = parseExpression(); if (exp instanceof ParentNodeConstructor) { ((ParentNodeConstructor) exp).setValidationAction(mode, mode == Validation.BY_TYPE ? requiredType : null); } else { // the expression must return a single element or document node. The type- // checking machinery can't handle a union type, so we just check that it's // a node for now. Because we are reusing XSLT copy-of code, we need // an ad-hoc check that the node is of the right kind. // below code moved to XQuery-specific path in CopyOf // try { // RoleLocator role = new RoleLocator(RoleLocator.TYPE_OP, "validate", 0); // role.setErrorCode("XQTY0030"); // setLocation(exp); // exp = config.getTypeChecker().staticTypeCheck(exp, // SequenceType.SINGLE_NODE, // false, // role, ExpressionVisitor.make(env, getExecutable())); // } catch (XPathException err) { // grumble(err.getMessage(), err.getErrorCodeQName(), -1); // } exp = new CopyOf(exp, true, mode, requiredType, true); setLocation(exp); ((CopyOf) exp).setRequireDocumentOrElement(true); } expect(Token.RCURLY); t.lookAhead(); // always done manually after an RCURLY nextToken(); return makeTracer(exp, null); } /** * Parse an Extension Expression. * Syntax: "(#" QName arbitrary-text "#)")+ "{" expr? "}" * * @throws XPathException if parsing fails */ /*@NotNull*/ @Override protected Expression parseExtensionExpression() throws XPathException { SchemaType requiredType = null; String trimmed = Whitespace.trim(t.currentTokenValue); int c = 0; int len = trimmed.length(); while (c < len && " \t\r\n".indexOf(trimmed.charAt(c)) < 0) { c++; } String qname = trimmed.substring(0, c); String pragmaContents = ""; while (c < len && " \t\r\n".indexOf(trimmed.charAt(c)) >= 0) { c++; } if (c < len) { pragmaContents = trimmed.substring(c, len); } boolean validateType = false; StructuredQName pragmaName = makeStructuredQName(qname, ""); assert pragmaName != null; String uri = pragmaName.getURI(); String localName = pragmaName.getLocalPart(); if (uri.equals(NamespaceConstant.SAXON)) { if ("validate-type".equals(localName)) { if (!env.getConfiguration().isLicensedFeature(Configuration.LicenseFeature.ENTERPRISE_XQUERY)) { warning("Ignoring saxon:validate-type. To use this feature " + "you need the Saxon-EE processor from http://www.saxonica.com/"); } else { String typeName = Whitespace.trim(pragmaContents); if (!NameChecker.isQName(StringTool.codePoints(typeName))) { grumble("Schema type name expected in saxon:validate-type pragma: found " + Err.wrap(typeName)); } requiredType = env.getConfiguration().getSchemaType( makeStructuredQName(typeName, env.getDefaultElementNamespace())); if (requiredType == null) { grumble("Unknown schema type " + typeName); } validateType = true; } } else { warning("Ignored pragma " + qname + " (unrecognized Saxon pragma)"); } } nextToken(); Expression expr; if (t.currentToken == Token.PRAGMA) { expr = parseExtensionExpression(); } else { expect(Token.LCURLY); nextToken(); if (t.currentToken == Token.RCURLY) { t.lookAhead(); // always done manually after an RCURLY nextToken(); grumble("Unrecognized pragma, with no fallback expression", "XQST0079"); } expr = parseExpression(); expect(Token.RCURLY); t.lookAhead(); // always done manually after an RCURLY nextToken(); } if (validateType) { if (expr instanceof ParentNodeConstructor) { ((ParentNodeConstructor) expr).setValidationAction(Validation.BY_TYPE, requiredType); return expr; } else if (expr instanceof AttributeCreator) { if (!(requiredType instanceof SimpleType)) { grumble("The type used for validating an attribute must be a simple type"); } //noinspection ConstantConditions ((AttributeCreator) expr).setSchemaType((SimpleType) requiredType); ((AttributeCreator) expr).setValidationAction(Validation.BY_TYPE); return expr; } else { CopyOf copy = new CopyOf(expr, true, Validation.BY_TYPE, requiredType, true); copy.setLocation(makeLocation()); return copy; } } else { return expr; } } /** * Parse a node constructor. This is allowed only in XQuery. This method handles * both the XML-like "direct" constructors, and the XQuery-based "computed" * constructors. * * @return an Expression for evaluating the parsed constructor * @throws XPathException in the event of a syntax error. */ /*@NotNull*/ @Override protected Expression parseConstructor() throws XPathException { int offset = t.currentTokenStartOffset; switch (t.currentToken) { case Token.TAG: Expression tag = parsePseudoXML(false); lookAhead(); t.setState(Tokenizer.OPERATOR_STATE); nextToken(); return tag; case Token.KEYWORD_CURLY: String nodeKind = t.currentTokenValue; switch (nodeKind) { case "validate": grumble("A validate expression is not allowed within a path expression"); //if (nodeKind.equals("validate")) { // this allows a validate{} expression to appear as an operand of '/', which the grammar does not allow // return parseValidateExpression(); break; case "ordered": case "unordered": // these are currently no-ops in Saxon nextToken(); Expression content; if (t.currentToken == Token.RCURLY && allowXPath31Syntax) { content = Literal.makeEmptySequence(); } else { content = parseExpression(); } expect(Token.RCURLY); lookAhead(); // must be done manually after an RCURLY nextToken(); return content; case "document": return parseDocumentConstructor(offset); case "element": return parseComputedElementConstructor(offset); case "attribute": return parseComputedAttributeConstructor(offset); case "text": return parseTextNodeConstructor(offset); case "comment": return parseCommentConstructor(offset); case "processing-instruction": return parseProcessingInstructionConstructor(offset); case "namespace": return parseNamespaceConstructor(offset); default: grumble("Unrecognized node constructor " + t.currentTokenValue + "{}"); break; } break; case Token.ELEMENT_QNAME: return parseNamedElementConstructor(offset); case Token.ATTRIBUTE_QNAME: return parseNamedAttributeConstructor(offset); case Token.NAMESPACE_QNAME: return parseNamedNamespaceConstructor(offset); case Token.PI_QNAME: return parseNamedProcessingInstructionConstructor(offset); } return new ErrorExpression(); } /** * Parse document constructor: document {...} * * @param offset the location in the source query * @return the document constructor instruction * @throws XPathException if parsing fails */ /*@NotNull*/ private Expression parseDocumentConstructor(int offset) throws XPathException { nextToken(); Expression content; if (t.currentToken == Token.RCURLY && allowXPath31Syntax) { content = Literal.makeEmptySequence(); } else { content = parseExpression(); } expect(Token.RCURLY); lookAhead(); // must be done manually after an RCURLY nextToken(); DocumentInstr doc = new DocumentInstr(false, null); if (!((QueryModule) env).isPreserveNamespaces()) { content = new CopyOf(content, false, Validation.PRESERVE, null, true); } doc.setValidationAction(((QueryModule) env).getConstructionMode(), null); doc.setContentExpression(content); setLocation(doc, offset); return doc; } /** * Parse an element constructor of the form * element {expr} {expr} * * @param offset location of the expression in the source query * @return the compiled instruction * @throws XPathException if a static error is found */ /*@NotNull*/ private Expression parseComputedElementConstructor(int offset) throws XPathException { nextToken(); // get the expression that yields the element name Expression name = parseExpression(); expect(Token.RCURLY); lookAhead(); // must be done manually after an RCURLY nextToken(); expect(Token.LCURLY); t.setState(Tokenizer.DEFAULT_STATE); nextToken(); Expression content = null; if (t.currentToken != Token.RCURLY) { // get the expression that yields the element content content = parseExpression(); // if the child expression creates another element, // suppress validation, as the parent already takes care of it if (content instanceof ElementCreator && ((ElementCreator) content).getSchemaType() == null) { ((ElementCreator) content).setValidationAction(Validation.PRESERVE, null); } expect(Token.RCURLY); } lookAhead(); // done manually after an RCURLY nextToken(); Instruction inst; if (name instanceof Literal) { GroundedValue vName = ((Literal) name).getGroundedValue(); // if element name is supplied as a literal, treat it like a direct element constructor NodeName elemName; if (vName instanceof StringValue && !(vName instanceof AnyURIValue)) { String lex = ((StringValue) vName).getStringValue(); try { QNameParser oldQP = getQNameParser(); setQNameParser(oldQP.withUnescaper(null)); elemName = makeNodeName(lex, true); setQNameParser(oldQP); elemName.obtainFingerprint(env.getConfiguration().getNamePool()); } catch (XPathException staticError) { String code = staticError.getErrorCodeLocalPart(); if ("XPST0008".equals(code) || "XPST0081".equals(code)) { staticError.setErrorCode("XQDY0074"); } else if ("XPST0003".equals(code)) { //staticError.setErrorCode("XQDY0074"); grumble("Invalid QName in element constructor: " + lex, "XQDY0074", offset); return new ErrorExpression(); } staticError.setLocator(makeLocation()); staticError.setIsStaticError(false); return new ErrorExpression(new XmlProcessingException(staticError)); } } else if (vName instanceof QualifiedNameValue) { String uri = ((QualifiedNameValue) vName).getNamespaceURI(); elemName = new FingerprintedQName("", uri, ((QualifiedNameValue) vName).getLocalName()); elemName.obtainFingerprint(env.getConfiguration().getNamePool()); } else { grumble("Element name must be either a string or a QName", "XPTY0004", offset); return new ErrorExpression(); } inst = new FixedElement(elemName, ((QueryModule) env).getActiveNamespaceBindings(), ((QueryModule) env).isInheritNamespaces(), true, null, ((QueryModule) env).getConstructionMode()); if (content == null) { content = Literal.makeEmptySequence(); } if (!((QueryModule) env).isPreserveNamespaces()) { content = new CopyOf(content, false, Validation.PRESERVE, null, true); } ((FixedElement) inst).setContentExpression(content); setLocation(inst, offset); //makeContentConstructor(content, (InstructionWithChildren) inst, offset); return makeTracer(inst, elemName.getStructuredQName()); } else { // it really is a computed element constructor: save the namespace context NamespaceResolver ns = new NamespaceResolverWithDefault( env.getNamespaceResolver(), env.getDefaultElementNamespace()); inst = new ComputedElement(name, null, null, ((QueryModule) env).getConstructionMode(), ((QueryModule) env).isInheritNamespaces(), true); setLocation(inst); if (content == null) { content = Literal.makeEmptySequence(); } if (!((QueryModule) env).isPreserveNamespaces()) { content = new CopyOf(content, false, Validation.PRESERVE, null, true); } ((ComputedElement) inst).setContentExpression(content); setLocation(inst, offset); //makeContentConstructor(content, (InstructionWithChildren) inst, offset); return makeTracer(inst, null); } } /** * Parse an element constructor of the form * element qname { expr } * * @param offset the position in the source query * @return the compiled instruction * @throws XPathException if parsing fails */ private Expression parseNamedElementConstructor(int offset) throws XPathException { NodeName nodeName = makeNodeName(t.currentTokenValue, true); Expression content = null; nextToken(); if (t.currentToken != Token.RCURLY) { content = parseExpression(); expect(Token.RCURLY); } lookAhead(); // after an RCURLY nextToken(); FixedElement el2 = new FixedElement(nodeName, ((QueryModule) env).getActiveNamespaceBindings(), ((QueryModule) env).isInheritNamespaces(), true, null, ((QueryModule) env).getConstructionMode()); setLocation(el2, offset); if (content == null) { content = Literal.makeEmptySequence(); } if (!((QueryModule) env).isPreserveNamespaces()) { content = new CopyOf(content, false, Validation.PRESERVE, null, true); } el2.setContentExpression(content); return makeTracer(el2, nodeName.getStructuredQName()); } /** * Parse an attribute constructor of the form * attribute {expr} {expr} * * @param offset position of the expression in the input * @return the compiled instruction * @throws XPathException if a static error is encountered */ /*@NotNull*/ private Expression parseComputedAttributeConstructor(int offset) throws XPathException { nextToken(); Expression name = parseExpression(); expect(Token.RCURLY); lookAhead(); // must be done manually after an RCURLY nextToken(); expect(Token.LCURLY); t.setState(Tokenizer.DEFAULT_STATE); nextToken(); Expression content = null; if (t.currentToken != Token.RCURLY) { content = parseExpression(); expect(Token.RCURLY); } lookAhead(); // after an RCURLY nextToken(); if (name instanceof Literal) { GroundedValue vName = ((Literal) name).getGroundedValue(); if (vName instanceof StringValue && !(vName instanceof AnyURIValue)) { String lex = ((StringValue) vName).getStringValue(); if (lex.equals("xmlns") || lex.startsWith("xmlns:")) { grumble("Cannot create a namespace using an attribute constructor", "XQDY0044", offset); } NodeName attributeName; try { QNameParser oldQP = getQNameParser(); setQNameParser(oldQP.withUnescaper(null)); attributeName = makeNodeName(lex, false); setQNameParser(oldQP); } catch (XPathException staticError) { String code = staticError.getErrorCodeLocalPart(); staticError.setLocator(makeLocation()); if ("XPST0008".equals(code) || "XPST0081".equals(code)) { staticError.setErrorCode("XQDY0074"); } else if ("XPST0003".equals(code)) { grumble("Invalid QName in attribute constructor: " + lex, "XQDY0074", offset); return new ErrorExpression(); } throw staticError; } if (attributeName.getPrefix().isEmpty() && !attributeName.hasURI("")) { attributeName = new FingerprintedQName("_", attributeName.getURI(), attributeName.getLocalPart(), attributeName.getFingerprint()); } FixedAttribute fatt = new FixedAttribute(attributeName, Validation.STRIP, null); fatt.setRejectDuplicates(); makeSimpleContent(content, fatt, offset); return makeTracer(fatt, null); } else if (vName instanceof QNameValue) { QNameValue qnv = (QNameValue) vName; NodeName attributeName = new FingerprintedQName( qnv.getPrefix(), qnv.getNamespaceURI(), qnv.getLocalName()); attributeName.obtainFingerprint(env.getConfiguration().getNamePool()); FixedAttribute fatt = new FixedAttribute(attributeName, Validation.STRIP, null); fatt.setRejectDuplicates(); makeSimpleContent(content, fatt, offset); return makeTracer(fatt, null); } } ComputedAttribute att = new ComputedAttribute(name, null, env.getNamespaceResolver(), Validation.STRIP, null, true); att.setRejectDuplicates(); makeSimpleContent(content, att, offset); return makeTracer(att, null); } /** * Parse an attribute constructor of the form * attribute name {expr} * * @param offset position of the expression in the source * @return the parsed expression * @throws XPathException if a static error is found */ private Expression parseNamedAttributeConstructor(int offset) throws XPathException { String warningMessage = null; if (t.currentTokenValue.equals("xmlns") || t.currentTokenValue.startsWith("xmlns:")) { warningMessage = "Cannot create a namespace declaration using an attribute constructor"; } NodeName attributeName = makeNodeName(t.currentTokenValue, false); if (!attributeName.getURI().equals("") && attributeName.getPrefix().equals("")) { // This must be because the name was given as Q{uri}local. Invent a prefix. attributeName = new FingerprintedQName("_", attributeName.getURI(), attributeName.getLocalPart()); } Expression attContent = null; nextToken(); if (t.currentToken != Token.RCURLY) { attContent = parseExpression(); expect(Token.RCURLY); } lookAhead(); // after an RCURLY nextToken(); if (warningMessage == null) { FixedAttribute att2 = new FixedAttribute(attributeName, Validation.STRIP, null); att2.setRejectDuplicates(); att2.setRetainedStaticContext(env.makeRetainedStaticContext()); makeSimpleContent(attContent, att2, offset); return makeTracer(att2, attributeName.getStructuredQName()); } else { warning(warningMessage); return new ErrorExpression(warningMessage, "XQDY0044", false); } } private Expression parseTextNodeConstructor(int offset) throws XPathException { nextToken(); Expression value; if (t.currentToken == Token.RCURLY && allowXPath31Syntax) { value = Literal.makeEmptySequence(); } else { value = parseExpression(); } expect(Token.RCURLY); lookAhead(); // after an RCURLY nextToken(); Expression select = stringify(value, true, env); ValueOf vof = new ValueOf(select, false, true); setLocation(vof, offset); return makeTracer(vof, null); } private Expression parseCommentConstructor(int offset) throws XPathException { nextToken(); Expression value; if (t.currentToken == Token.RCURLY && allowXPath31Syntax) { value = Literal.makeEmptySequence(); } else { value = parseExpression(); } expect(Token.RCURLY); lookAhead(); // after an RCURLY nextToken(); Comment com = new Comment(); makeSimpleContent(value, com, offset); return makeTracer(com, null); } /** * Parse a processing instruction constructor of the form * processing-instruction {expr} {expr} * * @param offset the position of the expression in the source query * @return the compiled instruction * @throws XPathException if parsing fails */ private Expression parseProcessingInstructionConstructor(int offset) throws XPathException { nextToken(); Expression name = parseExpression(); expect(Token.RCURLY); lookAhead(); // must be done manually after an RCURLY nextToken(); expect(Token.LCURLY); t.setState(Tokenizer.DEFAULT_STATE); nextToken(); Expression content = null; if (t.currentToken != Token.RCURLY) { content = parseExpression(); expect(Token.RCURLY); } lookAhead(); // after an RCURLY nextToken(); ProcessingInstruction pi = new ProcessingInstruction(name); makeSimpleContent(content, pi, offset); return makeTracer(pi, null); } /** * Parse a processing instruction constructor of the form * processing-instruction name { expr } * * @param offset position of the expression in the source * @return the parsed expression * @throws XPathException if a static error is found */ private Expression parseNamedProcessingInstructionConstructor(int offset) throws XPathException { String target = t.currentTokenValue; String warningMessage = null; if (target.equalsIgnoreCase("xml")) { warningMessage = "A processing instruction must not be named 'xml' in any combination of upper and lower case"; } if (!NameChecker.isValidNCName(target)) { grumble("Invalid processing instruction name " + Err.wrap(target)); } Expression piName = new StringLiteral(target); Expression piContent = null; nextToken(); if (t.currentToken != Token.RCURLY) { piContent = parseExpression(); expect(Token.RCURLY); } lookAhead(); // after an RCURLY nextToken(); if (warningMessage == null) { ProcessingInstruction pi2 = new ProcessingInstruction(piName); makeSimpleContent(piContent, pi2, offset); return makeTracer(pi2, null); } else { warning(warningMessage); return new ErrorExpression(warningMessage, "XQDY0064", false); } } /** * Parse a Try/Catch Expression. * This construct is XQuery-3.0 only. The syntax allows: * try { Expr } catch NameTest ('|' NameTest)* { Expr } * We don't currently implement the CatchVars */ @Override protected Expression parseTryCatchExpression() throws XPathException { if (!allowXPath30Syntax) { grumble("try/catch requires XQuery 3.0"); } int offset = t.currentTokenStartOffset; nextToken(); Expression tryExpr; if (t.currentToken == Token.RCURLY && allowXPath31Syntax) { tryExpr = Literal.makeEmptySequence(); } else { tryExpr = parseExpression(); } TryCatch tryCatch = new TryCatch(tryExpr); setLocation(tryCatch, offset); expect(Token.RCURLY); lookAhead(); // must be done manually after an RCURLY nextToken(); boolean foundOneCatch = false; List tests = new ArrayList<>(); while (isKeyword("catch")) { tests.clear(); foundOneCatch = true; boolean seenCurly = false; do { nextToken(); String tokv = t.currentTokenValue; switch (t.currentToken) { case Token.NAME: nextToken(); tests.add(makeQNameTest(Type.ELEMENT, tokv)); break; case Token.KEYWORD_CURLY: nextToken(); tests.add(makeQNameTest(Type.ELEMENT, tokv)); seenCurly = true; break; case Token.PREFIX: nextToken(); tests.add(makeNamespaceTest(Type.ELEMENT, tokv)); break; case Token.SUFFIX: nextToken(); tokv = t.currentTokenValue; if (t.currentToken == Token.NAME) { // OK } else if (t.currentToken == Token.KEYWORD_CURLY) { // OK seenCurly = true; } else { grumble("Expected name after '*:'"); } nextToken(); tests.add(makeLocalNameTest(Type.ELEMENT, tokv)); break; case Token.STAR: case Token.MULT: nextToken(); tests.add(AnyNodeTest.getInstance()); break; default: grumble("Unrecognized name test"); return null; } } while (t.currentToken == Token.UNION && !t.currentTokenValue.equals("union")); // must be "|" not "union"! if (!seenCurly) { expect(Token.LCURLY); nextToken(); } QNameTest test; if (tests.size() == 1) { test = tests.get(0); } else { test = new UnionQNameTest(tests); } catchDepth++; Expression catchExpr; if (t.currentToken == Token.RCURLY && allowXPath31Syntax) { catchExpr = Literal.makeEmptySequence(); } else { catchExpr = parseExpression(); } tryCatch.addCatchExpression(test, catchExpr); expect(Token.RCURLY); lookAhead(); // must be done manually after an RCURLY nextToken(); catchDepth--; } if (!foundOneCatch) { grumble("After try{}, expected 'catch'"); } return tryCatch; } /** * Parse a computed namespace constructor of the form * namespace {expr}{expr} * * @param offset the location of the expression in the query source * @return the compiled Namespace instruction * @throws XPathException in the event of a syntax error */ /*@NotNull*/ private Expression parseNamespaceConstructor(int offset) throws XPathException { if (!allowXPath30Syntax) { grumble("Namespace node constructors require XQuery 3.0"); } nextToken(); Expression nameExpr = parseExpression(); expect(Token.RCURLY); lookAhead(); // must be done manually after an RCURLY nextToken(); expect(Token.LCURLY); t.setState(Tokenizer.DEFAULT_STATE); nextToken(); Expression content = null; if (t.currentToken != Token.RCURLY) { content = parseExpression(); expect(Token.RCURLY); } lookAhead(); // after an RCURLY nextToken(); NamespaceConstructor instr = new NamespaceConstructor(nameExpr); setLocation(instr); makeSimpleContent(content, instr, offset); return makeTracer(instr, null); } /** * Parse a namespace node constructor of the form * namespace name { expr } * * @param offset the location of the expression in the query source * @return the compiled instruction * @throws XPathException in the event of a syntax error */ /*@NotNull*/ private Expression parseNamedNamespaceConstructor(int offset) throws XPathException { if (!allowXPath30Syntax) { grumble("Namespace node constructors require XQuery 3.0"); } String target = t.currentTokenValue; if (!NameChecker.isValidNCName(target)) { grumble("Invalid namespace prefix " + Err.wrap(target)); } Expression nsName = new StringLiteral(target); Expression nsContent = null; nextToken(); if (t.currentToken != Token.RCURLY) { nsContent = parseExpression(); expect(Token.RCURLY); } lookAhead(); // after an RCURLY nextToken(); NamespaceConstructor instr = new NamespaceConstructor(nsName); makeSimpleContent(nsContent, instr, offset); return makeTracer(instr, null); } /** * Make the instructions for the children of a node with simple content (attribute, text, PI, etc) * * @param content the expression making up the simple content * @param inst the skeletal instruction for creating the node * @param offset the character position of this construct within the source query */ protected void makeSimpleContent(Expression content, SimpleNodeConstructor inst, int offset) { if (content == null) { inst.setSelect(new StringLiteral(StringValue.EMPTY_STRING)); } else { inst.setSelect(stringify(content, false, env)); } setLocation(inst, offset); } /** * Parse pseudo-XML syntax in direct element constructors, comments, CDATA, etc. * This is handled by reading single characters from the Tokenizer until the * end of the tag (or an enclosed expression) is enountered. * This method is also used to read an end tag. Because an end tag is not an * expression, the method in this case returns a StringValue containing the * contents of the end tag. * * @param allowEndTag true if the context allows an End Tag to appear here * @return an Expression representing the result of parsing the constructor. * If an end tag was read, its contents will be returned as a StringValue. * @throws XPathException if parsing fails */ /*@NotNull*/ private Expression parsePseudoXML(boolean allowEndTag) throws XPathException { Expression exp; int offset = t.inputOffset; // we're reading raw characters, so we don't want the currentTokenStartOffset char c = t.nextChar(); switch (c) { case '!': c = t.nextChar(); if (c == '-') { exp = parseCommentConstructor(); } else if (c == '[') { grumble("A CDATA section is allowed only in element content"); return null; // if CDATA were allowed here, we would have already read it } else { grumble("Expected '--' or '[CDATA[' after '') { break; } else if (c == Tokenizer.NUL) { grumble("Expected '>' after '/'; found end of input"); } sb.append(c); } return new StringLiteral(sb.toString()); } grumble("Unmatched XML end tag"); return new ErrorExpression(); case Tokenizer.NUL: grumble("End of input encountered while parsing direct constructor"); return new ErrorExpression(); default: t.unreadChar(); exp = parseDirectElementConstructor(allowEndTag); break; } setLocation(exp, offset); return exp; } /** * Parse a direct element constructor * * @param isNested true if this constructor is nested directly as part of the content of another * element constructor. This has the effect that the child element is not copied, which means * that namespace inheritance (which only happens during copying) has no effect * @return the expression representing the constructor * @throws XPathException if a syntax error is found */ private Expression parseDirectElementConstructor(boolean isNested) throws XPathException { NamePool pool = env.getConfiguration().getNamePool(); boolean changesContext = false; int offset = t.inputOffset - 1; // we're reading raw characters, so we don't want the currentTokenStartOffset char c; StringBuilder buff = new StringBuilder(64); int namespaceCount = 0; while (true) { c = t.nextChar(); if (c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '/' || c == '>') { break; } else if (c == Tokenizer.NUL) { grumble("Found end of input while reading element name in XQuery element constructor"); } buff.append(c); } String elname = buff.toString(); if (elname.isEmpty()) { grumble("Expected element name after '<'"); } //Used LinkedHashMap because it is friendly to retain the order of attributes. LinkedHashMap attributes = new LinkedHashMap<>(10); while (true) { // loop through the attributes // We must process namespace declaration attributes first; // their scope applies to all preceding attribute names and values. // But finding the delimiting quote of an attribute value requires the // XPath expressions to be parsed, because they may contain nested quotes. // So we parse in "scanOnly" mode, which ignores any undeclared namespace // prefixes, use the result of this parse to determine the length of the // attribute value, save the value, and reparse it when all the namespace // declarations have been dealt with. c = skipSpaces(c); if (c == '/' || c == '>') { break; } else if (c == Tokenizer.NUL) { grumble("End of input encountered within element start tag"); } int attOffset = t.inputOffset - 1; buff.setLength(0); // read the attribute name do { buff.append(c); c = t.nextChar(); } while (c != ' ' && c != '\n' && c != '\r' && c != '\t' && c != '=' && c != Tokenizer.NUL); String attName = buff.toString(); if (!NameChecker.isQName(StringTool.codePoints(attName))) { grumble("Invalid attribute name " + Err.wrap(attName, Err.ATTRIBUTE)); } c = skipSpaces(c); expectChar(c, '='); c = t.nextChar(); c = skipSpaces(c); if (c == Tokenizer.NUL) { grumble("End of input encountered within element start tag"); } char delim = c; boolean isNamespace = "xmlns".equals(attName) || attName.startsWith("xmlns:"); int end; if (isNamespace) { end = makeNamespaceContent(t.input, t.inputOffset, delim); changesContext = true; } else { Expression avt; try { avt = makeAttributeContent(t.input, t.inputOffset, delim, true); } catch (XPathException err) { if (!err.hasBeenReported()) { grumble(err.getMessage()); } throw err; } // by convention, this returns the end position when called with scanOnly set end = (int) ((Int64Value) ((Literal) avt).getGroundedValue()).longValue(); } if (end >= t.input.length()) { grumble("Reached end of input while processing attributes in start tag"); } // save the value with its surrounding quotes String val = t.input.substring(t.inputOffset - 1, end + 1); // and without String rval = t.input.substring(t.inputOffset, end); // account for any newlines found in the value // (note, subexpressions between curlies will have been parsed using a different tokenizer) String tail = val; int pos; while ((pos = tail.indexOf('\n')) >= 0) { t.incrementLineNumber(t.inputOffset - 1 + pos); tail = tail.substring(pos + 1); } t.inputOffset = end + 1; if (isNamespace) { // Processing follows the resolution of bug 5083: doubled curly braces represent single // curly braces, single curly braces are not allowed. StringBuilder sb = new StringBuilder(rval.length()); boolean prevDelim = false; boolean prevOpenCurly = false; boolean prevCloseCurly = false; for (int i = 0; i < rval.length(); i++) { char n = rval.charAt(i); if (n == delim) { prevDelim = !prevDelim; if (prevDelim) { continue; } } if (n == '{') { prevOpenCurly = !prevOpenCurly; if (prevOpenCurly) { continue; } } else if (prevOpenCurly) { grumble("Namespace must not contain an unescaped opening brace", "XQST0022"); } if (n == '}') { prevCloseCurly = !prevCloseCurly; if (prevCloseCurly) { continue; } } else if (prevCloseCurly) { grumble("Namespace must not contain an unescaped closing brace", "XPST0003"); } sb.append(n); } if (prevOpenCurly) { grumble("Namespace must not contain an unescaped opening brace", "XQST0022"); } if (prevCloseCurly) { grumble("Namespace must not contain an unescaped closing brace", "XPST0003"); } rval = sb.toString(); String uri = uriLiteral(rval); if (!StandardURIChecker.getInstance().isValidURI(uri)) { grumble("Namespace must be a valid URI value", "XQST0046"); } String prefix; if ("xmlns".equals(attName)) { prefix = ""; if (uri.equals(NamespaceConstant.XML)) { grumble("Cannot have the XML namespace as the default namespace", "XQST0070"); } } else { prefix = attName.substring(6); if (prefix.equals("xml") && !uri.equals(NamespaceConstant.XML)) { grumble("Cannot bind the prefix 'xml' to a namespace other than the XML namespace", "XQST0070"); } else if (uri.equals(NamespaceConstant.XML) && !prefix.equals("xml")) { grumble("Cannot bind a prefix other than 'xml' to the XML namespace", "XQST0070"); } else if (prefix.equals("xmlns")) { grumble("Cannot use xmlns as a namespace prefix", "XQST0070"); } if (uri.isEmpty()) { if (env.getConfiguration().getXMLVersion() == Configuration.XML10) { grumble("Namespace URI must not be empty", "XQST0085"); } } } namespaceCount++; ((QueryModule) env).declareActiveNamespace(prefix, uri); } if (attributes.get(attName) != null) { if (isNamespace) { grumble("Duplicate namespace declaration " + attName, "XQST0071", attOffset); } else { grumble("Duplicate attribute name " + attName, "XQST0040", attOffset); } } // if (attName.equals("xml:id") && !NameChecker.isValidNCName(rval)) { // grumble("Value of xml:id must be a valid NCName", "XQST0082"); // } AttributeDetails a = new AttributeDetails(); a.value = val; a.startOffset = attOffset; attributes.put(attName, a); // on return, the current character is the closing quote c = t.nextChar(); if (!(c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '/' || c == '>')) { grumble("There must be whitespace after every attribute except the last"); } } StructuredQName qName = null; if (scanOnly) { qName = StandardNames.getStructuredQName(StandardNames.XSL_ELEMENT); // any name will do } else { try { String[] parts = NameChecker.getQNameParts(elname); String namespace = ((QueryModule) env).checkURIForPrefix(parts[0]); if (namespace == null) { grumble("Undeclared prefix in element name " + Err.wrap(elname, Err.ELEMENT), "XPST0081", offset); } qName = new StructuredQName(parts[0], namespace, parts[1]); } catch (QNameException e) { grumble("Invalid element name " + Err.wrap(elname, Err.ELEMENT), "XPST0003", offset); qName = StandardNames.getStructuredQName(StandardNames.XSL_ELEMENT); // any name will do } } int validationMode = ((QueryModule) env).getConstructionMode(); FingerprintedQName fqn = new FingerprintedQName( qName.getPrefix(), qName.getURI(), qName.getLocalPart(), pool.allocateFingerprint(qName.getURI(), qName.getLocalPart())); FixedElement elInst = new FixedElement(fqn, ((QueryModule) env).getActiveNamespaceBindings(), ((QueryModule) env).isInheritNamespaces(), !isNested, null, validationMode); setLocation(elInst, offset); List contents = new ArrayList<>(10); IntHashSet attFingerprints = new IntHashSet(attributes.size()); // we've checked for duplicate lexical QNames, but not for duplicate expanded-QNames for (Map.Entry entry : attributes.entrySet()) { String attName = entry.getKey(); AttributeDetails a = entry.getValue(); String attValue = a.value; int attOffset = a.startOffset; if ("xmlns".equals(attName) || attName.startsWith("xmlns:")) { // do nothing } else if (scanOnly) { // This means we are prescanning an attribute constructor, and we found a nested attribute // constructor, which we have prescanned; we now don't want to re-process the nested attribute // constructor because it might depend on things like variables declared in the containing // attribute constructor, and in any case we're going to come back to it again later. // See test qxmp180 } else { NodeName attributeName = null; String attNamespace; try { String[] parts = NameChecker.getQNameParts(attName); if (parts[0].isEmpty()) { // attributes don't use the default namespace attNamespace = ""; } else { attNamespace = ((QueryModule) env).checkURIForPrefix(parts[0]); } if (attNamespace == null) { grumble("Undeclared prefix in attribute name " + Err.wrap(attName, Err.ATTRIBUTE), "XPST0081", attOffset); } attributeName = new FingerprintedQName(parts[0], attNamespace, parts[1]); int key = attributeName.obtainFingerprint(pool); if (attFingerprints.contains(key)) { grumble("Duplicate expanded attribute name " + attName, "XQST0040", attOffset); } attFingerprints.add(key); } catch (QNameException e) { grumble("Invalid attribute name " + Err.wrap(attName, Err.ATTRIBUTE), "XPST0003", attOffset); } assert attributeName != null; FixedAttribute attInst = new FixedAttribute(attributeName, Validation.STRIP, null); setLocation(attInst); Expression select; try { select = makeAttributeContent(attValue, 1, attValue.charAt(0), false); } catch (XPathException err) { err.setIsStaticError(true); throw err; } attInst.setRetainedStaticContext(env.makeRetainedStaticContext()); attInst.setSelect(select); attInst.setRejectDuplicates(); setLocation(attInst); contents.add(makeTracer(attInst, attributeName.getStructuredQName())); } } if (c == '/') { // empty element tag expectChar(t.nextChar(), '>'); } else { readElementContent(elname, contents); } Expression[] elk = new Expression[contents.size()]; for (int i = 0; i < contents.size(); i++) { // if the child expression creates another element, // suppress validation, as the parent already takes care of it if (validationMode != Validation.STRIP) { contents.get(i).suppressValidation(validationMode); } elk[i] = contents.get(i); } Block block = new Block(elk); if (changesContext) { block.setRetainedStaticContext(env.makeRetainedStaticContext()); } elInst.setContentExpression(block); // reset the in-scope namespaces to what they were before for (int n = 0; n < namespaceCount; n++) { ((QueryModule) env).undeclareNamespace(); } return makeTracer(elInst, qName); } /** * Parse the content of an attribute in a direct element constructor. This may contain nested expressions * within curly braces. A particular problem is that the namespaces used in the expression may not yet be * known. This means we need the ability to parse in "scanOnly" mode, where undeclared namespace prefixes * are ignored. *

The code is based on the XSLT code in {@link AttributeValueTemplate#make}: the main difference is that * character entities and built-in entity references need to be recognized and expanded. Also, whitespace * needs to be normalized, mimicking the action of an XML parser

* * @param avt the content of the attribute as written, including variable portions enclosed in curly braces * @param start the character position in the attribute value where parsing should start * @param terminator a character that is to be taken as marking the end of the expression * @param scanOnly if the purpose of this parse is simply to locate the end of the attribute value, and not * to report any semantic errors. * @return the expression that will evaluate the content of the attribute * @throws XPathException if parsing fails */ /*@Nullable*/ private Expression makeAttributeContent(/*@NotNull*/ String avt, int start, char terminator, boolean scanOnly) throws XPathException { Location loc = makeLocation(); List components = new ArrayList<>(10); int i0, i1, i2, i8, i9, len, last; last = start; len = avt.length(); while (last < len) { i2 = avt.indexOf(terminator, last); if (i2 < 0) { XPathException e = new XPathException("Attribute constructor is not properly terminated"); e.setIsStaticError(true); throw e; } i0 = avt.indexOf("{", last); i1 = avt.indexOf("{{", last); i8 = avt.indexOf("}", last); i9 = avt.indexOf("}}", last); if ((i0 < 0 || i2 < i0) && (i8 < 0 || i2 < i8)) { // found end of string addStringComponent(components, avt, last, i2); // look for doubled quotes, and skip them (for now) if (i2 + 1 < avt.length() && avt.charAt(i2 + 1) == terminator) { components.add(new StringLiteral(terminator + "")); last = i2 + 2; //continue; } else { last = i2; break; } } else if (i8 >= 0 && (i0 < 0 || i8 < i0)) { // found a "}" if (i8 != i9) { // a "}" that isn't a "}}" XPathException e = new XPathException( "Closing curly brace in attribute value template \"" + avt + "\" must be doubled"); e.setIsStaticError(true); throw e; } addStringComponent(components, avt, last, i8 + 1); last = i8 + 2; } else if (i1 >= 0 && i1 == i0) { // found a doubled "{{" addStringComponent(components, avt, last, i1 + 1); last = i1 + 2; } else if (i0 >= 0) { // found a single "{" if (i0 > last) { addStringComponent(components, avt, last, i0); } Expression exp; XPathParser parser = newParser(); ((XQueryParser) parser).executable = executable; parser.setAllowAbsentExpression(allowXPath31Syntax); parser.setScanOnly(scanOnly); parser.setRangeVariableStack(rangeVariables); parser.setCatchDepth(catchDepth); exp = parser.parse(avt, i0 + 1, Token.RCURLY, env); if (!scanOnly) { exp = exp.simplify(); } last = parser.getTokenizer().currentTokenStartOffset + 1; components.add(makeStringJoin(exp, env)); } else { throw new IllegalStateException("Internal error parsing direct attribute constructor"); } } // if this is simply a prescan, return the position of the end of the // AVT, so we can parse it properly later if (scanOnly) { return Literal.makeLiteral(Int64Value.makeIntegerValue(last)); } // is it empty? if (components.isEmpty()) { return new StringLiteral(StringValue.EMPTY_STRING); } // is it a single component? if (components.size() == 1) { return components.get(0); } // otherwise, return an expression that concatenates the components Expression[] args = new Expression[components.size()]; args = components.toArray(args); RetainedStaticContext rsc = new RetainedStaticContext(env); Expression fn = SystemFunction.makeCall("concat", rsc, args); assert fn != null; fn.setLocation(loc); return fn; //return visitor.simplify(fn); } private void addStringComponent(/*@NotNull*/ List components, /*@NotNull*/ String avt, int start, int end) throws XPathException { // analyze fixed text within the value of a direct attribute constructor. if (start < end) { StringBuilder sb = new StringBuilder(end - start); for (int i = start; i < end; i++) { char c = avt.charAt(i); switch (c) { case '&': { int semic = avt.indexOf(';', i); if (semic < 0) { grumble("No closing ';' found for entity or character reference"); } else { String entity = avt.substring(i + 1, semic); sb.append(new Unescaper(env.getConfiguration().getValidCharacterChecker()).analyzeEntityReference(entity)); i = semic; } break; } case '<': grumble("The < character must not appear in attribute content"); break; case '\n': case '\t': sb.append(' '); break; case '\r': sb.append(' '); if (i + 1 < end && avt.charAt(i + 1) == '\n') { i++; } break; default: sb.append(c); break; } } components.add(new StringLiteral(sb.toString())); } } /** * Parse the content of an namespace declaration attribute in a direct element constructor. This is simpler * than an ordinary attribute because it does not contain nested expressions in curly braces. (But see bug 5083). * * @param avt the content of the attribute as written, including variable portions enclosed in curly braces * @param start the character position in the attribute value where parsing should start * @param terminator a character that is to be taken as marking the end of the expression * @return the position of the end of the URI value * @throws XPathException if parsing fails */ private int makeNamespaceContent(/*@NotNull*/ String avt, int start, char terminator) throws XPathException { int i2, len, last; last = start; len = avt.length(); while (last < len) { i2 = avt.indexOf(terminator, last); if (i2 < 0) { XPathException e = new XPathException("Namespace declaration is not properly terminated"); e.setIsStaticError(true); throw e; } // look for doubled quotes, and skip them (for now) if (i2 + 1 < avt.length() && avt.charAt(i2 + 1) == terminator) { last = i2 + 2; //continue; } else { last = i2; break; } } // return the position of the end of the literal return last; } /** * Read the content of a direct element constructor * * @param startTag the element start tag * @param components an empty list, to which the expressions comprising the element contents are added * @throws XPathException if any static errors are detected */ private void readElementContent(String startTag, /*@NotNull*/ List components) throws XPathException { boolean afterEnclosedExpr = false; while (true) { // read all the components of the element value StringBuilder text = new StringBuilder(64); char c; boolean containsEntities = false; while (true) { c = t.nextChar(); if (c == '<') { // See if we've got a CDATA section if (t.nextChar() == '!') { if (t.nextChar() == '[') { readCDATASection(text); containsEntities = true; continue; } else { t.unreadChar(); t.unreadChar(); } } else { t.unreadChar(); } break; } else if (c == '&') { text.append(readEntityReference()); containsEntities = true; } else if (c == '}') { c = t.nextChar(); if (c != '}') { grumble("'}' must be written as '}}' within element content"); } text.append(c); } else if (c == '{') { c = t.nextChar(); if (c != '{') { c = '{'; break; } text.append(c); } else if (c == Tokenizer.NUL) { grumble("Reached end of input while reading XQuery element content"); } else { if (!charChecker.test(c) && !UTF16CharacterSet.isSurrogate(c)) { grumble("Character code " + c + " is not a valid XML character"); } text.append(c); } } String textStr = text.toString(); if (!textStr.isEmpty() && (containsEntities | ((QueryModule) env).isPreserveBoundarySpace() || !Whitespace.isAllWhite(StringView.of(textStr)))) { ValueOf inst = new ValueOf(new StringLiteral(new StringValue(textStr)), false, false); setLocation(inst); components.add(inst); afterEnclosedExpr = false; } if (c == '<') { Expression exp = parsePseudoXML(true); // An end tag can appear here, and is returned as a string value if (exp instanceof StringLiteral) { String endTag = ((StringLiteral) exp).getString().toString(); if (Whitespace.isWhite(endTag.charAt(0))) { grumble("End tag contains whitespace before the name"); } endTag = Whitespace.trim(endTag); if (endTag.equals(startTag)) { return; } else { grumble("End tag does not match start tag <" + startTag + '>', "XQST0118"); // error code allocated by spec bug 11609 } } else { components.add(exp); } } else { // we read an '{' indicating an enclosed expression if (afterEnclosedExpr) { Expression previousComponent = components.get(components.size() - 1); boolean previousComponentIsNodeTest = true; UType previousItemType = previousComponent.getStaticUType(UType.ANY); previousComponentIsNodeTest = UType.ANY_NODE.subsumes(previousItemType); if (!previousComponentIsNodeTest) { // Add a zero-length text node, to prevent {"a"}{"b"} generating an intervening space // See tests (qxmp132, qxmp261) ValueOf inst = new ValueOf(new StringLiteral(StringValue.EMPTY_STRING), false, false); setLocation(inst); components.add(inst); } } t.unreadChar(); t.setState(Tokenizer.DEFAULT_STATE); lookAhead(); nextToken(); if (t.currentToken == Token.RCURLY && allowXPath31Syntax) { components.add(Literal.makeEmptySequence()); } else { Expression exp = parseExpression(); if (!((QueryModule) env).isPreserveNamespaces()) { exp = new CopyOf(exp, false, Validation.PRESERVE, null, true); } components.add(exp); expect(Token.RCURLY); } afterEnclosedExpr = true; } } } /*@Nullable*/ private Expression parsePIConstructor() throws XPathException { StringBuilder pi = new StringBuilder(64); int firstSpace = -1; while (!pi.toString().endsWith("?>")) { char c = t.nextChar(); if (c == Tokenizer.NUL) { grumble("Found end of input while reading processing instruction constructor"); } if (firstSpace < 0 && " \t\r\n".indexOf(c) >= 0) { firstSpace = pi.length(); } pi.append(c); } pi.setLength(pi.length() - 2); String target; String data = ""; if (firstSpace < 0) { // there is no data part target = pi.toString(); } else { // trim leading space from the data part, but not trailing space target = pi.toString().substring(0, firstSpace); firstSpace++; while (firstSpace < pi.length() && " \t\r\n".indexOf(pi.charAt(firstSpace)) >= 0) { firstSpace++; } data = pi.toString().substring(firstSpace); } if (!NameChecker.isValidNCName(target)) { grumble("Invalid processing instruction name " + Err.wrap(target)); } if (target.equalsIgnoreCase("xml")) { grumble("A processing instruction must not be named 'xml' in any combination of upper and lower case"); } ProcessingInstruction instruction = new ProcessingInstruction(new StringLiteral(target)); instruction.setSelect(new StringLiteral(data)); setLocation(instruction); return instruction; } private void readCDATASection(StringBuilder cdata) throws XPathException { char c; // CDATA section c = t.nextChar(); expectChar(c, 'C'); c = t.nextChar(); expectChar(c, 'D'); c = t.nextChar(); expectChar(c, 'A'); c = t.nextChar(); expectChar(c, 'T'); c = t.nextChar(); expectChar(c, 'A'); c = t.nextChar(); expectChar(c, '['); while (!cdata.toString().endsWith("]]>")) { char cc = t.nextChar(); if (cc == Tokenizer.NUL) { grumble("No closing ']]>' found for CDATA section"); } cdata.append(cc); } cdata.setLength(cdata.length() - 3); } /*@Nullable*/ private Expression parseCommentConstructor() throws XPathException { char c = t.nextChar(); // XML-like comment expectChar(c, '-'); StringBuilder comment = new StringBuilder(256); while (!comment.toString().endsWith("--")) { char cc = t.nextChar(); if (cc == Tokenizer.NUL) { grumble("Reached end of input while reading XML comment constructor"); } comment.append(cc); } if (t.nextChar() != '>') { grumble("'--' is not permitted in an XML comment"); } String commentText = comment.substring(0, comment.length() - 2); Comment instruction = new Comment(); instruction.setSelect(new StringLiteral(new StringValue(commentText.toString()))); setLocation(instruction); return instruction; } /** * Convert an expression so it generates a space-separated sequence of strings * * @param exp the expression that calculates the content * @param noNodeIfEmpty if true, no node is produced when the value of the content * expression is an empty sequence. If false, the effect of supplying an empty sequence * is that a node is created whose string-value is a zero-length string. Set to true for * text node constructors, false for other kinds of node. * @param env the static context * @return an expression that computes the content and converts the result to a character string */ public static Expression stringify(Expression exp, boolean noNodeIfEmpty, StaticContext env) { // Compare with XSLLeafNodeConstructor.makeSimpleContentConstructor // Fast path if given a string literal if (exp instanceof StringLiteral) { return exp; } if (exp.getLocalRetainedStaticContext() == null) { exp.setRetainedStaticContext(env.makeRetainedStaticContext()); } // Atomize the result exp = Atomizer.makeAtomizer(exp, null); // Convert each atomic value to a string exp = new AtomicSequenceConverter(exp, BuiltInAtomicType.STRING); //((AtomicSequenceConverter) exp).allocateConverter(config, false); // Join the resulting strings with a separator exp = SystemFunction.makeCall("string-join", exp.getRetainedStaticContext(), exp, new StringLiteral(StringValue.SINGLE_SPACE)); assert exp != null; if (noNodeIfEmpty) { ((StringJoin) ((SystemFunctionCall) exp).getTargetFunction()).setReturnEmptyIfEmpty(true); } // All that's left for the instruction to do is to construct the right kind of node return exp; } /** * Method to make a string literal from a token identified as a string * literal. This is trivial in XPath, but in XQuery the method is overridden * to identify pseudo-XML character and entity references * * @param token the string as written (or as returned by the tokenizer) * @return The string value of the string literal, after dereferencing entity and * character references * @throws XPathException if parsing fails */ @Override /*@NotNull*/ protected Literal makeStringLiteral(/*@NotNull*/ String token) throws XPathException { StringLiteral lit; if (token.indexOf('&') == -1) { lit = new StringLiteral(token); } else { CharSequence sb = unescape(token); lit = new StringLiteral(StringValue.makeStringValue(sb)); } setLocation(lit); return lit; } /** * Unescape character references and built-in entity references in a string * * @param token the input string, which may include XML-style character references or built-in * entity references * @return the string with character references and built-in entity references replaced by their expansion * @throws XPathException if a malformed character or entity reference is found */ /*@NotNull*/ @Override protected String unescape(/*@NotNull*/ String token) throws XPathException { return new Unescaper(env.getConfiguration().getValidCharacterChecker()).unescape(token); } public static class Unescaper { private final IntPredicateProxy characterChecker; public Unescaper(IntPredicateProxy characterChecker) { this.characterChecker = characterChecker; } public String unescape(String token) throws XPathException { StringBuilder sb = new StringBuilder(token.length()); for (int i = 0; i < token.length(); i++) { char c = token.charAt(i); if (c == '&') { int semic = token.indexOf(';', i); if (semic < 0) { throw new XPathException("No closing ';' found for entity or character reference", "XPST0003"); } else { String entity = token.substring(i + 1, semic); sb.append(analyzeEntityReference(entity)); i = semic; } } else { sb.append(c); } } return sb.toString(); } /*@Nullable*/ public String analyzeEntityReference(/*@NotNull*/ String entity) throws XPathException { if ("lt".equals(entity)) { return "<"; } else if ("gt".equals(entity)) { return ">"; } else if ("amp".equals(entity)) { return "&"; } else if ("quot".equals(entity)) { return "\""; } else if ("apos".equals(entity)) { return "'"; } else if (entity.length() < 2 || entity.charAt(0) != '#') { throw new XPathException("invalid character reference &" + entity + ';', "XPST0003"); } else { //entity = entity.toLowerCase(); return parseCharacterReference(entity); } } /*@Nullable*/ private String parseCharacterReference(/*@NotNull*/ String entity) throws XPathException { int value = 0; if (entity.charAt(1) == 'x') { if (entity.length() < 3) { throw new XPathException("No hex digits in hexadecimal character reference", "XPST0003"); } entity = entity.toLowerCase(); for (int i = 2; i < entity.length(); i++) { int digit = "0123456789abcdef".indexOf(entity.charAt(i)); if (digit < 0) { throw new XPathException("Invalid hex digit '" + entity.charAt(i) + "' in character reference", "XPST0003"); } value = (value * 16) + digit; if (value > UTF16CharacterSet.NONBMP_MAX) { throw new XPathException("Character reference exceeds Unicode codepoint limit", "XQST0090"); } } } else { for (int i = 1; i < entity.length(); i++) { int digit = "0123456789".indexOf(entity.charAt(i)); if (digit < 0) { throw new XPathException("Invalid digit '" + entity.charAt(i) + "' in decimal character reference", "XPST0003"); } value = (value * 10) + digit; if (value > UTF16CharacterSet.NONBMP_MAX) { throw new XPathException("Character reference exceeds Unicode codepoint limit", "XQST0090"); } } } if (!characterChecker.test(value)) { throw new XPathException("Invalid XML character reference x" + Integer.toHexString(value), "XQST0090"); } // following code borrowed from AElfred // Check for surrogates: 00000000 0000xxxx yyyyyyyy zzzzzzzz // (1101|10xx|xxyy|yyyy + 1101|11yy|zzzz|zzzz: if (value <= 0x0000ffff) { // no surrogates needed return "" + (char) value; } else { assert value <= 0x0010ffff; value -= 0x10000; // > 16 bits, surrogate needed return "" + (char) (0xd800 | (value >> 10)) + (char) (0xdc00 | (value & 0x0003ff)); } } } /** * Read a pseudo-XML character reference or entity reference. * * @return The character represented by the character or entity reference. Note * that this is a string rather than a char because a char only accommodates characters * up to 65535. * @throws XPathException if the character or entity reference is not well-formed */ /*@Nullable*/ private String readEntityReference() throws XPathException { StringBuilder sb = new StringBuilder(64); while (true) { char c = t.nextChar(); if (c == ';') { break; } else if (c == Tokenizer.NUL) { grumble("No closing ';' found for entity or character reference"); return ""; // to keep the Java compiler happy } sb.append(c); } String entity = sb.toString(); return new Unescaper(env.getConfiguration().getValidCharacterChecker()).analyzeEntityReference(entity); } /** * Parse a string constructor: introduced in XQuery 3.1 */ @Override protected Expression parseStringConstructor() throws XPathException { // For legacy reasons (see bug 4208) parsing of string constructors is split // rather arbitrarily between the parser and tokenizer. This method is called // when the tokenizer has seen the sequence ``[xxxx`{ which it reports as // a STRING_CONSTRUCTOR_INITIAL token. At this point it hands over to the parser, // which continues by parsing the enclosed expression, and then reading // character-by-character to get the literal content outside the enclosed expressions. int offset = t.currentTokenStartOffset; if (!allowXPath31Syntax) { throw new XPathException("String constructor expressions require XQuery 3.1"); } List components = new ArrayList<>(); components.add(new StringLiteral(t.currentTokenValue)); t.next(); outer: while (true) { boolean emptyExpression = t.currentToken == Token.RCURLY; if (emptyExpression) { components.add(new StringLiteral(StringValue.EMPTY_STRING)); } else { Expression enclosed = parseExpression(); Expression stringJoin = SystemFunction.makeCall( "string-join", env.makeRetainedStaticContext(), enclosed, new StringLiteral(" ")); components.add(stringJoin); } if (t.currentToken != Token.RCURLY) { grumble("Expected '}' after enclosed expression in string constructor"); } StringBuilder sb = new StringBuilder(256); char c = t.nextChar(); if (c != '`') { grumble("Expected '}`' after enclosed expression in string constructor"); } char prior = (char) 0; char penult = (char) 0; boolean continueOuter = false; while (true) { c = t.nextChar(); if (c == Tokenizer.NUL) { grumble("Reached end of input while reading string constructor"); } if (prior == '`' && c == '{') { sb.setLength(sb.length() - 1); components.add(new StringLiteral(sb.toString())); t.lookAhead(); t.next(); if (t.currentToken == Token.RCURLY) { components.add(Literal.makeEmptySequence()); sb.setLength(0); continue; } else { continueOuter = true; break; } } else if (penult == ']' && prior == '`' && c == '`') { sb.setLength(sb.length() - 2); components.add(new StringLiteral(sb.toString())); t.lookAhead(); t.next(); continueOuter = false; break; } sb.append(c); penult = prior; prior = c; } if (!continueOuter) { break; } } Expression[] args = components.toArray(new Expression[0]); Expression result = SystemFunction.makeCall("concat", env.makeRetainedStaticContext(), args); setLocation(result, offset); return result; } /** * Handle a URI literal. This is whitespace-normalized as well as being unescaped * * @param in the string as written * @return the URI after unescaping of entity and character references * followed by whitespace normalization * @throws net.sf.saxon.trans.XPathException if an error is found while unescaping the URI */ public String uriLiteral(/*@NotNull*/ String in) throws XPathException { return Whitespace.collapse(unescape(in)).toString(); } /** * Lookahead one token, catching any exception thrown by the tokenizer. This * method is only called from the query parser when switching from character-at-a-time * mode to tokenizing mode * * @throws XPathException if parsing fails */ protected void lookAhead() throws XPathException { try { t.lookAhead(); } catch (XPathException err) { grumble(err.getMessage()); } } @Override protected boolean atStartOfRelativePath() { return t.currentToken == Token.TAG || super.atStartOfRelativePath(); // "<" after "/" is recognized in XQuery but not in XPath. } @Override protected void testPermittedAxis(int axis, String errorCode) throws XPathException { super.testPermittedAxis(axis, errorCode); if (axis == AxisInfo.NAMESPACE && language == ParsedLanguage.XQUERY) { grumble("The namespace axis is not available in XQuery", errorCode); } } /** * Skip whitespace. * * @param c the current character * @return the first character after any whitespace, or NUL at end of input */ private char skipSpaces(char c) { while (c == ' ' || c == '\n' || c == '\r' || c == '\t') { c = t.nextChar(); } return c; } /** * Test whether the current character is the expected character. * * @param actual The character that was read * @param expected The character that was expected * @throws XPathException if they are different */ private void expectChar(char actual, char expected) throws XPathException { if (actual != expected) { grumble("Expected '" + expected + "', found " + (actual == Tokenizer.NUL ? "end of input" : "'" + actual + "'")); } } /** * Get the current language (XPath or XQuery) */ /*@NotNull*/ @Override protected String getLanguage() { return "XQuery"; } private static class AttributeDetails { String value; int startOffset; } private static class Import { String namespaceURI; List locationURIs; int offset; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy