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

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

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2023 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.expr.*;
import net.sf.saxon.expr.elab.*;
import net.sf.saxon.expr.parser.*;
import net.sf.saxon.functions.BooleanFn;
import net.sf.saxon.functions.SystemFunction;
import net.sf.saxon.om.*;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trans.SaxonErrorCode;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.trans.XmlProcessingException;
import net.sf.saxon.tree.iter.EmptyIterator;
import net.sf.saxon.tree.jiter.ConcatenatingIterable;
import net.sf.saxon.type.*;
import net.sf.saxon.value.BooleanValue;
import net.sf.saxon.value.Cardinality;
import net.sf.saxon.value.EmptySequence;
import net.sf.saxon.value.SequenceType;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;


/**
 * Compiled representation of an xsl:choose or xsl:if element in the stylesheet.
 * Also used for typeswitch in XQuery.
 */

public class Choose extends Instruction implements ConditionalInstruction {

    private final Operand[] conditionOps;
    private final Operand[] actionOps;
    private boolean _isInstruction;


    // The class implements both xsl:choose and xsl:if. There is a list of boolean
    // expressions (conditions) and a list of corresponding actions: the conditions
    // are evaluated in turn, and when one is found that is true, the corresponding
    // action is evaluated. For xsl:if, there is always one condition and one action.
    // An xsl:otherwise is compiled as if it were xsl:when test="true()". If no
    // condition is satisfied, the instruction returns an empty sequence.



    public final static OperandRole CHOICE_ACTION =
            new OperandRole(OperandRole.IN_CHOICE_GROUP, OperandUsage.TRANSMISSION, SequenceType.ANY_SEQUENCE);


    /**
     * Construct an xsl:choose instruction
     *
     * @param conditions the conditions to be tested, in order
     * @param actions    the actions to be taken when the corresponding condition is true
     */

    public Choose(Expression[] conditions, Expression[] actions) {
        assert conditions.length == actions.length;
        conditionOps = new Operand[conditions.length];
        for (int i=0; i conditions() {
        return Arrays.asList(conditionOps);
    }

    /**
     * Get i'th action operand (counting from zero)
     * @param i the action number (counting from zero)
     * @return the i'th action to be evaluated when the corresponding condition is true
     */

    public Operand getActionOperand(int i) {
        return actionOps[i];
    }

    /**
     * Get i'th action to be performed or evaluated (counting from zero)
     *
     * @param i the action number (counting from zero)
     * @return the i'th action to be evaluated when the corresponding condition is true
     */

    public Expression getAction(int i) {
        return actionOps[i].getChildExpression();
    }

    /**
     * Set the i'th action counting from zero
     *
     * @param i         the index of the required action
     * @param action the new value of the i'th action counting from zero
     */

    public void setAction(int i, Expression action) {
        actionOps[i].setChildExpression(action);
    }

    /**
     * Get the sequence of actions
     * @return all the actions, in order
     */

    public Iterable actions() {
        return Arrays.asList(actionOps);
    }

    /**
     * Get all the operands of the Choose expresssion - that is, all the conditions and all the actions
     * @return all the operands, in undefined order.
     */

    @Override
    public Iterable operands() {
        return new ConcatenatingIterable<>(Arrays.asList(conditionOps), Arrays.asList(actionOps));
    }

    /**
     * Ask whether common subexpressions found in the operands of this expression can
     * be extracted and evaluated outside the expression itself. The result is irrelevant
     * in the case of operands evaluated with a different focus, which will never be
     * extracted in this way, even if they have no focus dependency.
     *
     * @return false for this kind of expression
     */
    @Override
    public boolean allowExtractingCommonSubexpressions() {
        return false;
    }

    /**
     * Atomize all the action expressions
     */

    public void atomizeActions() {
        for (int i=0; i conditions = new ArrayList<>(count);
            List actions = new ArrayList<>(count);
            for (int i = 0; i < count; i++) {
                Expression condition = getCondition(i);
                if (!Literal.hasEffectiveBooleanValue(condition, false)) {
                    conditions.add(condition);
                    actions.add(getAction(i));
                }
                if (Literal.hasEffectiveBooleanValue(condition, true)) {
                    break;
                }
            }
            if (conditions.isEmpty()) {
                Literal lit = Literal.makeEmptySequence();
                ExpressionTool.copyLocationInfo(this, lit);
                return lit;
            } else if (conditions.size() == 1 && Literal.hasEffectiveBooleanValue(conditions.get(0), true)) {
                return actions.get(0);
            } else if (conditions.size() != count) {
                Expression[] c = conditions.toArray(new Expression[0]);
                Expression[] a = actions.toArray(new Expression[0]);
                Choose result = new Choose(c, a);
                result.setRetainedStaticContext(getRetainedStaticContext());
                return result;
            }
        }

        // See if only condition left is: if (true) then x else ()

        if (size() == 1 && Literal.hasEffectiveBooleanValue(getCondition(0), true)) {
            return getAction(0);
        }

        // Eliminate a redundant  or "when (test) then ()"

        if (Literal.isEmptySequence(getAction(size() - 1))) {
            if (size() == 1) {
                Literal lit = Literal.makeEmptySequence();
                ExpressionTool.copyLocationInfo(this, lit);
                return lit;
            } else {
                Expression[] conditions = new Expression[count-1];
                Expression[] actions = new Expression[count-1];
                for (int i = 0; i < count-1; i++) {
                    conditions[i] = getCondition(i);
                    actions[i] = getAction(i);
                }
                return new Choose(conditions, actions);
            }
        }

        // Flatten an "else if"

        if (Literal.hasEffectiveBooleanValue(getCondition(count - 1), true) &&
                getAction(count - 1) instanceof Choose) {
            Choose choose2 = (Choose) getAction(count - 1);
            int newLen = count + choose2.size() - 1;
            Expression[] c2 = new Expression[newLen];
            Expression[] a2 = new Expression[newLen];
            for (int i=0; i roleSupplier, ExpressionVisitor visitor)
            throws XPathException {
        int count = size();
        TypeChecker tc = getConfiguration().getTypeChecker(backwardsCompatible);
        for (int i = 0; i < count; i++) {
            try {
                setAction(i, tc.staticTypeCheck(getAction(i), req, roleSupplier, visitor));
            } catch (XPathException err) {
                if (err.isStaticError()) {
                    throw err;
                }
                ErrorExpression ee = new ErrorExpression(new XmlProcessingException(err));
                ExpressionTool.copyLocationInfo(getAction(i), ee);
                setAction(i, ee);
            }
        }
        // If the last condition isn't true(), then we need to consider the fall-through case, which returns
        // an empty sequence
        if (!Literal.hasEffectiveBooleanValue(getCondition(count - 1), true) &&
                !Cardinality.allowsZero(req.getCardinality())) {
            Expression[] c = new Expression[count + 1];
            Expression[] a = new Expression[count + 1];
            for (int i=0; iThe 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 PathMap nodes to which the paths from this expression should be appended * @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) { // expressions used in a condition contribute paths, but these do not contribute to the result for (Operand condition : conditions()) { condition.getChildExpression().addToPathMap(pathMap, pathMapNodeSet); } PathMap.PathMapNodeSet result = new PathMap.PathMapNodeSet(); for (Operand action : actions()) { PathMap.PathMapNodeSet temp = action.getChildExpression().addToPathMap(pathMap, pathMapNodeSet); result.addNodeSet(temp); } return result; } /** * The toString() method for an expression attempts to give a representation of the expression * in an XPath-like form, but there is no guarantee that the syntax will actually be true XPath. * In the case of XSLT instructions, the toString() method gives an abstracted view of the syntax * * @return a representation of the expression as a string */ public String toString() { StringBuilder sb = new StringBuilder(64); sb.append("if ("); for (int i = 0; i < size(); i++) { sb.append(getCondition(i).toString()); sb.append(") then ("); sb.append(getAction(i).toString()); if (i == size() - 1) { sb.append(")"); } else { sb.append(") else if ("); } } return sb.toString(); } @Override public String toShortString() { return "if(" + getCondition(0).toShortString() + ") then ... else ..."; } /** * 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("choose", this); for (int i = 0; i < size(); i++) { getCondition(i).export(out); getAction(i).export(out); } out.endElement(); } /** * Evaluate an expression as a single item. This always returns either a single Item or * null (denoting the empty sequence). No conversion is done. This method should not be * used unless the static type of the expression is a subtype of "item" or "item?": that is, * it should not be called if the expression may return a sequence. There is no guarantee that * this condition will be detected. * * @param context The context in which the expression is to be evaluated * @return the node or atomic value that results from evaluating the * expression; or null to indicate that the result is an empty * sequence * @throws XPathException if any dynamic error occurs evaluating the * expression */ @Override public Item evaluateItem(XPathContext context) throws XPathException { return makeElaborator().elaborateForItem().eval(context); } /** * Return an Iterator to iterate over the values of a sequence. The value of every * expression can be regarded as a sequence, so this method is supported for all * expressions. This default implementation relies on the process() method: it * "pushes" the results of the instruction to a sequence in memory, and then * iterates over this in-memory sequence. *

In principle instructions should implement a pipelined iterate() method that * avoids the overhead of intermediate storage.

* * @param context supplies the context for evaluation * @return a SequenceIterator that can be used to iterate over the result * of the expression * @throws XPathException if any dynamic error occurs evaluating the * expression */ /*@NotNull*/ @Override public SequenceIterator iterate(XPathContext context) throws XPathException { return makeElaborator().elaborateForPull().iterate(context); } /** * Get a name identifying the kind of expression, in terms meaningful to a user. * * @return a name identifying the kind of expression, in terms meaningful to a user. * The name will always be in the form of a lexical XML QName, and should match the name used * in explain() output displaying the expression. */ @Override public String getExpressionName() { return "choose"; } @Override public String getStreamerName() { return "Choose"; } /** * Make an elaborator for this expression * * @return a suitable elaborator */ @Override public Elaborator getElaborator() { return new ChooseExprElaborator(); } public BooleanEvaluator[] getConditionEvaluators() { return ((ChooseExprElaborator)makeElaborator()).makeConditionEvaluators(this); } /** * Elaborator for a "Choose" expression (which may be xsl:if, xsl:choose, or an XPath conditional expression). * *

Provides "push" and "pull" implementations, as well as a singleton implementation.

*/ public static class ChooseExprElaborator extends PullElaborator { private BooleanEvaluator[] conditions; public BooleanEvaluator[] getConditionEvaluators() { return conditions; } public synchronized BooleanEvaluator[] makeConditionEvaluators(Choose expr) { if (conditions == null) { conditions = new BooleanEvaluator[expr.size()]; for (int i = 0; i < expr.size(); i++) { conditions[i] = expr.getCondition(i).makeElaborator().elaborateForBoolean(); } } return conditions; } public SequenceEvaluator eagerly() { final Choose expr = (Choose) getExpression(); final int count = expr.size(); makeConditionEvaluators(expr); final SequenceEvaluator[] actions = new SequenceEvaluator[count]; for (int i = 0; i < count; i++) { actions[i] = expr.getAction(i).makeElaborator().eagerly(); } return new EagerChooseEvaluator(conditions, actions); } public PullEvaluator elaborateForPull() { final Choose expr = (Choose) getExpression(); final int count = expr.size(); final PullEvaluator[] actions = new PullEvaluator[count]; makeConditionEvaluators(expr); for (int i = 0; i < count; i++) { actions[i] = expr.getAction(i).makeElaborator().elaborateForPull(); } switch (count) { case 1: return context -> { if (conditions[0].eval(context)) return actions[0].iterate(context); return EmptyIterator.getInstance(); }; case 2: return context -> { if (conditions[0].eval(context)) return actions[0].iterate(context); if (conditions[1].eval(context)) return actions[1].iterate(context); return EmptyIterator.getInstance(); }; case 3: return context -> { if (conditions[0].eval(context)) return actions[0].iterate(context); if (conditions[1].eval(context)) return actions[1].iterate(context); if (conditions[2].eval(context)) return actions[2].iterate(context); return EmptyIterator.getInstance(); }; case 4: return context -> { if (conditions[0].eval(context)) return actions[0].iterate(context); if (conditions[1].eval(context)) return actions[1].iterate(context); if (conditions[2].eval(context)) return actions[2].iterate(context); if (conditions[3].eval(context)) return actions[3].iterate(context); return EmptyIterator.getInstance(); }; default: return context -> { for (int i = 0; i < count; i++) { if (conditions[i].eval(context)) { return actions[i].iterate(context); } } return EmptyIterator.getInstance(); }; } } @Override public ItemEvaluator elaborateForItem() { final Choose expr = (Choose) getExpression(); final int count = expr.size(); final ItemEvaluator[] actions = new ItemEvaluator[count]; makeConditionEvaluators(expr); for (int i = 0; i < count; i++) { actions[i] = expr.getAction(i).makeElaborator().elaborateForItem(); } switch (count) { case 1: return context -> { if (conditions[0].eval(context)) return actions[0].eval(context); return null; }; case 2: return context -> { if (conditions[0].eval(context)) return actions[0].eval(context); if (conditions[1].eval(context)) return actions[1].eval(context); return null; }; case 3: return context -> { if (conditions[0].eval(context)) return actions[0].eval(context); if (conditions[1].eval(context)) return actions[1].eval(context); if (conditions[2].eval(context)) return actions[2].eval(context); return null; }; case 4: return context -> { if (conditions[0].eval(context)) return actions[0].eval(context); if (conditions[1].eval(context)) return actions[1].eval(context); if (conditions[2].eval(context)) return actions[2].eval(context); if (conditions[3].eval(context)) return actions[3].eval(context); return null; }; default: return context -> { for (int i = 0; i < count; i++) { if (conditions[i].eval(context)) { return actions[i].eval(context); } } return null; }; } } @Override public PushEvaluator elaborateForPush() { final Choose expr = (Choose) getExpression(); final int count = expr.size(); makeConditionEvaluators(expr); final PushEvaluator[] actions = new PushEvaluator[count]; for (int i = 0; i < count; i++) { actions[i] = expr.getAction(i).makeElaborator().elaborateForPush(); } switch (count) { case 1: return (output, context) -> { if (conditions[0].eval(context)) return actions[0].processLeavingTail(output, context); return null; }; case 2: return (output, context) -> { if (conditions[0].eval(context)) return actions[0].processLeavingTail(output, context); if (conditions[1].eval(context)) return actions[1].processLeavingTail(output, context); return null; }; case 3: return (output, context) -> { if (conditions[0].eval(context)) return actions[0].processLeavingTail(output, context); if (conditions[1].eval(context)) return actions[1].processLeavingTail(output, context); if (conditions[2].eval(context)) return actions[2].processLeavingTail(output, context); return null; }; case 4: return (output, context) -> { if (conditions[0].eval(context)) return actions[0].processLeavingTail(output, context); if (conditions[1].eval(context)) return actions[1].processLeavingTail(output, context); if (conditions[2].eval(context)) return actions[2].processLeavingTail(output, context); if (conditions[3].eval(context)) return actions[3].processLeavingTail(output, context); return null; }; default: return (output, context) -> { for (int i = 0; i < count; i++) { if (conditions[i].eval(context)) { return actions[i].processLeavingTail(output, context); } } return null; }; } } @Override public UpdateEvaluator elaborateForUpdate() { final Choose expr = (Choose) getExpression(); final int count = expr.size(); makeConditionEvaluators(expr); final UpdateEvaluator[] actions = new UpdateEvaluator[count]; for (int i = 0; i < count; i++) { actions[i] = expr.getAction(i).makeElaborator().elaborateForUpdate(); } return (context, updates) -> { for (int i = 0; i < count; i++) { if (conditions[i].eval(context)) { actions[i].registerUpdates(context, updates); break; } } }; } } private static class EagerChooseEvaluator implements SequenceEvaluator { private final BooleanEvaluator[] conditions; private final SequenceEvaluator[] actions; private final int count; public EagerChooseEvaluator(BooleanEvaluator[] conditions, SequenceEvaluator[] actions) { this.conditions = conditions; this.actions = actions; this.count = conditions.length; } /** * Evaluate a construct to produce a value (which might be a lazily evaluated Sequence) * * @param context the evaluation context * @return a Sequence (not necessarily grounded) * @throws XPathException if a dynamic error occurs during the evaluation. */ @Override public Sequence evaluate(XPathContext context) throws XPathException { for (int i = 0; i < count; i++) { if (conditions[i].eval(context)) { return actions[i].evaluate(context); } } return EmptySequence.getInstance(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy