Bug 61063: Add a RefListEval to handle UnionPtg and implement it for Rank(), may be missing for other functions where ArrayEval is currently handled

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1795963 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Dominik Stadler 2017-05-23 21:13:02 +00:00
parent 8564aa7bb6
commit d868026959
8 changed files with 140 additions and 88 deletions

View File

@ -28,19 +28,7 @@ import java.util.TreeSet;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment.WorkbookNotFoundException;
import org.apache.poi.ss.formula.atp.AnalysisToolPak;
import org.apache.poi.ss.formula.eval.BlankEval;
import org.apache.poi.ss.formula.eval.BoolEval;
import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.EvaluationException;
import org.apache.poi.ss.formula.eval.ExternalNameEval;
import org.apache.poi.ss.formula.eval.FunctionEval;
import org.apache.poi.ss.formula.eval.FunctionNameEval;
import org.apache.poi.ss.formula.eval.MissingArgEval;
import org.apache.poi.ss.formula.eval.NotImplementedException;
import org.apache.poi.ss.formula.eval.NumberEval;
import org.apache.poi.ss.formula.eval.OperandResolver;
import org.apache.poi.ss.formula.eval.StringEval;
import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.formula.eval.*;
import org.apache.poi.ss.formula.function.FunctionMetadataRegistry;
import org.apache.poi.ss.formula.functions.Choose;
import org.apache.poi.ss.formula.functions.FreeRefFunction;
@ -84,12 +72,12 @@ public final class WorkbookEvaluator {
private final IStabilityClassifier _stabilityClassifier;
private final AggregatingUDFFinder _udfFinder;
private boolean _ignoreMissingWorkbooks = false;
private boolean _ignoreMissingWorkbooks;
/**
* whether print detailed messages about the next formula evaluation
*/
private boolean dbgEvaluationOutputForNextEval = false;
private boolean dbgEvaluationOutputForNextEval;
// special logger for formula evaluation output (because of possibly very large output)
private final POILogger EVAL_LOG = POILogFactory.getLogger("POI.FormulaEval");
@ -415,11 +403,10 @@ public final class WorkbookEvaluator {
Stack<ValueEval> stack = new Stack<ValueEval>();
for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
// since we don't know how to handle these yet :(
Ptg ptg = ptgs[i];
if (dbgEvaluationOutputIndent > 0) {
EVAL_LOG.log(POILogger.INFO, dbgIndentStr + " * ptg " + i + ": " + ptg);
EVAL_LOG.log(POILogger.INFO, dbgIndentStr + " * ptg " + i + ": " + ptg + ", stack: " + stack);
}
if (ptg instanceof AttrPtg) {
AttrPtg attrPtg = (AttrPtg) ptg;
@ -504,13 +491,17 @@ public final class WorkbookEvaluator {
continue;
}
if (ptg instanceof UnionPtg) {
ValueEval v2 = stack.pop();
ValueEval v1 = stack.pop();
stack.push(new RefListEval(v1, v2));
continue;
}
ValueEval opResult;
if (ptg instanceof OperationPtg) {
OperationPtg optg = (OperationPtg) ptg;
if (optg instanceof UnionPtg) { continue; }
int numops = optg.getNumberOfOperands();
ValueEval[] ops = new ValueEval[numops];

View File

@ -0,0 +1,46 @@
/* ====================================================================
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.eval;
import java.util.ArrayList;
import java.util.List;
/**
* Handling of a list of values, e.g. the 2nd argument in RANK(A1,(B1,B2,B3),1)
*/
public class RefListEval implements ValueEval {
private final List<ValueEval> list = new ArrayList<ValueEval>();
public RefListEval(ValueEval v1, ValueEval v2) {
add(v1);
add(v2);
}
private void add(ValueEval v) {
// flatten multiple nested RefListEval
if(v instanceof RefListEval) {
list.addAll(((RefListEval)v).list);
} else {
list.add(v);
}
}
public List<ValueEval> getList() {
return list;
}
}

View File

@ -17,16 +17,7 @@
package org.apache.poi.ss.formula.functions;
import org.apache.poi.ss.formula.eval.AreaEval;
import org.apache.poi.ss.formula.eval.BlankEval;
import org.apache.poi.ss.formula.eval.BoolEval;
import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.EvaluationException;
import org.apache.poi.ss.formula.eval.NumberEval;
import org.apache.poi.ss.formula.eval.OperandResolver;
import org.apache.poi.ss.formula.eval.RefEval;
import org.apache.poi.ss.formula.eval.StringEval;
import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.formula.eval.*;
/**
* Implementation of the various ISxxx Logical Functions, which
@ -131,7 +122,7 @@ public abstract class LogicalFunction extends Fixed1ArgFunction {
public static final Function ISREF = new Fixed1ArgFunction() {
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) {
if (arg0 instanceof RefEval || arg0 instanceof AreaEval) {
if (arg0 instanceof RefEval || arg0 instanceof AreaEval || arg0 instanceof RefListEval) {
return BoolEval.TRUE;
}
return BoolEval.FALSE;

View File

@ -19,13 +19,7 @@
package org.apache.poi.ss.formula.functions;
import org.apache.poi.ss.formula.eval.AreaEval;
import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.EvaluationException;
import org.apache.poi.ss.formula.eval.NumberEval;
import org.apache.poi.ss.formula.eval.OperandResolver;
import org.apache.poi.ss.formula.eval.RefEval;
import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.formula.eval.*;
/**
@ -45,51 +39,56 @@ import org.apache.poi.ss.formula.eval.ValueEval;
public class Rank extends Var2or3ArgFunction {
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
AreaEval aeRange;
double result;
try {
ValueEval ve = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
result = OperandResolver.coerceValueToDouble(ve);
double result = OperandResolver.coerceValueToDouble(ve);
if (Double.isNaN(result) || Double.isInfinite(result)) {
throw new EvaluationException(ErrorEval.NUM_ERROR);
}
aeRange = convertRangeArg(arg1);
if(arg1 instanceof RefListEval) {
return eval(result, ((RefListEval)arg1), true);
}
final AreaEval aeRange = convertRangeArg(arg1);
return eval(result, aeRange, true);
} catch (EvaluationException e) {
return e.getErrorEval();
}
return eval(srcRowIndex, srcColumnIndex, result, aeRange, true);
}
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, ValueEval arg2) {
AreaEval aeRange;
double result;
boolean order=false;
try {
ValueEval ve = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
result = OperandResolver.coerceValueToDouble(ve);
final double result = OperandResolver.coerceValueToDouble(ve);
if (Double.isNaN(result) || Double.isInfinite(result)) {
throw new EvaluationException(ErrorEval.NUM_ERROR);
}
aeRange = convertRangeArg(arg1);
ve = OperandResolver.getSingleValue(arg2, srcRowIndex, srcColumnIndex);
int order_value = OperandResolver.coerceValueToInt(ve);
if(order_value==0){
order=true;
}else if(order_value==1){
order=false;
}else throw new EvaluationException(ErrorEval.NUM_ERROR);
ve = OperandResolver.getSingleValue(arg2, srcRowIndex, srcColumnIndex);
int order_value = OperandResolver.coerceValueToInt(ve);
final boolean order;
if(order_value==0) {
order = true;
} else if(order_value==1) {
order = false;
} else {
throw new EvaluationException(ErrorEval.NUM_ERROR);
}
if(arg1 instanceof RefListEval) {
return eval(result, ((RefListEval)arg1), order);
}
final AreaEval aeRange = convertRangeArg(arg1);
return eval(result, aeRange, order);
} catch (EvaluationException e) {
return e.getErrorEval();
}
return eval(srcRowIndex, srcColumnIndex, result, aeRange, order);
}
private static ValueEval eval(int srcRowIndex, int srcColumnIndex, double arg0, AreaEval aeRange, boolean descending_order) {
private static ValueEval eval(double arg0, AreaEval aeRange, boolean descending_order) {
int rank = 1;
int height=aeRange.getHeight();
int width= aeRange.getWidth();
@ -105,9 +104,30 @@ public class Rank extends Var2or3ArgFunction {
}
return new NumberEval(rank);
}
private static Double getValue(AreaEval aeRange, int relRowIndex, int relColIndex) {
private static ValueEval eval(double arg0, RefListEval aeRange, boolean descending_order) {
int rank = 1;
for(ValueEval ve : aeRange.getList()) {
if (ve instanceof RefEval) {
ve = ((RefEval) ve).getInnerValueEval(((RefEval) ve).getFirstSheetIndex());
}
final Double value;
if (ve instanceof NumberEval) {
value = ((NumberEval)ve).getNumberValue();
} else {
continue;
}
if(descending_order && value>arg0 || !descending_order && value<arg0){
rank++;
}
}
return new NumberEval(rank);
}
private static Double getValue(AreaEval aeRange, int relRowIndex, int relColIndex) {
ValueEval addend = aeRange.getRelativeValue(relRowIndex, relColIndex);
if (addend instanceof NumberEval) {
return ((NumberEval)addend).getNumberValue();

View File

@ -3188,4 +3188,23 @@ public final class TestXSSFBugs extends BaseTestBugzillaIssues {
wb.close();
}
@Test
public void bug61063() throws Exception {
Workbook wb = XSSFTestDataSamples.openSampleWorkbook("61063.xlsx");
FormulaEvaluator eval = wb.getCreationHelper().createFormulaEvaluator();
Sheet s = wb.getSheetAt(0);
Row r = s.getRow(3);
Cell c = r.getCell(0);
assertEquals(CellType.FORMULA, c.getCellTypeEnum());
System.out.println(c.getCellFormula());
eval.setDebugEvaluationOutputForNextEval(true);
CellValue cv = eval.evaluate(c);
assertNotNull(cv);
assertEquals("Had: " + cv, 2.0, cv.getNumberValue(), 0.00001);
wb.close();
}
}

View File

@ -36,13 +36,7 @@ import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.MissingArgEval;
import org.apache.poi.ss.formula.eval.NumberEval;
import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.formula.ptg.AreaErrPtg;
import org.apache.poi.ss.formula.ptg.AttrPtg;
import org.apache.poi.ss.formula.ptg.DeletedArea3DPtg;
import org.apache.poi.ss.formula.ptg.DeletedRef3DPtg;
import org.apache.poi.ss.formula.ptg.IntPtg;
import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.ss.formula.ptg.RefErrorPtg;
import org.apache.poi.ss.formula.ptg.*;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.CellValue;
@ -112,7 +106,6 @@ public class TestWorkbookEvaluator {
*/
@Test
public void testMemFunc() {
Ptg[] ptgs = {
new IntPtg(42),
AttrPtg.SUM,
@ -122,7 +115,6 @@ public class TestWorkbookEvaluator {
assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0);
}
@Test
public void testEvaluateMultipleWorkbooks() {
HSSFWorkbook wbA = HSSFTestDataSamples.openSampleWorkbook("multibookFormulaA.xls");
@ -227,7 +219,6 @@ public class TestWorkbookEvaluator {
/**
* Functions like IF, INDIRECT, INDEX, OFFSET etc can return AreaEvals which
* should be dereferenced by the evaluator
* @throws IOException
*/
@Test
public void testResultOutsideRange() throws IOException {
@ -262,7 +253,6 @@ public class TestWorkbookEvaluator {
/**
* formulas with defined names.
* @throws IOException
*/
@Test
public void testNamesInFormulas() throws IOException {
@ -406,7 +396,7 @@ public class TestWorkbookEvaluator {
final String formula, final CellType cellType, final String expectedFormula, final double expectedValue) {
testIFEqualsFormulaEvaluation_evaluate(formula, cellType, expectedFormula, expectedValue);
testIFEqualsFormulaEvaluation_evaluateFormulaCell(formula, cellType, expectedFormula, expectedValue);
testIFEqualsFormulaEvaluation_evaluateInCell(formula, cellType, expectedFormula, expectedValue);
testIFEqualsFormulaEvaluation_evaluateInCell(formula, cellType, expectedValue);
testIFEqualsFormulaEvaluation_evaluateAll(formula, cellType, expectedFormula, expectedValue);
testIFEqualsFormulaEvaluation_evaluateAllFormulaCells(formula, cellType, expectedFormula, expectedValue);
}
@ -552,7 +542,7 @@ public class TestWorkbookEvaluator {
}
private void testIFEqualsFormulaEvaluation_evaluateInCell(
String formula, CellType cellType, String expectedFormula, double expectedResult) {
String formula, CellType cellType, double expectedResult) {
Workbook wb = testIFEqualsFormulaEvaluation_setup(formula, cellType);
Cell D1 = wb.getSheet("IFEquals").getRow(0).getCell(3);
@ -564,7 +554,9 @@ public class TestWorkbookEvaluator {
try {
D1.getCellFormula();
fail("cell formula should be overwritten with formula result");
} catch (final IllegalStateException expected) { }
} catch (final IllegalStateException expected) {
// expected here
}
assertEquals(CellType.NUMERIC, D1.getCellTypeEnum());
assertEquals(expectedResult, D1.getNumericCellValue(), EPSILON);

View File

@ -19,13 +19,7 @@ package org.apache.poi.ss.formula.functions;
import junit.framework.TestCase;
import org.apache.poi.ss.formula.eval.AreaEval;
import org.apache.poi.ss.formula.eval.BlankEval;
import org.apache.poi.ss.formula.eval.BoolEval;
import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.NumberEval;
import org.apache.poi.ss.formula.eval.StringEval;
import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.formula.eval.*;
/**
* Test cases for Excel function T()
@ -43,6 +37,7 @@ public final class TestTFunc extends TestCase {
assertNotNull("result may never be null", result);
return result;
}
/**
* Simulates call: T(A1)
* where cell A1 has the specified innerValue
@ -60,7 +55,6 @@ public final class TestTFunc extends TestCase {
}
public void testTextValues() {
confirmText("abc");
confirmText("");
confirmText(" ");
@ -121,8 +115,7 @@ public final class TestTFunc extends TestCase {
};
AreaEval ae = EvalFactory.createAreaEval("C10:D11", areaValues);
ValueEval ve;
ve = invokeT(ae);
ValueEval ve = invokeT(ae);
confirmString(ve, "abc");
areaValues[0] = new NumberEval(5.0);

Binary file not shown.