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

net.sf.saxon.functions.hof.LoadXqueryModule Maven / Gradle / Ivy

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2020 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.functions.hof;

import net.sf.saxon.Configuration;
import net.sf.saxon.Controller;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.instruct.GlobalContextRequirement;
import net.sf.saxon.expr.instruct.GlobalVariable;
import net.sf.saxon.expr.instruct.UserFunction;
import net.sf.saxon.functions.OptionsParameter;
import net.sf.saxon.functions.SystemFunction;
import net.sf.saxon.lib.ModuleURIResolver;
import net.sf.saxon.lib.StandardModuleURIResolver;
import net.sf.saxon.ma.map.*;
import net.sf.saxon.om.*;
import net.sf.saxon.query.*;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trans.SaxonErrorCode;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.iter.AtomicIterator;
import net.sf.saxon.type.BuiltInAtomicType;
import net.sf.saxon.type.ItemType;
import net.sf.saxon.value.*;

import javax.xml.transform.stream.StreamSource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * This class implements the function load-xquery-module(), which is a standard function in XPath 3.1.
 * It is classified as a higher-order function and therefore requires Saxon-PE or higher.
 */
public class LoadXqueryModule extends SystemFunction implements Callable {

    public static OptionsParameter makeOptionsParameter() {
        OptionsParameter op = new OptionsParameter();
        op.addAllowedOption("xquery-version", SequenceType.SINGLE_DECIMAL);
        op.addAllowedOption("location-hints", SequenceType.STRING_SEQUENCE);
        op.addAllowedOption("context-item", SequenceType.OPTIONAL_ITEM);
        op.addAllowedOption("variables", SequenceType.makeSequenceType(new MapType(BuiltInAtomicType.QNAME, SequenceType.ANY_SEQUENCE), StaticProperty.EXACTLY_ONE)); // standard type?
        op.addAllowedOption("vendor-options", SequenceType.makeSequenceType(new MapType(BuiltInAtomicType.QNAME, SequenceType.ANY_SEQUENCE), StaticProperty.EXACTLY_ONE));
        return op;
    }

    /**
     * Prepare an XPathContext object for evaluating the function
     *
     * @param callingContext the XPathContext of the function calling expression
     * @param originator identifies the location of the caller for diagnostics
     * @return a suitable context for evaluating the function (which may or may
     * not be the same as the caller's context)
     */
    @Override
    public XPathContext makeNewContext(XPathContext callingContext, ContextOriginator originator) {
        return callingContext;
    }

    /**
     * Invoke the function
     *
     * @param context the XPath dynamic evaluation context
     * @param args    the actual arguments to be supplied
     * @return the result of invoking the function
     * @throws net.sf.saxon.trans.XPathException if a dynamic error occurs within the function
     */

    @Override
    public MapItem call(XPathContext context, Sequence[] args) throws XPathException {
        Sequence xqueryVersionOption = null;
        Sequence locationHintsOption = null;
        Sequence variablesOption = null;
        Sequence contextItemOption = null;
        Sequence vendorOptionsOption = null;
        if (args.length == 2) {
            MapItem suppliedOptions = (MapItem) args[1].head();
            Map checkedOptions = getDetails().optionDetails.processSuppliedOptions(suppliedOptions, context);
            xqueryVersionOption = checkedOptions.get("xquery-version");
            if (xqueryVersionOption != null) {
                double vn = ((DecimalValue) xqueryVersionOption.head()).getDoubleValue();
                if (vn*10 > 31) {
                    throw new XPathException("No XQuery version " + vn + " processor is available", "FOQM0006");
                }
            }
            locationHintsOption = checkedOptions.get("location-hints");
            variablesOption = checkedOptions.get("variables");
            contextItemOption = checkedOptions.get("context-item");
            vendorOptionsOption = checkedOptions.get("vendor-options");

        }

        int qv = 31;
        if (xqueryVersionOption != null) {
            BigDecimal decimalVn = ((DecimalValue) xqueryVersionOption.head()).getDecimalValue();
            if (decimalVn.equals(new BigDecimal("1.0")) || decimalVn.equals(new BigDecimal("3.0")) || decimalVn.equals(new BigDecimal("3.1"))) {
                qv = decimalVn.multiply(BigDecimal.TEN).intValue();
            } else {
                throw new XPathException("Unsupported XQuery version " + decimalVn, "FOQM0006");
            }
        }

        NamespaceUri moduleUri = NamespaceUri.of(args[0].head().getStringValue());
        if (moduleUri.isEmpty()) {
            throw new XPathException("First argument of fn:load-xquery-module() must not be a zero length string", "FOQM0001");
        }
        List locationHints = new ArrayList<>(); // location hints are currently ignored by QT3TestDriver?
        if (locationHintsOption != null) {
            SequenceIterator iterator = locationHintsOption.iterate();
            Item hint;
            while ((hint = iterator.next()) != null) {
                locationHints.add(hint.getStringValue());
            }
        }

        // Set the vendor options (configuration features) -- at the moment none supported
        /*if (vendorOptionsOption != null) {
            MapItem vendorOptions = (MapItem) options.get(new StringValue("vendor-options")).head();
        }*/

        Configuration config = context.getConfiguration();
        StaticQueryContext staticQueryContext = config.newStaticQueryContext();
        ModuleURIResolver moduleURIResolver = config.getModuleURIResolver();
        if (moduleURIResolver == null) {
            moduleURIResolver = new StandardModuleURIResolver(config);
        }
        staticQueryContext.setModuleURIResolver(moduleURIResolver);
        String baseURI = getRetainedStaticContext().getStaticBaseUriString();
        staticQueryContext.setBaseURI(baseURI);
        StreamSource[] streamSources;
        try {
            String[] hints = locationHints.toArray(new String[0]);
            streamSources = staticQueryContext.getModuleURIResolver().resolve(moduleUri.toString(), baseURI, hints);
            if (streamSources == null) {
                streamSources = new StandardModuleURIResolver(config).resolve(moduleUri.toString(), baseURI, hints);
            }
        } catch (XPathException e) {
            e.maybeSetErrorCode("FOQM0002");
            throw e;
        }
        if (streamSources.length == 0) {
            throw new XPathException("No library module found with specified target namespace " + moduleUri, "FOQM0002");
        }


        try {
            // Note: Location hints other than the first are ignored
            String sourceQuery = QueryReader.readSourceQuery(streamSources[0], config.getValidCharacterChecker() );
            staticQueryContext.compileLibrary(sourceQuery);
        } catch (XPathException e) {
            throw new XPathException(e.getMessage(), "FOQM0003"); // catch when module is invalid
        }
        QueryLibrary lib = staticQueryContext.getCompiledLibrary(moduleUri);
        if (lib == null) {
            throw new XPathException("The library module located does not have the expected namespace " + moduleUri, "FOQM0002");
        }
        QueryModule main = new QueryModule(staticQueryContext); // module to be loaded is a library module not a main module
        // so use alternative constructor?
        main.setPackageData(lib.getPackageData());
        main.setExecutable(lib.getExecutable());
        lib.link(main);
        XQueryExpression xqe = new XQueryExpression(new ContextItemExpression(), main, false);
        DynamicQueryContext dqc = new DynamicQueryContext(context.getConfiguration());

        // Get the external variables and set parameters on DynamicQueryContext dqc
        if (variablesOption != null) {
            MapItem extVariables = (MapItem) variablesOption.head();
            AtomicIterator iterator = extVariables.keys();
            AtomicValue key;
            while ((key = iterator.next()) != null) {
                dqc.setParameter(((QNameValue) key).getStructuredQName(), ((Sequence) extVariables.get(key)).materialize());
            }
        }

        // Get the context item supplied, and set it on the new Controller
        if (contextItemOption != null) {
            Item contextItem = contextItemOption.head();
            GlobalContextRequirement gcr = main.getExecutable().getGlobalContextRequirement();
            if (gcr != null) {
                ItemType req = gcr.getRequiredItemType();
                if (req != null && !req.matches(contextItem, config.getTypeHierarchy())) {
                    throw new XPathException("Required context item type is " + req, "FOQM0005");
                }
            }
            dqc.setContextItem(contextItemOption.head());
        }

        Controller newController = xqe.newController(dqc);

        XPathContext newContext = newController.newXPathContext();

        // Evaluate the global variables, and add values to the result.

        MapItem variablesMap = new HashTrieMap();
        for (GlobalVariable var : lib.getImportedGlobalVariables()) {
            GroundedValue value;
            QNameValue qNameValue = new QNameValue(var.getVariableQName(), BuiltInAtomicType.QNAME);
            if (qNameValue.getNamespaceURI().equals(moduleUri) && !var.isPrivate()) {
                try {
                    value = var.evaluateVariable(newContext);
                } catch (XPathException e) {
                    e.setIsGlobalError(false);  // to make it catchable
                    if (e.getErrorCodeLocalPart().equals("XPTY0004")) {
                        throw new XPathException(e.getMessage(), "FOQM0005"); // catches when external variables have wrong type
                    } else {
                        throw e;
                    }
                }
                variablesMap = variablesMap.addEntry(qNameValue, value);
            }
        }
        // Add functions to the result.
        MapItem functionsMap = new HashTrieMap();
        XQueryFunctionLibrary functionLib = lib.getGlobalFunctionLibrary();

        ExportAgent agent = new ExportAgent() {
            @Override
            public void export(ExpressionPresenter out) throws XPathException {
                XPathException err = new XPathException(
                        "Cannot export a stylesheet that statically incorporates XQuery functions",
                        SaxonErrorCode.SXST0069);
                err.setIsStaticError(true);
                throw err;
            }
        };
        for (XQueryFunction function : functionLib.getFunctionDefinitions()) {
            MapItem newMap;
            QNameValue functionQName = new QNameValue(function.getFunctionName(), BuiltInAtomicType.QNAME);
            if (functionQName.getNamespaceURI().equals(moduleUri) && !function.isPrivate()) {
                UserFunction userFunction = function.getUserFunction();
                UserFunctionReference.BoundUserFunction buf =
                        new UserFunctionReference.BoundUserFunction(userFunction, userFunction.getArity(), null, agent, newController);
                if (functionsMap.get(functionQName) != null) {
                    newMap = ((MapItem) functionsMap.get(functionQName)).addEntry(new Int64Value(function.getNumberOfParameters()), buf);
                } else {
                    newMap = new SingleEntryMap(Int64Value.makeIntegerValue(function.getNumberOfParameters()), buf);
                }
                functionsMap = functionsMap.addEntry(functionQName, newMap);
            }
        }

        DictionaryMap map = new DictionaryMap();
        map.initialPut("variables", variablesMap);
        map.initialPut("functions", functionsMap);
        return map;
    }

}

// Copyright (c) 2018-2023 Saxonica Limited




© 2015 - 2024 Weber Informatics LLC | Privacy Policy