Browse Source

follow-up to Bug 62904. More tests and improved evaluation of IF in array mode

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1851263 13f79535-47bb-0310-9956-ffa450edef68
tags/REL_4_1_0
Yegor Kozlov 5 years ago
parent
commit
698d8eb006

+ 32
- 34
src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java View File

@@ -398,6 +398,9 @@ public final class WorkbookEvaluator {
dbgEvaluationOutputIndent++;
}

EvaluationSheet evalSheet = ec.getWorkbook().getSheet(ec.getSheetIndex());
EvaluationCell evalCell = evalSheet.getCell(ec.getRowIndex(), ec.getColumnIndex());

Stack<ValueEval> stack = new Stack<>();
for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
// since we don't know how to handle these yet :(
@@ -436,46 +439,41 @@ public final class WorkbookEvaluator {
continue;
}
if (attrPtg.isOptimizedIf()) {
ValueEval arg0 = stack.pop();
boolean evaluatedPredicate;
try {
evaluatedPredicate = IfFunc.evaluateFirstArg(arg0, ec.getRowIndex(), ec.getColumnIndex());
} catch (EvaluationException e) {
stack.push(e.getErrorEval());
int dist = attrPtg.getData();
i+= countTokensToBeSkipped(ptgs, i, dist);
attrPtg = (AttrPtg) ptgs[i];
dist = attrPtg.getData()+1;
i+= countTokensToBeSkipped(ptgs, i, dist);
continue;
}
if (evaluatedPredicate) {
// nothing to skip - true param follows
} else {
int dist = attrPtg.getData();
Ptg currPtg = ptgs[i+1];
i+= countTokensToBeSkipped(ptgs, i, dist);
Ptg nextPtg = ptgs[i+1];

if (ptgs[i] instanceof AttrPtg && nextPtg instanceof FuncVarPtg &&
// in order to verify that there is no third param, we need to check
// if we really have the IF next or some other FuncVarPtg as third param, e.g. ROW()/COLUMN()!
((FuncVarPtg)nextPtg).getFunctionIndex() == FunctionMetadataRegistry.FUNCTION_INDEX_IF) {
// this is an if statement without a false param (as opposed to MissingArgPtg as the false param)
//i++;
stack.push(arg0);
if(currPtg instanceof AreaPtg){
// IF in array mode. See Bug 62904
ValueEval currEval = getEvalForPtg(currPtg, ec);
stack.push(currEval);
} else {
if(!evalCell.isPartOfArrayFormulaGroup()) {
ValueEval arg0 = stack.pop();
boolean evaluatedPredicate;

try {
evaluatedPredicate = IfFunc.evaluateFirstArg(arg0, ec.getRowIndex(), ec.getColumnIndex());
} catch (EvaluationException e) {
stack.push(e.getErrorEval());
int dist = attrPtg.getData();
i += countTokensToBeSkipped(ptgs, i, dist);
attrPtg = (AttrPtg) ptgs[i];
dist = attrPtg.getData() + 1;
i += countTokensToBeSkipped(ptgs, i, dist);
continue;
}
if (evaluatedPredicate) {
// nothing to skip - true param follows
} else {
int dist = attrPtg.getData();
i += countTokensToBeSkipped(ptgs, i, dist);
Ptg nextPtg = ptgs[i + 1];
if (ptgs[i] instanceof AttrPtg && nextPtg instanceof FuncVarPtg &&
// in order to verify that there is no third param, we need to check
// if we really have the IF next or some other FuncVarPtg as third param, e.g. ROW()/COLUMN()!
((FuncVarPtg) nextPtg).getFunctionIndex() == FunctionMetadataRegistry.FUNCTION_INDEX_IF) {
// this is an if statement without a false param (as opposed to MissingArgPtg as the false param)
//i++;
stack.push(arg0);
stack.push(BoolEval.FALSE);
}
}
}
continue;
}
if (attrPtg.isSkip()) {
if (attrPtg.isSkip() && !evalCell.isPartOfArrayFormulaGroup()) {
int dist = attrPtg.getData()+1;
i+= countTokensToBeSkipped(ptgs, i, dist);
if (stack.peek() == MissingArgEval.instance) {

+ 4
- 1
src/java/org/apache/poi/ss/formula/eval/UnaryMinusEval.java View File

@@ -48,7 +48,10 @@ public final class UnaryMinusEval extends Fixed1ArgFunction implements ArrayFun

@Override
public ValueEval evaluateArray(ValueEval[] args, int srcRowIndex, int srcColumnIndex){
return evaluateOneArrayArg(args, srcRowIndex, srcColumnIndex, (valA) ->
if (args.length != 1) {
return ErrorEval.VALUE_INVALID;
}
return evaluateOneArrayArg(args[0], srcRowIndex, srcColumnIndex, (valA) ->
evaluate(srcRowIndex, srcColumnIndex, valA)
);
}

+ 4
- 1
src/java/org/apache/poi/ss/formula/eval/UnaryPlusEval.java View File

@@ -52,7 +52,10 @@ public final class UnaryPlusEval extends Fixed1ArgFunction implements ArrayFunc

@Override
public ValueEval evaluateArray(ValueEval[] args, int srcRowIndex, int srcColumnIndex){
return evaluateOneArrayArg(args, srcRowIndex, srcColumnIndex, (valA) ->
if (args.length != 1) {
return ErrorEval.VALUE_INVALID;
}
return evaluateOneArrayArg(args[0], srcRowIndex, srcColumnIndex, (valA) ->
evaluate(srcRowIndex, srcColumnIndex, valA)
);
}

+ 19
- 3
src/java/org/apache/poi/ss/formula/functions/ArrayFunction.java View File

@@ -111,6 +111,12 @@ public interface ArrayFunction {
vA = ErrorEval.NAME_INVALID;
} catch (EvaluationException e) {
vA = e.getErrorEval();
} catch (RuntimeException e) {
if(e.getMessage().startsWith("Don't now how to evaluate name")){
vA = ErrorEval.NAME_INVALID;
} else {
throw e;
}
}
ValueEval vB;
try {
@@ -119,6 +125,12 @@ public interface ArrayFunction {
vB = ErrorEval.NAME_INVALID;
} catch (EvaluationException e) {
vB = e.getErrorEval();
} catch (RuntimeException e) {
if(e.getMessage().startsWith("Don't now how to evaluate name")){
vB = ErrorEval.NAME_INVALID;
} else {
throw e;
}
}
if(vA instanceof ErrorEval){
vals[idx++] = vA;
@@ -138,10 +150,8 @@ public interface ArrayFunction {
return new CacheAreaEval(srcRowIndex, srcColumnIndex, srcRowIndex + height - 1, srcColumnIndex + width - 1, vals);
}

default ValueEval evaluateOneArrayArg(ValueEval[] args, int srcRowIndex, int srcColumnIndex,
default ValueEval evaluateOneArrayArg(ValueEval arg0, int srcRowIndex, int srcColumnIndex,
java.util.function.Function<ValueEval, ValueEval> evalFunc){
ValueEval arg0 = args[0];

int w1, w2, h1, h2;
int a1FirstCol = 0, a1FirstRow = 0;
if (arg0 instanceof AreaEval) {
@@ -178,6 +188,12 @@ public interface ArrayFunction {
vA = ErrorEval.NAME_INVALID;
} catch (EvaluationException e) {
vA = e.getErrorEval();
} catch (RuntimeException e) {
if(e.getMessage().startsWith("Don't now how to evaluate name")){
vA = ErrorEval.NAME_INVALID;
} else {
throw e;
}
}
vals[idx++] = evalFunc.apply(vA);
}

+ 25
- 3
src/java/org/apache/poi/ss/formula/functions/BooleanFunction.java View File

@@ -37,7 +37,7 @@ import org.apache.poi.ss.formula.eval.ValueEval;
*
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*/
public abstract class BooleanFunction implements Function {
public abstract class BooleanFunction implements Function,ArrayFunction {

public final ValueEval evaluate(ValueEval[] args, int srcRow, int srcCol) {
if (args.length < 1) {
@@ -142,7 +142,20 @@ public abstract class BooleanFunction implements Function {
return BoolEval.TRUE;
}
};
public static final Function NOT = new Fixed1ArgFunction() {

abstract static class Boolean1ArgFunction extends Fixed1ArgFunction implements ArrayFunction {
@Override
public ValueEval evaluateArray(ValueEval[] args, int srcRowIndex, int srcColumnIndex) {
if (args.length != 1) {
return ErrorEval.VALUE_INVALID;
}
return evaluateOneArrayArg(args[0], srcRowIndex, srcColumnIndex,
vA -> evaluate(srcRowIndex, srcColumnIndex, vA));
}

}

public static final Function NOT = new Boolean1ArgFunction() {
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) {
boolean boolArgVal;
try {
@@ -156,4 +169,13 @@ public abstract class BooleanFunction implements Function {
return BoolEval.valueOf(!boolArgVal);
}
};
}

@Override
public ValueEval evaluateArray(ValueEval[] args, int srcRowIndex, int srcColumnIndex) {
if (args.length != 1) {
return ErrorEval.VALUE_INVALID;
}
return evaluateOneArrayArg(args[0], srcRowIndex, srcColumnIndex,
vA -> evaluate(new ValueEval[]{vA}, srcRowIndex, srcColumnIndex));
}
}

+ 110
- 11
src/java/org/apache/poi/ss/formula/functions/IfFunc.java View File

@@ -17,10 +17,14 @@

package org.apache.poi.ss.formula.functions;

import org.apache.poi.ss.formula.CacheAreaEval;
import org.apache.poi.ss.formula.FormulaParseException;
import org.apache.poi.ss.formula.eval.*;
import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.ss.formula.ptg.RefPtg;

import java.util.function.BiFunction;

/**
* Implementation for the Excel function IF
* <p>
@@ -84,25 +88,120 @@ public final class IfFunc extends Var2or3ArgFunction implements ArrayFunction {

@Override
public ValueEval evaluateArray(ValueEval[] args, int srcRowIndex, int srcColumnIndex) {
if (args.length < 2 || args.length > 3) {
return ErrorEval.VALUE_INVALID;
}

ValueEval arg0 = args[0];
ValueEval arg1 = args[1];
return evaluateTwoArrayArgs(arg0, arg1, srcRowIndex, srcColumnIndex,
(vA, vB) -> {
ValueEval arg2 = args.length == 2 ? BoolEval.FALSE : args[2];
return evaluateArrayArgs(arg0, arg1, arg2, srcRowIndex, srcColumnIndex);
}

ValueEval evaluateArrayArgs(ValueEval arg0, ValueEval arg1, ValueEval arg2, int srcRowIndex, int srcColumnIndex) {
int w1, w2, h1, h2;
int a1FirstCol = 0, a1FirstRow = 0;
if (arg0 instanceof AreaEval) {
AreaEval ae = (AreaEval)arg0;
w1 = ae.getWidth();
h1 = ae.getHeight();
a1FirstCol = ae.getFirstColumn();
a1FirstRow = ae.getFirstRow();
} else if (arg0 instanceof RefEval){
RefEval ref = (RefEval)arg0;
w1 = 1;
h1 = 1;
a1FirstCol = ref.getColumn();
a1FirstRow = ref.getRow();
} else {
w1 = 1;
h1 = 1;
}
int a2FirstCol = 0, a2FirstRow = 0;
if (arg1 instanceof AreaEval) {
AreaEval ae = (AreaEval)arg1;
w2 = ae.getWidth();
h2 = ae.getHeight();
a2FirstCol = ae.getFirstColumn();
a2FirstRow = ae.getFirstRow();
} else if (arg1 instanceof RefEval){
RefEval ref = (RefEval)arg1;
w2 = 1;
h2 = 1;
a2FirstCol = ref.getColumn();
a2FirstRow = ref.getRow();
} else {
w2 = 1;
h2 = 1;
}

int a3FirstCol = 0, a3FirstRow = 0;
if (arg2 instanceof AreaEval) {
AreaEval ae = (AreaEval)arg2;
a3FirstCol = ae.getFirstColumn();
a3FirstRow = ae.getFirstRow();
} else if (arg2 instanceof RefEval){
RefEval ref = (RefEval)arg2;
a3FirstCol = ref.getColumn();
a3FirstRow = ref.getRow();
}

int width = Math.max(w1, w2);
int height = Math.max(h1, h2);

ValueEval[] vals = new ValueEval[height * width];

int idx = 0;
for(int i = 0; i < height; i++){
for(int j = 0; j < width; j++){
ValueEval vA;
try {
vA = OperandResolver.getSingleValue(arg0, a1FirstRow + i, a1FirstCol + j);
} catch (FormulaParseException e) {
vA = ErrorEval.NAME_INVALID;
} catch (EvaluationException e) {
vA = e.getErrorEval();
}
ValueEval vB;
try {
vB = OperandResolver.getSingleValue(arg1, a2FirstRow + i, a2FirstCol + j);
} catch (FormulaParseException e) {
vB = ErrorEval.NAME_INVALID;
} catch (EvaluationException e) {
vB = e.getErrorEval();
}

ValueEval vC;
try {
vC = OperandResolver.getSingleValue(arg2, a3FirstRow + i, a3FirstCol + j);
} catch (FormulaParseException e) {
vC = ErrorEval.NAME_INVALID;
} catch (EvaluationException e) {
vC = e.getErrorEval();
}

if(vA instanceof ErrorEval){
vals[idx++] = vA;
} else if (vB instanceof ErrorEval) {
vals[idx++] = vB;
} else {
Boolean b;
try {
b = OperandResolver.coerceValueToBoolean(vA, false);
vals[idx++] = b != null && b ? vB : vC;
} catch (EvaluationException e) {
return e.getErrorEval();
}
if (b != null && b) {
if (vB == MissingArgEval.instance) {
return BlankEval.instance;
}
return vB;
vals[idx++] = e.getErrorEval();
}
return BoolEval.FALSE;
}
);

}
}

if (vals.length == 1) {
return vals[0];
}

return new CacheAreaEval(srcRowIndex, srcColumnIndex, srcRowIndex + height - 1, srcColumnIndex + width - 1, vals);
}

}

+ 4
- 1
src/java/org/apache/poi/ss/formula/functions/LogicalFunction.java View File

@@ -44,7 +44,10 @@ public abstract class LogicalFunction extends Fixed1ArgFunction implements Array

@Override
public ValueEval evaluateArray(ValueEval[] args, int srcRowIndex, int srcColumnIndex){
return evaluateOneArrayArg(args, srcRowIndex, srcColumnIndex, (valA) ->
if (args.length != 1) {
return ErrorEval.VALUE_INVALID;
}
return evaluateOneArrayArg(args[0], srcRowIndex, srcColumnIndex, (valA) ->
BoolEval.valueOf(evaluate(valA))
);
}

+ 32
- 0
src/testcases/org/apache/poi/ss/formula/functions/TestBooleanFunctionsFromSpreadsheet.java View File

@@ -0,0 +1,32 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */

package org.apache.poi.ss.formula.functions;

import org.junit.runners.Parameterized.Parameters;

import java.util.Collection;

/**
* Tests boolean functions as loaded from a test data spreadsheet.<p>
*/
public class TestBooleanFunctionsFromSpreadsheet extends BaseTestFunctionsFromSpreadsheet {
@Parameters(name="{0}")
public static Collection<Object[]> data() throws Exception {
return data(TestBooleanFunctionsFromSpreadsheet.class, "BooleanFunctionsTestCaseData.xls");
}
}

BIN
test-data/spreadsheet/BooleanFunctionsTestCaseData.xls View File


BIN
test-data/spreadsheet/IfFunctionTestCaseData.xls View File


BIN
test-data/spreadsheet/RowFunctionTestCaseData.xls View File


Loading…
Cancel
Save