git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1850646 13f79535-47bb-0310-9956-ffa450edef68pull/140/head
@@ -56,6 +56,7 @@ import org.apache.poi.ss.formula.function.FunctionMetadataRegistry; | |||
import org.apache.poi.ss.formula.functions.ArrayFunction; | |||
import org.apache.poi.ss.formula.functions.Function; | |||
import org.apache.poi.ss.formula.functions.Indirect; | |||
import org.apache.poi.ss.util.CellRangeAddress; | |||
/** | |||
* This class creates <tt>OperationEval</tt> instances to help evaluate <tt>OperationPtg</tt> | |||
@@ -138,8 +139,16 @@ final class OperationEvaluatorFactory { | |||
EvaluationSheet evalSheet = ec.getWorkbook().getSheet(ec.getSheetIndex()); | |||
EvaluationCell evalCell = evalSheet.getCell(ec.getRowIndex(), ec.getColumnIndex()); | |||
if (evalCell != null && (evalCell.isPartOfArrayFormulaGroup() || ec.isArraymode()) && result instanceof ArrayFunction) | |||
return ((ArrayFunction) result).evaluateArray(args, ec.getRowIndex(), ec.getColumnIndex()); | |||
if (evalCell != null && result instanceof ArrayFunction) { | |||
ArrayFunction func = (ArrayFunction) result; | |||
if(evalCell.isPartOfArrayFormulaGroup()){ | |||
// array arguments must be evaluated relative to the function defining range | |||
CellRangeAddress ca = evalCell.getArrayFormulaRange(); | |||
return func.evaluateArray(args, ca.getFirstRow(), ca.getFirstColumn()); | |||
} else if (ec.isArraymode()){ | |||
return func.evaluateArray(args, ec.getRowIndex(), ec.getColumnIndex()); | |||
} | |||
} | |||
return result.evaluate(args, ec.getRowIndex(), ec.getColumnIndex()); | |||
} else if (udfFunc != null){ |
@@ -453,15 +453,24 @@ public final class WorkbookEvaluator { | |||
// 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 (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(BoolEval.FALSE); | |||
//i++; | |||
stack.push(arg0); | |||
if(currPtg instanceof AreaPtg){ | |||
// IF in array mode. See Bug 62904 | |||
ValueEval currEval = getEvalForPtg(currPtg, ec); | |||
stack.push(currEval); | |||
} else { | |||
stack.push(BoolEval.FALSE); | |||
} | |||
} | |||
} | |||
continue; | |||
@@ -759,7 +768,7 @@ public final class WorkbookEvaluator { | |||
return evaluateNameFormula(nameRecord.getNameDefinition(), ec); | |||
} | |||
throw new RuntimeException("Don't now how to evalate name '" + nameRecord.getNameText() + "'"); | |||
throw new RuntimeException("Don't now how to evaluate name '" + nameRecord.getNameText() + "'"); | |||
} | |||
/** |
@@ -17,7 +17,6 @@ | |||
package org.apache.poi.ss.formula.eval; | |||
import org.apache.poi.ss.formula.CacheAreaEval; | |||
import org.apache.poi.ss.formula.functions.ArrayFunction; | |||
import org.apache.poi.ss.formula.functions.Fixed2ArgFunction; | |||
import org.apache.poi.ss.formula.functions.Function; | |||
@@ -74,84 +73,16 @@ public abstract class RelationalOperationEval extends Fixed2ArgFunction implemen | |||
return BoolEval.valueOf(result); | |||
} | |||
@Override | |||
public ValueEval evaluateArray(ValueEval[] args, int srcRowIndex, int srcColumnIndex) { | |||
ValueEval arg0 = args[0]; | |||
ValueEval arg1 = args[1]; | |||
return evaluateTwoArrayArgs(arg0, arg1, srcRowIndex, srcColumnIndex, (vA, vB) -> { | |||
int cmpResult = doCompare(vA, vB); | |||
boolean result = convertComparisonResult(cmpResult); | |||
return BoolEval.valueOf(result); | |||
}); | |||
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 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 (EvaluationException e) { | |||
vA = e.getErrorEval(); | |||
} | |||
ValueEval vB; | |||
try { | |||
vB = OperandResolver.getSingleValue(arg1, a2FirstRow + i, a2FirstCol + j); | |||
} catch (EvaluationException e) { | |||
vB = e.getErrorEval(); | |||
} | |||
if(vA instanceof ErrorEval){ | |||
vals[idx++] = vA; | |||
} else if (vB instanceof ErrorEval) { | |||
vals[idx++] = vB; | |||
} else { | |||
int cmpResult = doCompare(vA, vB); | |||
boolean result = convertComparisonResult(cmpResult); | |||
vals[idx++] = BoolEval.valueOf(result); | |||
} | |||
} | |||
} | |||
if (vals.length == 1) { | |||
return vals[0]; | |||
} | |||
return new CacheAreaEval(srcRowIndex, srcColumnIndex, srcRowIndex + height - 1, srcColumnIndex + width - 1, vals); | |||
} | |||
private static int doCompare(ValueEval va, ValueEval vb) { |
@@ -37,7 +37,20 @@ public abstract class TwoOperandNumericOperation extends Fixed2ArgFunction imple | |||
if (args.length != 2) { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
return new ArrayEval().evaluate(srcRowIndex, srcColumnIndex, args[0], args[1]); | |||
//return new ArrayEval().evaluate(srcRowIndex, srcColumnIndex, args[0], args[1]); | |||
return evaluateTwoArrayArgs(args[0], args[1], srcRowIndex, srcColumnIndex, | |||
(vA, vB) -> { | |||
try { | |||
double d0 = OperandResolver.coerceValueToDouble(vA); | |||
double d1 = OperandResolver.coerceValueToDouble(vB); | |||
double result = evaluate(d0, d1); | |||
return new NumberEval(result); | |||
} catch (EvaluationException e){ | |||
return e.getErrorEval(); | |||
} | |||
}); | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { |
@@ -17,13 +17,14 @@ | |||
package org.apache.poi.ss.formula.eval; | |||
import org.apache.poi.ss.formula.functions.ArrayFunction; | |||
import org.apache.poi.ss.formula.functions.Fixed1ArgFunction; | |||
import org.apache.poi.ss.formula.functions.Function; | |||
/** | |||
* @author Amol S. Deshmukh < amolweb at ya hoo dot com > | |||
*/ | |||
public final class UnaryMinusEval extends Fixed1ArgFunction { | |||
public final class UnaryMinusEval extends Fixed1ArgFunction implements ArrayFunction { | |||
public static final Function instance = new UnaryMinusEval(); | |||
@@ -44,4 +45,12 @@ public final class UnaryMinusEval extends Fixed1ArgFunction { | |||
} | |||
return new NumberEval(-d); | |||
} | |||
@Override | |||
public ValueEval evaluateArray(ValueEval[] args, int srcRowIndex, int srcColumnIndex){ | |||
return evaluateOneArrayArg(args, srcRowIndex, srcColumnIndex, (valA) -> | |||
evaluate(srcRowIndex, srcColumnIndex, valA) | |||
); | |||
} | |||
} |
@@ -17,6 +17,7 @@ | |||
package org.apache.poi.ss.formula.eval; | |||
import org.apache.poi.ss.formula.functions.ArrayFunction; | |||
import org.apache.poi.ss.formula.functions.Fixed1ArgFunction; | |||
import org.apache.poi.ss.formula.functions.Function; | |||
@@ -24,7 +25,7 @@ import org.apache.poi.ss.formula.functions.Function; | |||
/** | |||
* @author Amol S. Deshmukh < amolweb at ya hoo dot com > | |||
*/ | |||
public final class UnaryPlusEval extends Fixed1ArgFunction { | |||
public final class UnaryPlusEval extends Fixed1ArgFunction implements ArrayFunction { | |||
public static final Function instance = new UnaryPlusEval(); | |||
@@ -48,4 +49,12 @@ public final class UnaryPlusEval extends Fixed1ArgFunction { | |||
} | |||
return new NumberEval(+d); | |||
} | |||
@Override | |||
public ValueEval evaluateArray(ValueEval[] args, int srcRowIndex, int srcColumnIndex){ | |||
return evaluateOneArrayArg(args, srcRowIndex, srcColumnIndex, (valA) -> | |||
evaluate(srcRowIndex, srcColumnIndex, valA) | |||
); | |||
} | |||
} |
@@ -17,10 +17,11 @@ | |||
package org.apache.poi.ss.formula.functions; | |||
import org.apache.poi.ss.formula.eval.BlankEval; | |||
import org.apache.poi.ss.formula.eval.ErrorEval; | |||
import org.apache.poi.ss.formula.eval.MissingArgEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.ss.formula.CacheAreaEval; | |||
import org.apache.poi.ss.formula.FormulaParseException; | |||
import org.apache.poi.ss.formula.eval.*; | |||
import java.util.function.BiFunction; | |||
/** | |||
* @author Robert Hulbert | |||
@@ -41,4 +42,153 @@ public interface ArrayFunction { | |||
*/ | |||
ValueEval evaluateArray(ValueEval[] args, int srcRowIndex, int srcColumnIndex); | |||
/** | |||
* Evaluate an array function with two arguments. | |||
* | |||
* @param arg0 the first function argument. Empty values are represented with | |||
* {@link BlankEval} or {@link MissingArgEval}, never <code>null</code> | |||
* @param arg1 the first function argument. Empty values are represented with | |||
* @link BlankEval} or {@link MissingArgEval}, never <code>null</code> | |||
* | |||
* @param srcRowIndex row index of the cell containing the formula under evaluation | |||
* @param srcColumnIndex column index of the cell containing the formula under evaluation | |||
* @return The evaluated result, possibly an {@link ErrorEval}, never <code>null</code>. | |||
* <b>Note</b> - Excel uses the error code <i>#NUM!</i> instead of IEEE <i>NaN</i>, so when | |||
* numeric functions evaluate to {@link Double#NaN} be sure to translate the result to {@link | |||
* ErrorEval#NUM_ERROR}. | |||
*/ | |||
default ValueEval evaluateTwoArrayArgs(ValueEval arg0, ValueEval arg1, int srcRowIndex, int srcColumnIndex, | |||
BiFunction<ValueEval, ValueEval, ValueEval> evalFunc) { | |||
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 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(); | |||
} | |||
if(vA instanceof ErrorEval){ | |||
vals[idx++] = vA; | |||
} else if (vB instanceof ErrorEval) { | |||
vals[idx++] = vB; | |||
} else { | |||
vals[idx++] = evalFunc.apply(vA, vB); | |||
} | |||
} | |||
} | |||
if (vals.length == 1) { | |||
return vals[0]; | |||
} | |||
return new CacheAreaEval(srcRowIndex, srcColumnIndex, srcRowIndex + height - 1, srcColumnIndex + width - 1, vals); | |||
} | |||
default ValueEval evaluateOneArrayArg(ValueEval[] args, 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) { | |||
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; | |||
} | |||
w2 = 1; | |||
h2 = 1; | |||
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(); | |||
} | |||
vals[idx++] = evalFunc.apply(vA); | |||
} | |||
} | |||
if (vals.length == 1) { | |||
return vals[0]; | |||
} | |||
return new CacheAreaEval(srcRowIndex, srcColumnIndex, srcRowIndex + height - 1, srcColumnIndex + width - 1, vals); | |||
} | |||
} |
@@ -17,12 +17,7 @@ | |||
package org.apache.poi.ss.formula.functions; | |||
import org.apache.poi.ss.formula.eval.BlankEval; | |||
import org.apache.poi.ss.formula.eval.BoolEval; | |||
import org.apache.poi.ss.formula.eval.EvaluationException; | |||
import org.apache.poi.ss.formula.eval.MissingArgEval; | |||
import org.apache.poi.ss.formula.eval.OperandResolver; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.ss.formula.eval.*; | |||
import org.apache.poi.ss.formula.ptg.Ptg; | |||
import org.apache.poi.ss.formula.ptg.RefPtg; | |||
@@ -36,8 +31,9 @@ import org.apache.poi.ss.formula.ptg.RefPtg; | |||
* See bug numbers #55324 and #55747 for the full details on this. | |||
* TODO Fix this... | |||
*/ | |||
public final class IfFunc extends Var2or3ArgFunction { | |||
public final class IfFunc extends Var2or3ArgFunction implements ArrayFunction { | |||
@Override | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { | |||
boolean b; | |||
try { | |||
@@ -54,6 +50,7 @@ public final class IfFunc extends Var2or3ArgFunction { | |||
return BoolEval.FALSE; | |||
} | |||
@Override | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||
ValueEval arg2) { | |||
boolean b; | |||
@@ -83,4 +80,29 @@ public final class IfFunc extends Var2or3ArgFunction { | |||
} | |||
return b.booleanValue(); | |||
} | |||
@Override | |||
public ValueEval evaluateArray(ValueEval[] args, int srcRowIndex, int srcColumnIndex) { | |||
ValueEval arg0 = args[0]; | |||
ValueEval arg1 = args[1]; | |||
return evaluateTwoArrayArgs(arg0, arg1, srcRowIndex, srcColumnIndex, | |||
(vA, vB) -> { | |||
Boolean b; | |||
try { | |||
b = OperandResolver.coerceValueToBoolean(vA, false); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
if (b != null && b) { | |||
if (vB == MissingArgEval.instance) { | |||
return BlankEval.instance; | |||
} | |||
return vB; | |||
} | |||
return BoolEval.FALSE; | |||
} | |||
); | |||
} | |||
} |
@@ -23,7 +23,7 @@ import org.apache.poi.ss.formula.eval.*; | |||
* Implementation of the various ISxxx Logical Functions, which | |||
* take a single expression argument, and return True or False. | |||
*/ | |||
public abstract class LogicalFunction extends Fixed1ArgFunction { | |||
public abstract class LogicalFunction extends Fixed1ArgFunction implements ArrayFunction{ | |||
@SuppressWarnings("unused") | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { | |||
@@ -41,6 +41,14 @@ public abstract class LogicalFunction extends Fixed1ArgFunction { | |||
return BoolEval.valueOf(evaluate(ve)); | |||
} | |||
@Override | |||
public ValueEval evaluateArray(ValueEval[] args, int srcRowIndex, int srcColumnIndex){ | |||
return evaluateOneArrayArg(args, srcRowIndex, srcColumnIndex, (valA) -> | |||
BoolEval.valueOf(evaluate(valA)) | |||
); | |||
} | |||
/** | |||
* @param arg any {@link ValueEval}, potentially {@link BlankEval} or {@link ErrorEval}. | |||
*/ |
@@ -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 IF() as loaded from a test data spreadsheet.<p> | |||
*/ | |||
public class TestIFFunctionFromSpreadsheet extends BaseTestFunctionsFromSpreadsheet { | |||
@Parameters(name="{0}") | |||
public static Collection<Object[]> data() throws Exception { | |||
return data(TestIFFunctionFromSpreadsheet.class, "IfFunctionTestCaseData.xls"); | |||
} | |||
} |
@@ -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 for logical ISxxx functions as loaded from a test data spreadsheet.<p> | |||
*/ | |||
public class TestLogicalFunctionsFromSpreadsheet extends BaseTestFunctionsFromSpreadsheet { | |||
@Parameters(name="{0}") | |||
public static Collection<Object[]> data() throws Exception { | |||
return data(TestLogicalFunctionsFromSpreadsheet.class, "LogicalFunctionsTestCaseData.xls"); | |||
} | |||
} |