![JAR search and dependency download from the Maven repository](/logo.png)
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