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

net.sf.saxon.expr.parser.ExpressionTool 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.expr.parser;

import net.sf.saxon.Configuration;
import net.sf.saxon.Controller;
import net.sf.saxon.event.ComplexContentOutputter;
import net.sf.saxon.event.SequenceCollector;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.flwor.Clause;
import net.sf.saxon.expr.flwor.FLWORExpression;
import net.sf.saxon.expr.flwor.LocalVariableBinding;
import net.sf.saxon.expr.instruct.*;
import net.sf.saxon.expr.sort.ConditionalSorter;
import net.sf.saxon.expr.sort.DocumentSorter;
import net.sf.saxon.functions.*;
import net.sf.saxon.functions.hof.UserFunctionReference;
import net.sf.saxon.lib.Logger;
import net.sf.saxon.lib.NamespaceConstant;
import net.sf.saxon.lib.StandardLogger;
import net.sf.saxon.om.*;
import net.sf.saxon.pattern.Pattern;
import net.sf.saxon.str.StringView;
import net.sf.saxon.style.Compilation;
import net.sf.saxon.style.ScopedBindingElement;
import net.sf.saxon.trans.Err;
import net.sf.saxon.trans.NoDynamicContextException;
import net.sf.saxon.trans.SymbolicName;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.transpile.CSharpSuppressWarnings;
import net.sf.saxon.type.AnyItemType;
import net.sf.saxon.type.ItemType;
import net.sf.saxon.value.*;

import javax.xml.transform.SourceLocator;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;

/**
 * This class, ExpressionTool, contains a number of useful static methods
 * for manipulating expressions. Most importantly, it provides the factory
 * method make() for constructing a new expression
 */

public class ExpressionTool {

    private ExpressionTool() {
    }

    /**
     * Parse an XPath expression. This performs the basic analysis of the expression against the
     * grammar, it binds variable references and function calls to variable definitions and
     * function definitions, and it performs context-independent expression rewriting for
     * optimization purposes.
     *
     * @param expression   The expression (as a character string)
     * @param env          An object giving information about the compile-time
     *                     context of the expression
     * @param start        position of the first significant character in the expression
     * @param terminator   The token that marks the end of this expression; typically
     *                     Token.EOF, but may for example be a right curly brace
     * @param codeInjector true  allows injection of tracing, debugging, or performance monitoring code; null if
     *                     not required
     * @return an object of type Expression
     * @throws XPathException if the expression contains a static error
     */

    /*@NotNull*/
    public static Expression make(String expression, StaticContext env,
                                  int start, int terminator,
                                  CodeInjector codeInjector) throws XPathException {
        int languageLevel = env.getXPathVersion();
        XPathParser parser = env.getConfiguration().newExpressionParser("XP", false, languageLevel);
        if (codeInjector != null) {
            parser.setCodeInjector(codeInjector);
        }
        if (terminator == -1) {
            terminator = Token.EOF;
        }
        Expression exp = parser.parse(expression, start, terminator, env);
        // TODO: parser.parse() already sets the retained static context
        setDeepRetainedStaticContext(exp, env.makeRetainedStaticContext());
        exp = exp.simplify();
        return exp;
    }

    /**
     * Ensure that every node in the expression tree has a retained static context.
     * A node that already has a retained static context will propagate it to its children.
     *
     * @param exp the root of the expression tree
     * @param rsc the retained static context to be applied to nodes that do not already have one,
     *            and that do not have an ancestor with an existing retained static context.
     */

    public static void setDeepRetainedStaticContext(Expression exp, RetainedStaticContext rsc) {
        if (exp.getLocalRetainedStaticContext() == null) {
            exp.setRetainedStaticContextLocally(rsc);
        } else {
            rsc = exp.getLocalRetainedStaticContext();
        }
        for (Operand o : exp.operands()) {
            setDeepRetainedStaticContext(o.getChildExpression(), rsc);
        }
    }

    /**
     * Copy location information (the line number and reference to the container) from one expression
     * to another
     *
     * @param from the expression containing the location information
     * @param to   the expression to which the information is to be copied
     */

    public static void copyLocationInfo(Expression from, Expression to) {
        if (from != null && to != null) {
            if (to.getLocation() == null || to.getLocation() == Loc.NONE) {
                to.setLocation(from.getLocation());
            }
            if (to.getLocalRetainedStaticContext() == null) {
                to.setRetainedStaticContextLocally(from.getLocalRetainedStaticContext());
            }
        }
    }

    /**
     * Remove unwanted sorting from an expression, at compile time, if and only if it is known
     * that the result of the expression will be homogeneous (all nodes, or all atomic values).
     * This is done when we need the effective boolean value of a sequence: the EBV of a
     * homogenous sequence does not depend on its order, but this is not true when atomic
     * values and nodes are mixed: (N, AV) is true, but (AV, N) is an error.
     *
     * @param exp          the expression to be optimized
     * @param forStreaming true if streamed evaluation of the expression is required
     * @return the expression after rewriting
     * @throws net.sf.saxon.trans.XPathException if a static error is found while doing the rewrite
     */

    public static Expression unsortedIfHomogeneous(Expression exp, boolean forStreaming)
            throws XPathException {
        if (exp instanceof Literal) {
            return exp;   // fast exit
        }
        if (exp.getItemType() instanceof AnyItemType) {
            return exp;
        } else {
            return exp.unordered(false, forStreaming);
        }
    }

    /**
     * Inject extra code into an expression, recursively, by applying a supplied {@link CodeInjector}
     * to every expression (and FLWOR clause) in the subtree
     *
     * @param exp      the expression to be augmented with injected code
     * @param injector the code injector
     * @return the augmented expression
     */

    public static Expression injectCode(Expression exp, CodeInjector injector) {
        if (exp instanceof FLWORExpression) {
            ((FLWORExpression) exp).injectCode(injector);
        } else if (exp instanceof TraceExpression) {
            for (Operand o : ((TraceExpression) exp).getChild().operands()) {
                if (!o.getOperandRole().isConstrainedClass()) {
                    o.setChildExpression(injectCode(o.getChildExpression(), injector));
                }
            }
        } else {
            for (Operand o : exp.operands()) {
                if (!o.getOperandRole().isConstrainedClass()) {
                    o.setChildExpression(injectCode(o.getChildExpression(), injector));
                }
            }
        }
        return injector.inject(exp);
    }

    /**
     * Get an expression evaluator to be used when lazy evaluation of an expression is
     * preferred. This method is called at compile time, after all optimizations have been done,
     * to determine the preferred strategy for lazy evaluation, depending on the type of expression.
     *
     * @param exp        the expression to be evaluated
     * @param repeatable true if the returned value needs to be readable more than once
     * @return an expression evaluator
     */

    public static Evaluator lazyEvaluator(Expression exp, boolean repeatable) {
        if (exp instanceof Literal) {
            return Evaluator.Literal.INSTANCE;

        } else if (exp instanceof VariableReference) {
            return Evaluator.Variable.INSTANCE;

        } else if (exp instanceof SuppliedParameterReference) {
            return Evaluator.SuppliedParameter.INSTANCE;

        } else if ((exp.getDependencies() &
                            (StaticProperty.DEPENDS_ON_POSITION |
                                     StaticProperty.DEPENDS_ON_LAST |
                                     StaticProperty.DEPENDS_ON_CURRENT_ITEM |
                                     StaticProperty.DEPENDS_ON_CURRENT_GROUP |
                                     StaticProperty.DEPENDS_ON_REGEX_GROUP)) != 0) {
            // we can't save these values in the closure, so we evaluate
            // the expression now if they are needed
            return eagerEvaluator(exp);

        } else if (exp instanceof ErrorExpression) {
            return Evaluator.SingleItem.INSTANCE;
            // evaluateItem() on an error expression throws the latent exception

        } else if (!Cardinality.allowsMany(exp.getCardinality())) {
            // singleton expressions are always evaluated eagerly
            return eagerEvaluator(exp);

        } else if (exp instanceof TailExpression) {
            // Treat tail recursion as a special case, to avoid creating a deeply-nested
            // tree of Closures.
            TailExpression tail = (TailExpression) exp;
            Expression base = tail.getBaseExpression();
            if (base instanceof VariableReference) {
                return Evaluator.LazyTail.INSTANCE;
            } else if (repeatable) {
                return Evaluator.MemoClosureEvaluator.INSTANCE;
            } else {
                return Evaluator.LazySequence.INSTANCE;
            }

        } else if (exp instanceof Block && ((Block) exp).isCandidateForSharedAppend()) {
            // If the expression is a Block, that is, it is appending a value to a sequence,
            // then we have the opportunity to use a shared list underpinning the old value and
            // the new. This takes precedence over lazy evaluation (it would be possible to do this
            // lazily, but more difficult). We currently do this for any Block that has a variable
            // reference as one of its subexpressions. The most common case is that the first argument is a reference
            // to an argument of recursive function, where the recursive function returns the result of
            // appending to the sequence.
            return Evaluator.SharedAppend.INSTANCE;

        } else if (repeatable) {
            // create a Closure, a wrapper for the expression and its context
            return Evaluator.MemoClosureEvaluator.INSTANCE;
        } else {
            return Evaluator.LazySequence.INSTANCE;
        }
    }


    /**
     * Get the evaluator to be used when eager evaluation of an expression is
     * preferred. This method is called at compile time, after all optimizations have been done,
     * to determine the preferred strategy for lazy evaluation, depending on the type of expression.
     *
     * @param exp the expression to be evaluated
     * @return an integer constant identifying the evaluation mode
     */

    public static Evaluator eagerEvaluator(Expression exp) {
        if (exp instanceof Literal && !(((Literal) exp).getGroundedValue() instanceof Closure)) {
            return Evaluator.Literal.INSTANCE;
        }
        if (exp instanceof VariableReference) {
            return Evaluator.Variable.INSTANCE;
        }
        int m = exp.getImplementationMethod();
        if ((m & Expression.EVALUATE_METHOD) != 0 && !Cardinality.allowsMany(exp.getCardinality())) {
            if (Cardinality.allowsZero(exp.getCardinality())) {
                return Evaluator.OptionalItem.INSTANCE;
            } else {
                return Evaluator.SingleItem.INSTANCE;
            }

        } else if ((m & Expression.ITERATE_METHOD) != 0) {
            return Evaluator.EagerSequence.INSTANCE;
        } else {
            return Evaluator.Process.INSTANCE;
        }
    }


    /**
     * Do lazy evaluation of an expression. This will return a value, which may optionally
     * be a SequenceIntent, which is a wrapper around an iterator over the value of the expression.
     *
     * @param exp        the expression to be evaluated
     * @param context    the run-time evaluation context for the expression. If
     *                   the expression is not evaluated immediately, then parts of the
     *                   context on which the expression depends need to be saved as part of
     *                   the Closure
     * @param repeatable an indication of how the value will be used. The value false indicates that the value
     *                   is only expected to be used once, so that there is no need to keep it in memory. The value true
     *                   indicates multiple references, so the value will be saved when first evaluated.
     * @return a value: either the actual value obtained by evaluating the
     * expression, or a Closure containing all the information needed to
     * evaluate it later
     * @throws XPathException if any error occurs in evaluating the
     *                        expression
     */

    public static Sequence lazyEvaluate(Expression exp, XPathContext context, boolean repeatable) throws XPathException {
        Evaluator evaluator = lazyEvaluator(exp, repeatable);
        return evaluator.evaluate(exp, context);
    }

    /**
     * Evaluate an expression now; lazy evaluation is not permitted in this case
     *
     * @param exp     the expression to be evaluated
     * @param context the run-time evaluation context
     * @return the result of evaluating the expression
     * @throws net.sf.saxon.trans.XPathException if any dynamic error occurs evaluating the
     *                                           expression
     */

    public static GroundedValue eagerEvaluate(Expression exp, XPathContext context) throws XPathException {
        Evaluator evaluator = eagerEvaluator(exp);
        return evaluator.evaluate(exp, context).materialize();
    }

    /**
     * Scan an expression to find and mark any recursive tail function calls
     *
     * @param exp   the expression to be analyzed
     * @param qName the name of the containing function
     * @param arity the arity of the containing function
     * @return 0 if no tail call was found; 1 if a tail call to a different function was found;
     * 2 if a tail call to the specified function was found. In this case the
     * UserFunctionCall object representing the tail function call will also have been marked as
     * a tail call.
     */

    public static int markTailFunctionCalls(Expression exp, StructuredQName qName, int arity) {
        return exp.markTailFunctionCalls(qName, arity);
    }

    /**
     * Construct indent string, for diagnostic output
     *
     * @param level the indentation level (the number of spaces to return)
     * @return a string of "level*2" spaces
     */

    public static String indent(int level) {
        StringBuilder fsb = new StringBuilder(level);
        for (int i = 0; i < level; i++) {
            fsb.append("  ");
        }
        return fsb.toString();
    }

    /**
     * Ask whether one expression is a subexpression of another
     *
     * @param a the containing expression
     * @param b the putative contained expression
     * @return true if and only if b is a subexpression (at some level) of a
     */

    public static boolean contains(Expression a, Expression b) {
        Expression temp = b;
        while (temp != null) {
            if (temp == a) {
                return true;
            } else {
                temp = temp.getParentExpression();
            }
        }
        return false;
    }

    /**
     * Determine whether an expression contains a LocalParamSetter subexpression
     *
     * @param exp the expression to be tested
     * @return true if the exprssion contains a local parameter setter
     */

    public static boolean containsLocalParam(Expression exp) {
        return contains(exp, true, e -> e instanceof LocalParam);
    }

    /**
     * Determine whether an expression contains a local variable reference,
     * other than a reference to a local variable whose binding occurs
     * within this expression
     *
     * @param exp the expression to be tested
     * @return true if the expression contains a local variable reference to a local
     * variable declared outside this expression.
     */

    public static boolean containsLocalVariableReference(final Expression exp) {
        return contains(exp, false, e -> {
            if (e instanceof LocalVariableReference) {
                LocalVariableReference vref = (LocalVariableReference) e;
                LocalBinding binding = vref.getBinding();
                return !(binding instanceof Expression && contains(exp, (Expression) binding));
            }
            return false;
        });
    }

    /**
     * Test whether a given expression is, or contains, at any depth, an expression that satisfies a supplied
     * condition
     *
     * @param exp           the given expression
     * @param sameFocusOnly if true, only subexpressions evaluated in the same focus are searched. If false,
     *                      all subexpressions are searched
     * @param predicate     the condition to be satisfied
     * @return true if the given expression is or contains an expression that satisfies the condition.
     */

    public static boolean contains(Expression exp, boolean sameFocusOnly, Predicate predicate) {
        if (predicate.test(exp)) {
            return true;
        }
        for (Operand info : exp.operands()) {
            if ((info.hasSameFocus() || !sameFocusOnly) && contains(info.getChildExpression(), sameFocusOnly, predicate)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Determine whether an expression possible calls (directly or indirectly) xsl:result-document, or
     * has other context dependencies that prevent function inlining,
     * which we assume is true if it contains a template call.
     * A call on result-document disqualifies the function from being
     * inlined, because error XTDE1480 would then not be detected. We don't have to worry about functions
     * containing further function calls because they are disqualified from inlining anyway.
     *
     * @param exp the expression to be tested
     * @return true if there is any possibility that calling the function might result in a call on
     * xsl:result-document
     */

    public static boolean changesXsltContext(Expression exp) {
        exp = exp.getInterpretedExpression();
        if (exp instanceof ResultDocument || exp instanceof CallTemplate || exp instanceof ApplyTemplates ||
                exp instanceof NextMatch || exp instanceof ApplyImports || exp.isCallOn(RegexGroup.class)
                || exp.isCallOn(CurrentGroup.class)) {
            return true;
        }
        for (Operand o : exp.operands()) {
            if (changesXsltContext(o.getChildExpression())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Return true if a given expression is evaluated repeatedly
     * when a given ancestor expression is evaluated once
     *
     * @param child    the expression to be tested
     * @param ancestor the ancestor expression. May be null, in which case the search goes all the way
     *                 to the base of the stack.
     * @return true if the current expression is evaluated repeatedly
     */

    public static boolean isLoopingSubexpression(Expression child, Expression ancestor) {
        while (true) {

            Expression parent = child.getParentExpression();
            if (parent == null) {
                return false;
            }
            if (hasLoopingSubexpression(parent, child)) {
                return true;
            }
            if (parent == ancestor) {
                return false;
            }
            child = parent;
        }
    }

    public static boolean isLoopingReference(VariableReference reference, Binding binding) {
        Expression child = reference;
        Expression parent = child.getParentExpression();
        while (true) {
            if (parent == null) {
                // haven't found the binding on the stack, so the safe thing is to assume we're in a loop
                return true;
            }
            if (parent instanceof FLWORExpression) {
                if (parent.hasVariableBinding(binding)) {
                    // The variable is declared in one of the clauses of the FLWOR expression
                    return ((FLWORExpression) parent).hasLoopingVariableReference(binding);
                } else {
                    // The variable is declared outside the FLWOR expression
                    if (hasLoopingSubexpression(parent, child)) {
                        return true;
                    }
                }
            } else if (parent.getExpressionName().equals("tryCatch")) {
                return true; // not actually a loop, but it's a simple way to prevent inlining of variables (test QT3 try-007)
            } else {
                if (parent instanceof ForEachGroup && parent.hasVariableBinding(binding)) {
                    return false;
                }
                if (hasLoopingSubexpression(parent, child)) {
                    return true;
                }
                if (parent.hasVariableBinding(binding)) {
                    return false;
                }
            }
            child = parent;
            parent = child.getParentExpression();
        }
    }

    public static boolean hasLoopingSubexpression(Expression parent, Expression child) {
        for (Operand info : parent.operands()) {
            if (info.getChildExpression() == child) {
                return info.isEvaluatedRepeatedly();
            }
        }
        return false;
    }

    /**
     * Get the focus-setting container of an expression
     * 

* * *

Note, this always returns an expression or null. Unlike the like-named concept in the * spec, it can't return a component such as a template or an attribute set.

* * @param exp the expression whose focus-setting container is required * @return the focus-setting container, or null if the focus for the expression is the same as the * focus for the containing component as a whole */ public static Expression getFocusSettingContainer(Expression exp) { Expression child = exp; Expression parent = child.getParentExpression(); while (parent != null) { Operand o = findOperand(parent, child); if (o == null) { throw new AssertionError(); } if (!o.hasSameFocus()) { return parent; } child = parent; parent = child.getParentExpression(); } return null; } /** * Get the context-document-setting container of an expression. This is the same as the * focus-setting container, except when the context-setting operand is a single-document node-set, * in which case we can go up one more level. For example, given /a/b[/d/e = 3], the predicate * /d/e = 3 can be loop-lifted from the filter expression, because it is known that the base * expression /a/b is a single-document node-set. * * @param exp the expression whose document-context-setting container is required * @return the context-document-setting container, or null if the focus for the expression is the same as the * focus for the containing component as a whole */ public static Expression getContextDocumentSettingContainer(Expression exp) { Expression child = exp; Expression parent = child.getParentExpression(); while (parent != null) { if (parent instanceof ContextSwitchingExpression) { ContextSwitchingExpression switcher = (ContextSwitchingExpression) parent; if (child == switcher.getActionExpression()) { if (switcher.getSelectExpression().hasSpecialProperty(StaticProperty.CONTEXT_DOCUMENT_NODESET)) { parent.resetLocalStaticProperties(); parent.getSpecialProperties(); return getContextDocumentSettingContainer(parent); } } } Operand o = findOperand(parent, child); if (o == null) { throw new AssertionError(); } if (!o.hasSameFocus()) { return parent; } child = parent; parent = child.getParentExpression(); } return null; } /** * Reset the static properties for the current expression and for all its containing expressions. * This should be done whenever the expression is changed in a way that might * affect the properties. It causes the properties to be recomputed next time they are needed. * * @param exp the expression whose properties are to be reset; the method also resets local * properties for all its ancestor expressions. */ public static void resetStaticProperties(Expression exp) { int i = 0; while (exp != null) { exp.resetLocalStaticProperties(); exp = exp.getParentExpression(); if (i++ > 100000) { throw new IllegalStateException("Loop in parent expression chain"); } } } /** * Return true if two objects are equal or if both are null * * @param x the first object * @param y the second object * @return true if both x and y are null or if x.equals(y) */ public static boolean equalOrNull(Object x, Object y) { if (x == null) { return y == null; } else { return x.equals(y); } } /** * Helper method to construct an iterator over the results of the expression when all that * the expression itself offers is a process() method. This builds the entire results of the * expression as a sequence in memory and then iterates over it. * * @param exp the expression * @param context the dynamic evaluation context * @return an iterator over the results of the expression * @throws XPathException if a dynamic error occurs */ public static SequenceIterator getIteratorFromProcessMethod( Expression exp, XPathContext context) throws XPathException { Controller controller = context.getController(); assert controller != null; SequenceCollector seq = controller.allocateSequenceOutputter(); exp.process(new ComplexContentOutputter(seq), context); seq.close(); return seq.iterate(); } /** * Helper method to construct an item representing the results of the expression when all that * the expression itself offers is a process() method. * * @param exp the expression * @param context the dynamic evaluation context * @return an iterator over the results of the expression * @throws XPathException if a dynamic error occurs */ public static Item getItemFromProcessMethod(Expression exp, XPathContext context) throws XPathException { Controller controller = context.getController(); if (controller == null) { throw new NoDynamicContextException("No controller available"); } SequenceCollector seq = controller.allocateSequenceOutputter(1); exp.process(new ComplexContentOutputter(seq), context); seq.close(); Item result = seq.getFirstItem(); seq.reset(); return result; } /** * Allocate slot numbers to range variables * * @param exp the expression whose range variables need to have slot numbers assigned * @param nextFree the next slot number that is available for allocation * @param frame a SlotManager object that is used to track the mapping of slot numbers * to variable names for debugging purposes. May be null. * @return the next unallocated slot number. */ public static int allocateSlots(Expression exp, int nextFree, SlotManager frame) { if (exp instanceof Assignation) { ((Assignation) exp).setSlotNumber(nextFree); int count = ((Assignation) exp).getRequiredSlots(); nextFree += count; if (frame != null) { frame.allocateSlotNumber(((Assignation) exp).getVariableQName()); } } if (exp instanceof LocalParam && ((LocalParam) exp).getSlotNumber() < 0) { ((LocalParam) exp).setSlotNumber(nextFree++); } if (exp instanceof FLWORExpression) { for (Clause c : ((FLWORExpression) exp).getClauseList()) { for (LocalVariableBinding b : c.getRangeVariables()) { b.setSlotNumber(nextFree++); frame.allocateSlotNumber(b.getVariableQName()); } } } if (exp instanceof VariableReference) { VariableReference var = (VariableReference) exp; Binding binding = var.getBinding(); if (exp instanceof LocalVariableReference) { ((LocalVariableReference) var).setSlotNumber(((LocalBinding) binding).getLocalSlotNumber()); } if (binding instanceof Assignation && ((LocalBinding) binding).getLocalSlotNumber() < 0) { // This indicates something badly wrong: we've found a variable reference on the tree, that's // bound to a variable declaration that is no longer on the tree. All we can do is print diagnostics. // The most common reason for this failure is that the declaration of the variable was removed // from the tree in the mistaken belief that there were no references to the variable. Variable // references are counted during the typeCheck phase, so this can happen if typeCheck() fails to // visit some branch of the expression tree. Assignation decl = (Assignation) binding; Logger err; try { err = exp.getConfiguration().getLogger(); } catch (Exception ex) { err = new StandardLogger(); } String msg = "*** Internal Saxon error: local variable encountered whose binding has been deleted"; err.error(msg); err.error("Variable name: " + decl.getVariableName()); err.error("Line number of reference: " + var.getLocation().getLineNumber() + " in " + var.getLocation().getSystemId()); err.error("Line number of declaration: " + decl.getLocation().getLineNumber() + " in " + decl.getLocation().getSystemId()); err.error("DECLARATION:"); try { decl.explain(err); } catch (Exception e) { // ignore the secondary error } throw new IllegalStateException(msg); } } if (exp instanceof Pattern) { nextFree = ((Pattern) exp).allocateSlots(frame, nextFree); } else if (exp instanceof ScopedBindingElement) { nextFree = ((ScopedBindingElement) exp).allocateSlots(frame, nextFree); } else { for (Operand o : exp.operands()) { nextFree = allocateSlots(o.getChildExpression(), nextFree, frame); } } return nextFree; // Note, we allocate a distinct slot to each range variable, even if the // scopes don't overlap. This isn't strictly necessary, but might help // debugging. } /** * Determine the effective boolean value of a sequence, given an iterator over the sequence * * @param iterator An iterator over the sequence whose effective boolean value is required * @return the effective boolean value * @throws XPathException if a dynamic error occurs */ public static boolean effectiveBooleanValue(SequenceIterator iterator) throws XPathException { Item first = iterator.next(); if (first == null) { return false; } Genre genre = first.getGenre(); // Variable introduced for C# type checking switch (genre) { case NODE: iterator.close(); return true; case ATOMIC: { if (first instanceof BooleanValue) { if (iterator.next() != null) { iterator.close(); ebvError("a sequence of two or more items starting with a boolean"); } iterator.close(); return ((BooleanValue) first).getBooleanValue(); } else if (first instanceof StringValue) { // includes anyURI value if (iterator.next() != null) { iterator.close(); ebvError("a sequence of two or more items starting with a string ('" + first.getStringValue() + "')"); } return !((StringValue) first).isEmpty(); } else if (first instanceof NumericValue) { if (iterator.next() != null) { iterator.close(); ebvError("a sequence of two or more items starting with a numeric value (" + first.getStringValue() + ")"); } final NumericValue n = (NumericValue) first; return (n.compareTo(0) != 0) && !n.isNaN(); } else { iterator.close(); ebvError("a sequence starting with an atomic value of type " + ((AtomicValue) first).getItemType().getTypeName().getDisplayName()); return false; } } case ARRAY: iterator.close(); ebvError("a sequence starting with an array item (" + first.toShortString() + ")"); return false; case MAP: iterator.close(); ebvError("a sequence starting with a map (" + first.toShortString() + ")"); return false; case FUNCTION: { iterator.close(); ebvError("a sequence starting with a function (" + first.toShortString() + ")"); return false; } case EXTERNAL: { if (iterator.next() != null) { iterator.close(); ebvError("a sequence of two or more items starting with an external object value"); } return true; } } ebvError("a sequence starting with an item of unknown kind"); return false; } /** * Determine the effective boolean value of a single item * * @param item the item whose effective boolean value is required * @return the effective boolean value * @throws XPathException if a dynamic error occurs */ public static boolean effectiveBooleanValue(Item item) throws XPathException { if (item == null) { return false; } if (item instanceof NodeInfo) { return true; } else if (item instanceof AtomicValue) { if (item instanceof BooleanValue) { return ((BooleanValue) item).getBooleanValue(); } else if (item instanceof StringValue) { // includes anyURI value return !((StringValue) item).isEmpty(); } else if (item instanceof NumericValue) { final NumericValue n = (NumericValue) item; return (n.compareTo(0) != 0) && !n.isNaN(); } else if (item.getGenre() == Genre.EXTERNAL) { return true; } else { ebvError("an atomic value of type " + ((AtomicValue) item).getPrimitiveType().getDisplayName()); return false; } } else { ebvError(item.getGenre().toString()); return false; } } /** * Report an error in computing the effective boolean value of an expression * * @param reason the nature of the error * @throws XPathException always */ public static void ebvError(String reason) throws XPathException { XPathException err = new XPathException("Effective boolean value is not defined for " + reason); err.setErrorCode("FORG0006"); err.setIsTypeError(true); throw err; } /** * Report an error in computing the effective boolean value of an expression * * @param reason the nature of the error * @param cause the failing expression * @throws XPathException always */ public static void ebvError(String reason, Expression cause) throws XPathException { XPathException err = new XPathException("Effective boolean value is not defined for " + reason); err.setErrorCode("FORG0006"); err.setIsTypeError(true); err.setFailingExpression(cause); throw err; } /** * Ask whether an expression has a dependency on the focus * * @param exp the expression * @return true if the value of the expression depends on the context item, position, or size */ public static boolean dependsOnFocus(Expression exp) { return (exp.getDependencies() & StaticProperty.DEPENDS_ON_FOCUS) != 0; } /** * Determine whether an expression depends on any one of a set of variables * * @param exp the expression being tested * @param bindingList the set of variables being tested * @return true if the expression depends on one of the given variables */ public static boolean dependsOnVariable(Expression exp, final Binding[] bindingList) { return !(bindingList == null || bindingList.length == 0) && contains(exp, false, e -> { if (e instanceof VariableReference) { for (Binding binding : bindingList) { if (((VariableReference) e).getBinding() == binding) { return true; } } } return false; }); } /** * Gather a list of all the variable bindings on which a given expression depends. * In the light of spec bug 29590, this only considers variable references that * are unconditionally evaluated: that is, it ignores variable references appearing * in the then/else branches of a conditional or equivalent. Cycles involving * conditional evaluation are detected dynamically. * * @param e the expression being tested * @param list a list to which the bindings are to be added. The items in this list must * implement {@link Binding} */ public static void gatherReferencedVariables(Expression e, List list) { if (e instanceof VariableReference) { Binding binding = ((VariableReference) e).getBinding(); if (!list.contains(binding)) { list.add(binding); } } else { for (Operand o : e.operands()) { if (!o.getOperandRole().isInChoiceGroup()) { gatherReferencedVariables(o.getChildExpression(), list); } } } } /** * Determine whether the expression contains any variable references or calls to user-written functions * * @param exp the expression being tested * @return true if the expression includes a variable reference or function call, or an XSLT construct * equivalent to a function call (e.g call-template). Also returns true if the expression includes * a variable binding element, as (a) this is likely to mean it also contains a reference, and (b) * it also needs to be caught on the same paths. */ public static boolean refersToVariableOrFunction(Expression exp) { return contains(exp, false, e -> e instanceof VariableReference || e instanceof UserFunctionCall || e instanceof Binding || e instanceof CallTemplate || e instanceof ApplyTemplates || e instanceof ApplyImports || isCallOnSystemFunction(e, "function-lookup") || e.isCallOn(ApplyFn.class)); } public static boolean isCallOnSystemFunction(Expression e, String localName) { return e instanceof StaticFunctionCall && localName.equals(((StaticFunctionCall) e).getFunctionName().getLocalPart()); } /** * Determine whether an expression contains a call on the function with a given name * * @param exp The expression being tested * @param qName The name of the function * @param sameFocusOnly If true, only expressions with the same (top-level) focus are searched * @return true if the expression contains a call on the function */ public static boolean callsFunction(Expression exp, final StructuredQName qName, boolean sameFocusOnly) { return contains(exp, sameFocusOnly, e -> e instanceof FunctionCall && qName.equals(((FunctionCall) e).getFunctionName()) ); } /** * Determine whether an expression has a subexpression of a given implementation class * * @param exp The expression being tested * @param subClass The implementation class of the sought subexpression * @return true if the expression contains a subexpression that is an instance of the specified class */ public static boolean containsSubexpression(Expression exp, final Class subClass) { return contains(exp, false, e -> subClass.isAssignableFrom(e.getClass())); } /** * Gather a list of all the user-defined functions which a given expression calls directly * * @param e the expression being tested * @param list a list of the functions that are called. The items in this list must * be objects of class {@link UserFunction} */ public static void gatherCalledFunctions(Expression e, List list) { if (e instanceof UserFunctionCall) { UserFunction function = ((UserFunctionCall) e).getFunction(); if (!list.contains(function)) { list.add(function); } } else { for (Operand o : e.operands()) { gatherCalledFunctions(o.getChildExpression(), list); } } } /** * Gather a list of the names of the user-defined functions which a given expression calls directly * * @param e the expression being tested * @param list a list of the functions that are called. The items in this list are strings in the format * "{uri}local/arity" */ public static void gatherCalledFunctionNames(Expression e, List list) { if (e instanceof UserFunctionCall) { list.add(((UserFunctionCall) e).getSymbolicName()); } else { for (Operand o : e.operands()) { gatherCalledFunctionNames(o.getChildExpression(), list); } } } /** * Optimize the implementation of a component such as a function, template, or global variable * * @param body the expression forming the body of the component * @param compilation the current compilation. May be null. * @param visitor the expression visitor * @param cisi information about the context item for evaluation of the component body * @param extractGlobals true if constant expressions are to be extracted as global variables * @return the optimized expression body * @throws XPathException if anything goes wrong */ public static Expression optimizeComponentBody( Expression body, final Compilation compilation, ExpressionVisitor visitor, ContextItemStaticInfo cisi, boolean extractGlobals) throws XPathException { final Configuration config = visitor.getConfiguration(); Optimizer opt = visitor.obtainOptimizer(); StaticContext env = visitor.getStaticContext(); // boolean compileWithTracing = config.isCompileWithTracing(); // if (!compileWithTracing) { // // Bug 3472 - desperate attempts to discover whether tracing was enabled for this particular compilation // if (compilation != null) { // compileWithTracing = compilation.getCompilerInfo().isCompileWithTracing(); // } else if (env instanceof QueryModule) { // compileWithTracing = ((QueryModule) env).getUserQueryContext().isCompileWithTracing(); // } else if (env instanceof ExpressionContext) { // compileWithTracing = ((ExpressionContext) env).getStyleElement().getCompilation().getCompilerInfo().isCompileWithTracing(); // } // } if (opt.isOptionSet(OptimizerOptions.MISCELLANEOUS)) { ExpressionTool.resetPropertiesWithinSubtree(body); if (opt.isOptionSet(OptimizerOptions.MISCELLANEOUS)) { body = body.optimize(visitor, cisi); } body.setParentExpression(null); if (extractGlobals && compilation != null) { Expression exp2 = opt.promoteExpressionsToGlobal(body, compilation.getPrincipalStylesheetModule(), visitor); if (exp2 != null) { // Try another optimization pass: extracting global variables can identify things that are indexable ExpressionTool.resetPropertiesWithinSubtree(exp2); body = exp2.optimize(visitor, cisi); } } if (opt.isOptionSet(OptimizerOptions.LOOP_LIFTING)) { body = LoopLifter.process(body, visitor, cisi); } } else { body = avoidDocumentSort(body); } if (!visitor.isOptimizeForStreaming()) { body = opt.eliminateCommonSubexpressions(body); } opt.prepareForStreaming(body); computeEvaluationModesForUserFunctionCalls(body); body.restoreParentPointers(); return body; } /** * An optimization-lite pass over the expression tree that attempts to eliminate unnecessary * sorting into document order. We avoid a full optimization when STRICT_STREAMABILITY analysis * is requested, because it rewrites non-streamable expressions into streamable expressions; * but to get the streamability right, we need as a minimum to eliminate unwanted document sort * operations. */ private static Expression avoidDocumentSort(Expression exp) { if (exp instanceof DocumentSorter) { Expression base = ((DocumentSorter) exp).getBaseExpression(); if (base.hasSpecialProperty(StaticProperty.ORDERED_NODESET)) { return base; } return exp; } else if (exp instanceof ConditionalSorter) { DocumentSorter sorter = ((ConditionalSorter) exp).getDocumentSorter(); Expression eliminatedSorter = avoidDocumentSort(sorter); if (eliminatedSorter != sorter) { return eliminatedSorter; } } for (Operand o : exp.operands()) { o.setChildExpression(avoidDocumentSort(o.getChildExpression())); } return exp; } /** * Compute argument evaluation modes for all calls on user defined functions with * a specified expression * * @param exp the expression * @throws XPathException if any error occurs */ public static void computeEvaluationModesForUserFunctionCalls(Expression exp) throws XPathException { ExpressionTool.processExpressionTree(exp, null, (expression, result) -> { if (expression instanceof UserFunctionCall) { ((UserFunctionCall) expression).allocateArgumentEvaluators(); } if (expression instanceof LocalParam) { ((LocalParam) expression).computeEvaluationMode(); } return false; }); } /** * Clear all computed streamability properties for an expression and its contained subtree * * @param exp the expression whose streamability data is to be reset * @throws XPathException should not happen */ public static void clearStreamabilityData(Expression exp) throws XPathException { ExpressionTool.processExpressionTree(exp, null, (expression, result) -> { expression.setExtraProperty("P+S", null); expression.setExtraProperty("inversion", null); return false; }); } /** * Reset cached static properties within a subtree, meaning that they have to be * recalulated next time they are required * * @param exp the root of the subtree within which static properties should be reset */ public static void resetPropertiesWithinSubtree(Expression exp) { exp.resetLocalStaticProperties(); if (exp instanceof LocalVariableReference) { LocalVariableReference ref = (LocalVariableReference) exp; Binding binding = ref.getBinding(); if (binding instanceof Assignation) { binding.addReference(ref, ref.isInLoop()); } } for (Operand o : exp.operands()) { resetPropertiesWithinSubtree(o.getChildExpression()); o.getChildExpression().setParentExpression(exp); } } /** * Resolve calls to the XSLT current() function within an expression * * @param exp the expression within which calls to current() should be resolved * @return the expression after resolving calls to current() */ public static Expression resolveCallsToCurrentFunction(Expression exp) { if (exp.isCallOn(Current.class)) { ContextItemExpression cie = new ContextItemExpression(); copyLocationInfo(exp, cie); return cie; } else { if (callsFunction(exp, Current.FN_CURRENT, true)) { // replace trivial (same-focus) calls to current by a simple "." replaceTrivialCallsToCurrent(exp); } if (callsFunction(exp, Current.FN_CURRENT, false)) { // replace non-trivial (different-focus) calls to current by a variable reference LetExpression let = new LetExpression(); let.setVariableQName( new StructuredQName("vv", NamespaceConstant.SAXON_GENERATED_VARIABLE, "current" + exp.hashCode())); let.setRequiredType(SequenceType.SINGLE_ITEM); let.setSequence(new CurrentItemExpression()); replaceCallsToCurrent(exp, let); let.setAction(exp); return let; } else { return exp; } } } /** * Get a list of all references to a particular variable within a subtree * * @param exp the expression at the root of the subtree * @param binding the variable binding whose references are sought * @param list a list to be populated with the references to this variable */ public static void gatherVariableReferences(Expression exp, Binding binding, List list) { if (exp instanceof VariableReference && ((VariableReference) exp).getBinding() == binding) { list.add((VariableReference) exp); } else { for (Operand o : exp.operands()) { gatherVariableReferences(o.getChildExpression(), binding, list); } } } /** * Process every node on a subtree of the expression tree using a supplied action. * * @param root the root of the subtree to be processed * @param result an arbitrary object that is passed to each action call and that can be * updated to gather results of the processing * @param action an action to be performed on each node of the expression tree. Processing * stops if any action returns the value true * @return true if any call on the action operand returned true * @throws XPathException if the callback throws an error */ public static boolean processExpressionTree(Expression root, Object result, ExpressionAction action) throws XPathException { boolean done = action.process(root, result); if (!done) { for (Operand o : root.operands()) { done = processExpressionTree(o.getChildExpression(), result, action); if (done) { return true; } } } return false; } /** * Replace all selected subexpressions within a subtree * * @param exp the expression at the root of the subtree * @param selector callback to determine whether a subexpression is selected * @param replacement the expression to be used in place of the variable reference * @param mustCopy if true, the replacement expression must be copied before use * @return true if a replacement has been performed (in which case the replacement expression * must be copied before being used again). */ public static boolean replaceSelectedSubexpressions( Expression exp, Predicate selector, Expression replacement, boolean mustCopy) { boolean replaced = false; for (Operand o : exp.operands()) { if (replaced) { mustCopy = true; } Expression child = o.getChildExpression(); if (selector.test(child)) { Expression e2 = mustCopy ? replacement.copy(new RebindingMap()) : replacement; o.setChildExpression(e2); replaced = true; } else { replaced |= replaceSelectedSubexpressions(child, selector, replacement, mustCopy); } } return replaced; } /** * Replace all references to a particular variable within a subtree * * @param exp the expression at the root of the subtree * @param binding the variable binding whose references are sought * @param replacement the expression to be used in place of the variable reference * @param mustCopy true if the replacement expression must be copied before use */ public static void replaceVariableReferences(Expression exp, final Binding binding, Expression replacement, boolean mustCopy) { Predicate selector = child -> child instanceof VariableReference && ((VariableReference) child).getBinding() == binding; boolean changed = replaceSelectedSubexpressions(exp, selector, replacement, mustCopy); if (changed) { resetPropertiesWithinSubtree(exp); } } /** * Determine how often a variable is referenced. This is the number of times * it is referenced at run-time: so a reference in a loop counts as "many". This code * currently handles local variables (Let expressions) and function parameters. It is * not currently used for XSLT template parameters. It's not the end of the world if * the answer is wrong (unless it's wrongly given as zero), but if wrongly returned as * 1 then the variable will be repeatedly evaluated. * * @param exp the expression within which variable references are to be counted * @param binding identifies the variable of interest * @param inLoop true if the expression is within a loop, in which case a reference counts as many. * This should be set to false on the initial call, it may be set to true on an internal recursive * call * @return the number of references. The interesting values are 0, 1, "many" (represented * by any value >1), and the special value FILTERED, which indicates that there are * multiple references and one or more of them is of the form {@code $x[....]} indicating that an * index might be useful. */ public static int getReferenceCount(Expression exp, Binding binding, boolean inLoop) { int rcount = 0; if (exp instanceof VariableReference && ((VariableReference) exp).getBinding() == binding) { if (((VariableReference) exp).isFiltered()) { return FilterExpression.FILTERED; } else { rcount += inLoop ? 10 : 1; } } else if ((exp.getDependencies() & StaticProperty.DEPENDS_ON_LOCAL_VARIABLES) == 0) { return 0; } else { for (Operand info : exp.operands()) { Expression child = info.getChildExpression(); boolean childLoop = inLoop || info.isEvaluatedRepeatedly(); rcount += getReferenceCount(child, binding, childLoop); if (rcount >= FilterExpression.FILTERED) { break; } } } return rcount; } /** * Get the size of an expression tree (the number of subexpressions it contains) * * @param exp the expression whose size is required * @return the size of the expression tree, as the number of nodes */ public static int expressionSize(Expression exp) { exp = exp.getInterpretedExpression(); int total = 1; for (Operand o : exp.operands()) { total += expressionSize(o.getChildExpression()); if (o.getChildExpression() instanceof UserFunctionReference) { // bug 5054 UserFunction uf = ((UserFunctionReference) o.getChildExpression()).getNominalTarget(); total += expressionSize(uf.getBody()); } } return total; } /** * Rebind all variable references to a binding * * @param exp the expression whose contained variable references are to be rebound * @param oldBinding the old binding for the variable references * @param newBinding the new binding to which the variables should be rebound */ public static void rebindVariableReferences( Expression exp, Binding oldBinding, Binding newBinding) { if (exp instanceof VariableReference) { if (((VariableReference) exp).getBinding() == oldBinding) { ((VariableReference) exp).fixup(newBinding); } } else { for (Operand o : exp.operands()) { rebindVariableReferences(o.getChildExpression(), oldBinding, newBinding); } } } /** * Make a mapping expression. The resulting expression will include logic to check that the first operand * returns nodes, and that the expression as a whole is homogeneous, unless the caller requests otherwise. * * @param start the start expression (the first operand of "/") * @param step the step expression (the second operand of "/") * @return the resulting expression. */ /*@NotNull*/ public static Expression makePathExpression(Expression start, Expression step) { // the expression /.. is sometimes used to represent the empty node-set. Applying this simplification // now avoids generating warnings for this case. if (start instanceof RootExpression && step instanceof AxisExpression && ((AxisExpression) step).getAxis() == AxisInfo.PARENT) { return Literal.makeEmptySequence(); } SlashExpression expr = new SlashExpression(start, step); // If start is a path expression such as a, and step is b/c, then // instead of a/(b/c) we construct (a/b)/c. This is because it often avoids // a sort. // The "/" operator in XPath 2.0 is not always left-associative. Problems // can occur if position() and last() are used on the rhs, or if node-constructors // appear, e.g. //b/../. So we only do this rewrite if the step is a path // expression in which both operands are axis expressions optionally with predicates if (step instanceof SlashExpression) { SlashExpression stepPath = (SlashExpression) step; if (isFilteredAxisPath(stepPath.getSelectExpression()) && isFilteredAxisPath(stepPath.getActionExpression())) { expr.setStart(ExpressionTool.makePathExpression(start, stepPath.getSelectExpression())); expr.setStep(stepPath.getActionExpression()); } } return expr; } /** * Find the operand corresponding to a particular child expression * * @param parentExpression the parent expression * @param childExpression the child expression * @return the relevant operand, or null if not found */ public static Operand findOperand(Expression parentExpression, Expression childExpression) { for (Operand o : parentExpression.operands()) { if (o.getChildExpression() == childExpression) { return o; } } return null; } /** * Determine whether an expression is an axis step with optional filter predicates. * * @param exp the expression to be examined * @return true if the supplied expression is an AxisExpression, or an AxisExpression wrapped by one * or more filter expressions */ private static boolean isFilteredAxisPath(Expression exp) { return unfilteredExpression(exp, true) instanceof AxisExpression; } /** * Get the expression that remains after removing any filter predicates * * @param exp the expression to be examined * @param allowPositional true if positional predicates are allowed * @return the expression underlying exp after removing any predicates */ public static Expression unfilteredExpression(Expression exp, boolean allowPositional) { if (exp instanceof FilterExpression && (allowPositional || !((FilterExpression) exp).isFilterIsPositional())) { return unfilteredExpression(((FilterExpression) exp).getSelectExpression(), allowPositional); } else if (exp instanceof SingleItemFilter && allowPositional) { return unfilteredExpression(((SingleItemFilter) exp).getBaseExpression(), allowPositional); } else { return exp; } } /** * Try to factor out dependencies on the context item, by rewriting an expression f(.) as * let $dot := . return f($dot). This is not always possible, for example where f() is an extension * function call that uses XPathContext as an implicit argument. However, doing this increases the * chances of distributing a "where" condition in a FLWOR expression to the individual input sequences * selected by the "for" clauses. * * @param exp the expression from which references to "." should be factored out if possible * @param contextItemType the static type of the context item * @return either the expression, after binding "." to a local variable and replacing all references to it; * or null, if no changes were made. */ public static Expression tryToFactorOutDot(Expression exp, ItemType contextItemType) { if (exp instanceof ContextItemExpression) { return null; } else if (exp instanceof LetExpression && ((LetExpression) exp).getSequence() instanceof ContextItemExpression) { Expression action = ((LetExpression) exp).getAction(); boolean changed = factorOutDot(action, (LetExpression) exp); if (changed) { exp.resetLocalStaticProperties(); } return exp; } else if ((exp.getDependencies() & (StaticProperty.DEPENDS_ON_CONTEXT_ITEM | StaticProperty.DEPENDS_ON_CONTEXT_DOCUMENT)) != 0) { LetExpression let = new LetExpression(); let.setVariableQName( new StructuredQName("saxon", NamespaceConstant.SAXON, "dot" + exp.hashCode())); let.setRequiredType(SequenceType.makeSequenceType(contextItemType, StaticProperty.EXACTLY_ONE)); let.setSequence(new ContextItemExpression()); let.setAction(exp); boolean changed = factorOutDot(exp, let); if (changed) { return let; } else { return exp; } } else { return null; } } /** * Replace references to the context item with references to a variable * * @param exp the expression in which the replacement is to take place * @param variable the declaration of the variable * @return true if replacement has taken place (at any level) */ public static boolean factorOutDot(Expression exp, Binding variable) { boolean changed = false; if ((exp.getDependencies() & (StaticProperty.DEPENDS_ON_CONTEXT_ITEM | StaticProperty.DEPENDS_ON_CONTEXT_DOCUMENT)) != 0) { for (Operand info : exp.operands()) { if (info.hasSameFocus()) { Expression child = info.getChildExpression(); if (child instanceof ContextItemExpression) { VariableReference ref = makeReference(variable); copyLocationInfo(child, ref); info.setChildExpression(ref); changed = true; } else if (child instanceof AxisExpression || child instanceof RootExpression) { VariableReference ref = makeReference(variable); copyLocationInfo(child, ref); Expression path = ExpressionTool.makePathExpression(ref, child); info.setChildExpression(path); changed = true; } else { changed |= factorOutDot(child, variable); } } } } if (changed) { exp.resetLocalStaticProperties(); } return changed; } private static VariableReference makeReference(Binding variable) { if (variable.isGlobal()) { return new GlobalVariableReference((GlobalVariable) variable); } else { return new LocalVariableReference((LocalBinding) variable); } } /** * Inline variable references. * * @param expr the expression containing the variable references to be inlined * @param binding the variable binding to be inlined * @param replacement the expression to be used as a replacement for the variable reference * @return true if any replacement was carried out within the subtree of this expression */ public static boolean inlineVariableReferences(Expression expr, Binding binding, Expression replacement) { return inlineVariableReferencesInternal(expr, binding, replacement); } public static boolean inlineVariableReferencesInternal(Expression expr, Binding binding, Expression replacement) { if (expr instanceof TryCatch && !(replacement instanceof Literal)) { // Don't inline variable references within a try/catch, as this will lead to errors in the // variable's initializer being incorrectly caught by the catch clause. See XSLT3 test try-029. return false; } else { boolean found = false; for (Operand o : expr.operands()) { Expression child = o.getChildExpression(); if (child instanceof VariableReference && ((VariableReference) child).getBinding() == binding) { Expression copy; try { copy = replacement.copy(new RebindingMap()); ExpressionTool.copyLocationInfo(child, copy); } catch (UnsupportedOperationException err) { // If we can't make a copy, return the original. This is safer than it seems, // because on the paths where this happens, we are merely moving the expression from // one place to another, not replicating it copy = replacement; } o.setChildExpression(copy); found = true; } else { found |= inlineVariableReferencesInternal(child, binding, replacement); } } if (found) { expr.resetLocalStaticProperties(); } return found; } } /** * Replace trivial calls to current() by a context item expression ({@code .}). * * @param expr the expression potentially containing the calls to be replaced * @return true if any replacement was carried out within the subtree of this expression */ public static boolean replaceTrivialCallsToCurrent(Expression expr) { boolean found = false; for (Operand o : expr.operands()) { if (o.hasSameFocus()) { Expression child = o.getChildExpression(); if (child.isCallOn(Current.class)) { CurrentItemExpression var = new CurrentItemExpression(); ExpressionTool.copyLocationInfo(child, var); o.setChildExpression(var); found = true; } else { found = replaceTrivialCallsToCurrent(child); } } } if (found) { expr.resetLocalStaticProperties(); } return found; } /** * Replace calls to current() by a variable reference. * * @param expr the expression potentially containing the calls to be replaced * @param binding the variable binding to be referenced * @return true if any replacement was carried out within the subtree of this expression */ public static boolean replaceCallsToCurrent(Expression expr, LocalBinding binding) { boolean found = false; for (Operand o : expr.operands()) { Expression child = o.getChildExpression(); if (child.isCallOn(Current.class)) { LocalVariableReference var = new LocalVariableReference(binding); ExpressionTool.copyLocationInfo(child, var); o.setChildExpression(var); binding.addReference(var, true); found = true; } else { found = replaceCallsToCurrent(child, binding); } } if (found) { expr.resetLocalStaticProperties(); } return found; } /** * Determine whether the expression is either an updating expression, or an expression that is permitted * in a context where updating expressions are allowed * * @param exp the expression under test * @return true if the expression is neither an updating expression, nor an empty sequence, * nor a call on error() */ public static boolean isNotAllowedInUpdatingContext(Expression exp) { return !exp.isUpdatingExpression() && !exp.isVacuousExpression(); } public static String getCurrentDirectory() { String dir; try { dir = System.getProperty("user.dir"); } catch (Exception geterr) { // this doesn't work when running an applet return null; } if (!dir.endsWith("/")) { dir = dir + '/'; } URI currentDirectoryURL = new File(dir).toURI(); return currentDirectoryURL.toString(); } /** * Determine the base URI of an expression, so that it can be saved on the expression tree for use * when the expression is evaluated * * @param env the static context * @param locator location of the expression for error messages * @param fail if true, the method throws an exception when there is no absolute base URI; otherwise, the * method returns null * @return the absolute base URI of the expression * @throws net.sf.saxon.trans.XPathException if an error occurs */ public static URI getBaseURI(StaticContext env, SourceLocator locator, boolean fail) throws XPathException { URI expressionBaseURI = null; String base = null; try { base = env.getStaticBaseURI(); if (base == null) { base = getCurrentDirectory(); } if (base != null && !base.isEmpty()) { expressionBaseURI = new URI(base); } } catch (URISyntaxException e) { // perhaps escaping special characters will fix the problem String esc = IriToUri.iriToUri(StringView.tidy(base)).toString(); try { expressionBaseURI = new URI(esc); } catch (URISyntaxException e2) { // don't fail unless the base URI is actually needed (it usually isn't) expressionBaseURI = null; } if (expressionBaseURI == null && fail) { XPathException err = new XPathException("The base URI " + Err.wrap(env.getStaticBaseURI(), Err.URI) + " is not a valid URI"); err.setLocator(locator); throw err; } } return expressionBaseURI; } /** * Display an expression adding parentheses if it is possible they are necessary * because the expression has sub-expressions * * @param exp the expression to be displayed * @return a representation of the expression in parentheses */ @CSharpSuppressWarnings("UnsafeIteratorConversion") public static String parenthesize(Expression exp) { if (exp.operands().iterator().hasNext()) { return "(" + exp.toString() + ")"; } else { return exp.toString(); } } public static String parenthesizeShort(Expression exp) { if (hasTwoOrMoreOperands(exp)) { return "(" + exp.toShortString() + ")"; } else { return exp.toShortString(); } } @CSharpSuppressWarnings("UnsafeIteratorConversion") private static boolean hasTwoOrMoreOperands(Expression exp) { Iterator ops = exp.operands().iterator(); if (!ops.hasNext()) { return false; } Operand o = ops.next(); // dummy variable needed for C# conversion return ops.hasNext(); } public static void validateTree(Expression exp) { try { for (Operand o : exp.checkedOperands()) { validateTree(o.getChildExpression()); } } catch (IllegalStateException e) { e.printStackTrace(); } } /** * Ask whether a supplied expression is a nested node constructor. * That is, return true if the child expression creates nodes that will only be * used as children of some parent node (meaning that they never need to be copied). * * @param child child expression to be tested * @return true if the node constructed by the child expression does not need to be copied. */ public static boolean isLocalConstructor(Expression child) { if (!(child instanceof ParentNodeConstructor || child instanceof SimpleNodeConstructor)) { return false; } Expression parent = child.getParentExpression(); while (parent != null) { if (parent instanceof ParentNodeConstructor) { return true; } Operand o = findOperand(parent, child); if (o.getUsage() != OperandUsage.TRANSMISSION) { return false; } child = parent; parent = parent.getParentExpression(); } return false; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy