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

net.sf.saxon.expr.instruct.EvaluateInstr Maven / Gradle / Ivy

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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.expr.instruct;

import net.sf.saxon.Configuration;
import net.sf.saxon.Controller;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.elab.*;
import net.sf.saxon.expr.parser.*;
import net.sf.saxon.expr.sort.LFUCache;
import net.sf.saxon.functions.ExecutableFunctionLibrary;
import net.sf.saxon.functions.FunctionLibrary;
import net.sf.saxon.functions.FunctionLibraryList;
import net.sf.saxon.functions.registry.BuiltInFunctionSet;
import net.sf.saxon.lib.Feature;
import net.sf.saxon.ma.map.HashTrieMap;
import net.sf.saxon.ma.map.MapItem;
import net.sf.saxon.om.*;
import net.sf.saxon.style.PublicStylesheetFunctionLibrary;
import net.sf.saxon.style.StylesheetFunctionLibrary;
import net.sf.saxon.style.StylesheetPackage;
import net.sf.saxon.sxpath.IndependentContext;
import net.sf.saxon.sxpath.XPathVariable;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.iter.AtomicIterator;
import net.sf.saxon.tree.iter.ManualIterator;
import net.sf.saxon.type.AtomicType;
import net.sf.saxon.type.BuiltInAtomicType;
import net.sf.saxon.type.ItemType;
import net.sf.saxon.value.*;

import java.util.*;
import java.util.function.Predicate;
import java.util.function.Supplier;


/**
 * An EvaluateInstr is the compiled form of an xsl:evaluate instruction
 * 

The implementation maintains a cache of compiled expressions, provided that the namespace context * is not constructed dynamically. The cache is an LRU cache with a fixed size of 100 entries. This is * to avoid uncontrolled use of memory when the XPath expressions are completely dynamic and each one * is unique.

*/ public final class EvaluateInstr extends Expression { private Operand xpathOp; private final SequenceType requiredType; private Operand contextItemOp; private Operand baseUriOp; private Operand namespaceContextOp; private Operand schemaAwareOp; private Operand optionsOp; private Set importedSchemaNamespaces = new HashSet<>(); private WithParam[] actualParams; private Operand dynamicParamsOp; private NamespaceUri defaultXPathNamespace = null; /** * Create an xsl:evaluate instruction * * @param xpath The expression that returns the string that is to be parsed as an XPath expression * @param requiredType the required type of the result of evaluating the XPath expression * @param contextItemExpr The expression that delivers the context item * @param baseUriExpr the expression whose value is used as the static base URI of the XPath expression * @param namespaceContextExpr the expression that delivers a node whose namespace context provides the * static namespace context for the XPath expression * @param schemaAwareExpr an expression whose value is true if the XPath expression is to be treated as schema * aware, or false otherwise */ public EvaluateInstr(Expression xpath, SequenceType requiredType, Expression contextItemExpr, Expression baseUriExpr, Expression namespaceContextExpr, Expression schemaAwareExpr) { if (xpath != null) { xpathOp = new Operand(this, xpath, OperandRole.SINGLE_ATOMIC); } if (contextItemExpr != null) { contextItemOp = new Operand(this, contextItemExpr, OperandRole.NAVIGATE); } if (baseUriExpr != null) { baseUriOp = new Operand(this, baseUriExpr, OperandRole.SINGLE_ATOMIC); } if (namespaceContextExpr != null) { namespaceContextOp = new Operand(this, namespaceContextExpr, OperandRole.INSPECT); } if (schemaAwareExpr != null) { schemaAwareOp = new Operand(this, schemaAwareExpr, OperandRole.SINGLE_ATOMIC); } this.requiredType = requiredType; } public void setOptionsExpression(Expression options) { optionsOp = new Operand(this, options, OperandRole.ABSORB); } public void setActualParameters(WithParam[] params) { setActualParams(params); } public void setDefaultXPathNamespace(NamespaceUri defaultXPathNamespace) { this.defaultXPathNamespace = defaultXPathNamespace; } /** * Ask whether this expression is an instruction. In XSLT streamability analysis this * is used to distinguish constructs corresponding to XSLT instructions from other constructs, * typically XPath expressions. * * @return true (if this construct exists at all in an XSLT environment, then it represents an instruction) */ @Override public boolean isInstruction() { return true; } /** * Add an imported schema namespace * @param ns the namespace to be imported ("" for the non-namespace) */ public void importSchemaNamespace(NamespaceUri ns) { if (importedSchemaNamespaces == null) { importedSchemaNamespaces = new HashSet<>(); } importedSchemaNamespaces.add(ns); } /** * Type-check the expression */ /*@NotNull*/ @Override public Expression typeCheck(ExpressionVisitor visitor, ContextItemStaticInfo contextInfo) throws XPathException { importedSchemaNamespaces = visitor.getStaticContext().getImportedSchemaNamespaces(); typeCheckChildren(visitor, contextInfo); WithParam.typeCheck(getActualParams(), visitor, contextInfo); return this; } /*@NotNull*/ @Override public Expression optimize(ExpressionVisitor visitor, ContextItemStaticInfo contextItemType) throws XPathException { optimizeChildren(visitor, contextItemType); return this; } /** * Determine the data type of the items returned by this expression * * @return the data type */ /*@NotNull*/ @Override public final ItemType getItemType() { return requiredType.getPrimaryType(); } @Override protected int computeCardinality() { return requiredType.getCardinality(); } /** * Add a representation of this expression to a PathMap. The PathMap captures a map of the nodes visited * by an expression in a source tree. *

The default implementation of this method assumes that an expression does no navigation other than * the navigation done by evaluating its subexpressions, and that the subexpressions are evaluated in the * same context as the containing expression. The method must be overridden for any expression * where these assumptions do not hold. For example, implementations exist for AxisExpression, ParentExpression, * and RootExpression (because they perform navigation), and for the doc(), document(), and collection() * functions because they create a new navigation root. Implementations also exist for PathExpression and * FilterExpression because they have subexpressions that are evaluated in a different context from the * calling expression.

* * @param pathMap the PathMap to which the expression should be added * @param pathMapNodeSet the set of nodes in the path map that are affected * @return the pathMapNode representing the focus established by this expression, in the case where this * expression is the first operand of a path expression or filter expression. For an expression that does * navigation, it represents the end of the arc in the path map that describes the navigation route. For other * expressions, it is the same as the input pathMapNode. */ @Override public PathMap.PathMapNodeSet addToPathMap(PathMap pathMap, PathMap.PathMapNodeSet pathMapNodeSet) { throw new UnsupportedOperationException("Cannot do document projection when xsl:evaluate is used"); } @Override public int getIntrinsicDependencies() { return StaticProperty.DEPENDS_ON_FOCUS | StaticProperty.DEPENDS_ON_XSLT_CONTEXT; // assume the worst } @Override public Iterable operands() { List sub = new ArrayList<>(8); if (xpathOp != null) { sub.add(xpathOp); } if (contextItemOp != null) { sub.add(contextItemOp); } if (baseUriOp != null) { sub.add(baseUriOp); } if (namespaceContextOp != null) { sub.add(namespaceContextOp); } if (schemaAwareOp != null) { sub.add(schemaAwareOp); } if (dynamicParamsOp != null) { sub.add(dynamicParamsOp); } if (optionsOp != null) { sub.add(optionsOp); } WithParam.gatherOperands(this, getActualParams(), sub); return sub; } /** * An implementation of Expression must provide at least one of the methods evaluateItem(), iterate(), or process(). * This method indicates which of these methods is provided. This implementation provides the iterate() method natively. */ @Override public int getImplementationMethod() { return ITERATE_METHOD; } /** * Copy an expression. This makes a deep copy. * * @return the copy of the original expression * @param rebindings a mutable list of (old binding, new binding) pairs * that is used to update the bindings held in any * local variable references that are copied. */ /*@NotNull*/ @Override public Expression copy(RebindingMap rebindings) { EvaluateInstr e2 = new EvaluateInstr(getXpath().copy(rebindings), requiredType, getContextItemExpr().copy(rebindings), getBaseUriExpr() == null ? null : getBaseUriExpr().copy(rebindings), getNamespaceContextExpr() == null ? null : getNamespaceContextExpr().copy(rebindings), getSchemaAwareExpr() == null ? null : getSchemaAwareExpr().copy(rebindings)); ExpressionTool.copyLocationInfo(this, e2); e2.setRetainedStaticContext(getRetainedStaticContext()); e2.importedSchemaNamespaces = importedSchemaNamespaces; e2.setActualParams(WithParam.copy(e2, getActualParams(), rebindings)); if (optionsOp != null) { e2.setOptionsExpression(optionsOp.getChildExpression().copy(rebindings)); } if (dynamicParamsOp != null) { e2.setDynamicParams(dynamicParamsOp.getChildExpression().copy(rebindings)); } return e2; } /*@NotNull*/ @Override public SequenceIterator iterate(final XPathContext context) throws XPathException { return makeElaborator().elaborateForPull().iterate(context); } /** * Diagnostic print of expression structure. The abstract expression tree * is written to the supplied output destination. */ @Override public void export(ExpressionPresenter out) throws XPathException { out.startElement("evaluate", this); if (!SequenceType.ANY_SEQUENCE.equals(requiredType)) { out.emitAttribute("as", requiredType.toAlphaCode()); } if (importedSchemaNamespaces != null && !importedSchemaNamespaces.isEmpty()) { StringBuilder buff = new StringBuilder(256); for (NamespaceUri s : importedSchemaNamespaces) { buff.append(s.isEmpty() ? "##" : s); buff.append(' '); } buff.setLength(buff.length() - 1); out.emitAttribute("schNS", buff.toString()); } if(defaultXPathNamespace != null) { out.emitAttribute("dxns", defaultXPathNamespace.toString()); } out.setChildRole("xpath"); getXpath().export(out); if (getContextItemExpr() != null) { out.setChildRole("cxt"); getContextItemExpr().export(out); } if (getBaseUriExpr() != null) { out.setChildRole("baseUri"); getBaseUriExpr().export(out); } if (getNamespaceContextExpr() != null) { out.setChildRole("nsCxt"); getNamespaceContextExpr().export(out); } if (getSchemaAwareExpr() != null) { out.setChildRole("sa"); getSchemaAwareExpr().export(out); } if (optionsOp != null) { out.setChildRole("options"); optionsOp.getChildExpression().export(out); } WithParam.exportParameters(actualParams, out, false); if (dynamicParamsOp != null) { out.setChildRole("wp"); getDynamicParams().export(out); } out.endElement(); } public Expression getXpath() { return xpathOp.getChildExpression(); } public void setXpath(Expression xpath) { if (xpathOp == null) { xpathOp = new Operand(this, xpath, OperandRole.SINGLE_ATOMIC); } else { xpathOp.setChildExpression(xpath); } } public Expression getContextItemExpr() { return contextItemOp == null ? null : contextItemOp.getChildExpression(); } public void setContextItemExpr(Expression contextItemExpr) { if (contextItemOp == null) { contextItemOp = new Operand(this, contextItemExpr, OperandRole.NAVIGATE); } else { contextItemOp.setChildExpression(contextItemExpr); } } public Expression getBaseUriExpr() { return baseUriOp == null ? null : baseUriOp.getChildExpression(); } public void setBaseUriExpr(Expression baseUriExpr) { if (baseUriOp == null) { baseUriOp = new Operand(this, baseUriExpr, OperandRole.SINGLE_ATOMIC); } else { baseUriOp.setChildExpression(baseUriExpr); } } public Expression getNamespaceContextExpr() { return namespaceContextOp == null ? null : namespaceContextOp.getChildExpression(); } public void setNamespaceContextExpr(Expression namespaceContextExpr) { if (namespaceContextOp == null) { namespaceContextOp = new Operand(this, namespaceContextExpr, OperandRole.INSPECT); } else { namespaceContextOp.setChildExpression(namespaceContextExpr); } } public Expression getSchemaAwareExpr() { return schemaAwareOp == null ? null : schemaAwareOp.getChildExpression(); } public void setSchemaAwareExpr(Expression schemaAwareExpr) { if (schemaAwareOp == null) { schemaAwareOp = new Operand(this, schemaAwareExpr, OperandRole.SINGLE_ATOMIC); } else { schemaAwareOp.setChildExpression(schemaAwareExpr); } } public WithParam[] getActualParams() { return actualParams; } public void setActualParams(WithParam[] actualParams) { this.actualParams = actualParams; } public boolean isActualParam(StructuredQName name) { for (WithParam wp : actualParams) { if (wp.getVariableQName().equals(name)) { return true; } } return false; } public void setDynamicParams(Expression params) { if (dynamicParamsOp == null) { dynamicParamsOp = new Operand(this, params, OperandRole.SINGLE_ATOMIC); } else { dynamicParamsOp.setChildExpression(params); } } public Expression getDynamicParams() { return dynamicParamsOp.getChildExpression(); } /** * Make an elaborator for this expression * * @return an appropriate {@link Elaborator} */ @Override public Elaborator getElaborator() { return new EvaluateInstrElaborator(); } private static class EvaluateInstrElaborator extends PullElaborator { @Override public PullEvaluator elaborateForPull() { EvaluateInstr instr = (EvaluateInstr) getExpression(); StringEvaluator exprTextEval = instr.getXpath().makeElaborator().elaborateForString(false); StringEvaluator baseUriEval = instr.getBaseUriExpr() == null ? null : instr.getBaseUriExpr().makeElaborator().elaborateForString(false); ItemEvaluator contextItemEval = instr.getContextItemExpr().makeElaborator().elaborateForItem(); ItemEvaluator namespaceContextEval = instr.getNamespaceContextExpr() == null ? null : instr.getNamespaceContextExpr().makeElaborator().elaborateForItem(); StringEvaluator schemaAwareEval = instr.getSchemaAwareExpr().makeElaborator().elaborateForString(false); ItemEvaluator dynamicParamsEval = instr.getDynamicParams() == null ? null : instr.getDynamicParams().makeElaborator().elaborateForItem(); ItemEvaluator optionsEval = instr.optionsOp == null ? null : instr.optionsOp.getChildExpression().makeElaborator().elaborateForItem(); return context -> { Configuration config = context.getConfiguration(); if (config.getBooleanProperty(Feature.DISABLE_XSL_EVALUATE)) { throw new XPathException("xsl:evaluate has been disabled", "XTDE3175"); } final String exprText = exprTextEval.eval(context); String baseUri = baseUriEval == null ? instr.getStaticBaseURIString() : Whitespace.trim(baseUriEval.eval(context)); Item focus = contextItemEval.eval(context); NodeInfo namespaceContextBase = null; if (namespaceContextEval != null) { namespaceContextBase = (NodeInfo) namespaceContextEval.eval(context); } String schemaAwareAttr = Whitespace.trim(schemaAwareEval.eval(context)); boolean isSchemaAware; if ("yes".equals(schemaAwareAttr) || "true".equals(schemaAwareAttr) || "1".equals(schemaAwareAttr)) { isSchemaAware = true; } else if ("no".equals(schemaAwareAttr) || "false".equals(schemaAwareAttr) || "0".equals(schemaAwareAttr)) { isSchemaAware = false; } else { throw new XPathException("The schema-aware attribute of xsl:evaluate must be yes|no|true|false|0|1") .withErrorCode("XTDE0030") .withLocation(instr.getLocation()) .withXPathContext(context); } Expression expr = null; SlotManager slotMap = null; // Create a cache key so the compiled expression can be reused StringBuilder fsb = new StringBuilder(exprText.length() + (baseUri == null ? 4 : baseUri.length()) + 40); fsb.append(baseUri); fsb.append("##"); fsb.append(schemaAwareAttr); fsb.append("##"); fsb.append(exprText); if (namespaceContextBase != null) { fsb.append("##"); namespaceContextBase.generateId(fsb); } String cacheKey = fsb.toString(); Collection declaredVars = null; Controller controller = context.getController(); LFUCache cache; //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized(controller) { cache = (LFUCache) controller.getUserData(instr.getLocation(), "xsl:evaluate"); if (cache == null) { cache = new LFUCache<>(100); controller.setUserData(instr.getLocation(), "xsl:evaluate", cache); } else { Object[] o = cache.get(cacheKey); if (o != null) { expr = (Expression) o[0]; slotMap = (SlotManager) o[1]; declaredVars = (Collection) o[2]; } } } MapItem dynamicParams = null; if (dynamicParamsEval != null) { dynamicParams = (MapItem) dynamicParamsEval.eval(context); } if (expr == null) { // Expression needs to be compiled. First create the static context... int version = instr.getRetainedStaticContext().getPackageData().getHostLanguageVersion(); // if (version == 30) { // version = 31; // } MapItem options = (optionsEval == null ? new HashTrieMap() : (MapItem) optionsEval.eval(context)); IndependentContext env = new IndependentContext(config); env.setWarningHandler((str, loc) -> { String message = "In dynamic expression {" + exprText + "}: " + str; context.getController().warning(message, null, loc); }); env.setBaseURI(baseUri); env.setExecutable(context.getController().getExecutable()); env.setXPathLanguageLevel(version == 40 ? 40 : config.getConfigurationProperty(Feature.XPATH_VERSION_FOR_XSLT)); env.setDefaultCollationName(instr.getRetainedStaticContext().getDefaultCollationName()); if (namespaceContextEval != null) { env.setNamespaces(namespaceContextBase); } else { env.setNamespaceResolver(instr.getRetainedStaticContext()); env.setDefaultElementNamespace(instr.getRetainedStaticContext().getDefaultElementNamespace()); } // Copy the function library list, except for XSLT-defined system functions and private user-written functions FunctionLibraryList libraryList0 = ((StylesheetPackage) instr.getRetainedStaticContext().getPackageData()).getFunctionLibrary(); FunctionLibraryList libraryList1 = new FunctionLibraryList(); for (FunctionLibrary lib : libraryList0.getLibraryList()) { if (lib instanceof BuiltInFunctionSet && ((BuiltInFunctionSet) lib).getNamespace().equals(NamespaceUri.FN)) { // Exclude XSLT-defined functions libraryList1.addFunctionLibrary(config.getXPathFunctionSet(version == 40 ? 40 : 31)); // see bug 6221 } else if (lib instanceof StylesheetFunctionLibrary || lib instanceof ExecutableFunctionLibrary) { libraryList1.addFunctionLibrary(new PublicStylesheetFunctionLibrary(lib)); } else { libraryList1.addFunctionLibrary(lib); } } env.setFunctionLibrary(libraryList1); env.setDecimalFormatManager(instr.getRetainedStaticContext().getDecimalFormatManager()); //env.setXPathLanguageLevel(config.getConfigurationProperty(Feature.XPATH_VERSION_FOR_XSLT)); if (isSchemaAware) { GroundedValue allowAny = options.get(StringValue.bmp("allow-any-namespace")); if (allowAny != null && allowAny.effectiveBooleanValue()) { env.setImportedSchemaNamespaces(config.getImportedNamespaces()); } else { env.setImportedSchemaNamespaces(instr.importedSchemaNamespaces); } } GroundedValue defaultCollation = options.get(StringValue.bmp("default-collation")); if (defaultCollation != null) { env.setDefaultCollationName(defaultCollation.head().getStringValue()); } Map locals = new HashMap<>(); if (dynamicParams != null) { SequenceTool.supply(dynamicParams.keys(), (ItemConsumer) paramName -> { if (!(paramName instanceof QNameValue)) { final AtomicType primitiveItemType = ((AtomicValue) paramName).getPrimitiveType(); XPathException err = new XPathException( "Parameter names supplied to xsl:evaluate must have type xs:QName, not " + primitiveItemType.getDisplayName(), "XTTE3165"); err.setIsTypeError(true); throw err; } XPathVariable var = env.declareVariable((QNameValue) paramName); locals.put(((QNameValue) paramName).getStructuredQName(), var.getLocalSlotNumber()); }); } if (instr.getActualParams() != null) { for (WithParam actualParam : instr.getActualParams()) { StructuredQName name = actualParam.getVariableQName(); if (!locals.containsKey(name)) { XPathVariable var = env.declareVariable(name); locals.put(name, var.getLocalSlotNumber()); } } } // Now compile the expression try { expr = ExpressionTool.make(exprText, env, 0, Token.EOF, null); } catch (XPathException e) { throw new XPathException("Static error in XPath expression supplied to xsl:evaluate: " + e.getMessage() + ". Expression: {" + exprText + "}") .withErrorCode("XTDE3160") .withLocation(instr.getLocation()); } // Type check, and allocate slots for variables expr.setRetainedStaticContext(env.makeRetainedStaticContext()); Supplier role = () -> new RoleDiagnostic(RoleDiagnostic.EVALUATE_RESULT, exprText, 0); ExpressionVisitor visitor = ExpressionVisitor.make(env); TypeChecker tc = config.getTypeChecker(false); expr = tc.staticTypeCheck(expr, instr.requiredType, role, visitor); expr = ExpressionTool.resolveCallsToCurrentFunction(expr); ContextItemStaticInfo cit; if (instr.getContextItemExpr() != null) { cit = config.makeContextItemStaticInfo( instr.getContextItemExpr().getItemType(), Cardinality.allowsZero(instr.getContextItemExpr().getCardinality())); } else { cit = ContextItemStaticInfo.ABSENT; } expr = expr.typeCheck(visitor, cit).optimize(visitor, cit); slotMap = env.getStackFrameMap(); ExpressionTool.allocateSlots(expr, slotMap.getNumberOfVariables(), slotMap); //expr.setContainer(env); // Save the compiled expression in the cache for next time if (cacheKey != null) { declaredVars = env.getDeclaredVariables(); cache.put(cacheKey, new Object[]{expr, slotMap, declaredVars}); //System.err.println("Cache miss, size = " + cache.size()); } } XPathContextMajor c2 = context.newContext(); if (focus == null) { c2.setCurrentIterator(null); } else { ManualIterator mono = new ManualIterator(focus); c2.setCurrentIterator(mono); } c2.openStackFrame(slotMap); if (instr.getActualParams() != null) { for (int i = 0; i < instr.getActualParams().length; i++) { final StructuredQName variableQName = instr.getActualParams()[i].getVariableQName(); if (dynamicParams != null && dynamicParams.get(new QNameValue(variableQName, BuiltInAtomicType.QNAME)) != null) { // Don't evaluate xsl:with-param if there is a dynamic parameter of the same name continue; } int slot = slotMap.getVariableMap().indexOf(variableQName); c2.setLocalVariable(slot, instr.getActualParams()[i].getSelectValue(context)); } } if (dynamicParams != null) { AtomicIterator iter = dynamicParams.keys(); QNameValue paramName; while ((paramName = (QNameValue) iter.next()) != null) { int slot = slotMap.getVariableMap().indexOf(paramName.getStructuredQName()); if (slot >= 0) { // can be false if the with-params changes from one call to the next c2.setLocalVariable(slot, dynamicParams.get(paramName)); } } } // Check that all required variables are present for (XPathVariable var : declaredVars) { final StructuredQName name = var.getVariableQName(); Predicate nameMatch = e -> e instanceof LocalVariableReference && ((LocalVariableReference) e).getVariableName().equals(name) && ((LocalVariableReference) e).getBinding() instanceof XPathVariable; if (dynamicParams != null && dynamicParams.get(new QNameValue(name, BuiltInAtomicType.QNAME)) == null && !instr.isActualParam(name) && ExpressionTool.contains(expr, false, nameMatch)) { throw new XPathException("No value has been supplied for variable " + name.getDisplayName(), "XPST0008"); } } try { return expr.iterate(c2); } catch (XPathException err) { throw err.withMessage("Dynamic error in expression {" + exprText + "} called using xsl:evaluate") .withLocation(instr.getLocation()); } }; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy