Browse Source

[bug-65915] add test for 'OR' function

pull/1579/head
Antony Liu 2 months ago
parent
commit
4673ee6764
  1. 2
      main/SS/Formula/Atp/XLookupFunction.cs
  2. 2
      main/SS/Formula/Eval/RelationalOperationEval/RelationalOperationEval.cs
  3. 2
      main/SS/Formula/Eval/TwoOperandNumeric/TwoOperandNumericOperation.cs
  4. 240
      main/SS/Formula/Functions/ArrayFunction.cs
  5. 73
      main/SS/Formula/Functions/Boolean/BooleanFunction.cs
  6. 5
      main/SS/Formula/OperationEvaluatorFactory.cs
  7. 3
      main/SS/Formula/UserDefinedFunction.cs
  8. 153
      testcases/main/SS/Formula/Functions/TestOrFunction.cs

2
main/SS/Formula/Atp/XLookupFunction.cs

@ -6,7 +6,7 @@ using System.Text;
namespace NPOI.SS.Formula.Atp
{
class XLookupFunction : FreeRefFunction, ArrayFunction
class XLookupFunction : FreeRefFunction, IArrayFunction
{
public static FreeRefFunction instance = new XLookupFunction(ArgumentsEvaluator.instance);

2
main/SS/Formula/Eval/RelationalOperationEval/RelationalOperationEval.cs

@ -35,7 +35,7 @@ namespace NPOI.SS.Formula.Eval
* @author Amol S. Deshmukh < amolweb at ya hoo Dot com >
*
*/
public abstract class RelationalOperationEval : Fixed2ArgFunction, ArrayFunction
public abstract class RelationalOperationEval : Fixed2ArgFunction, IArrayFunction
{
private static int DoCompare(ValueEval va, ValueEval vb)
{

2
main/SS/Formula/Eval/TwoOperandNumeric/TwoOperandNumericOperation.cs

@ -4,7 +4,7 @@ using NPOI.SS.Formula.Functions;
namespace NPOI.SS.Formula.Eval
{
public abstract class TwoOperandNumericOperation : Fixed2ArgFunction, ArrayFunction
public abstract class TwoOperandNumericOperation : Fixed2ArgFunction, IArrayFunction
{
protected double SingleOperandEvaluate(ValueEval arg, int srcCellRow, int srcCellCol)
{

240
main/SS/Formula/Functions/ArrayFunction.cs

@ -1,11 +1,13 @@
using NPOI.SS.Formula.Eval;
using NPOI.SS.Formula;
using NPOI.SS.Formula.Eval;
using NPOI.Util;
using System;
using System.Collections.Generic;
using System.Text;
namespace NPOI.SS.Formula.Functions
{
public interface ArrayFunction
public interface IArrayFunction
{
/// <summary>
/// - Excel uses the error code #NUM! instead of IEEE NaN, so when numeric functions evaluate to Double#NaN be sure to translate the result to ErrorEval#NUM_ERROR.
@ -16,4 +18,238 @@ namespace NPOI.SS.Formula.Functions
/// <returns> The evaluated result, possibly an ErrorEval, never <code>null</code></returns>
ValueEval EvaluateArray(ValueEval[] args, int srcRowIndex, int srcColumnIndex);
}
public class ArrayFunction
{
/// <summary>
/// Evaluate an array function with two arguments.
/// </summary>
/// <param name="arg0">the first function argument. Empty values are represented with <see cref="BlankEval"/> or <see cref="MissingArgEval"/>, never <c>null</c></param>
/// <param name="arg1">the second function argument. Empty values are represented with <see cref="BlankEval"/> or <see cref="MissingArgEval"/>, never <c>null</c></param>
/// <param name="srcRowIndex">row index of the cell containing the formula under evaluation</param>
/// <param name="srcColumnIndex">column index of the cell containing the formula under evaluation</param>
/// <param name="evalFunc"></param>
/// <returns>The evaluated result, possibly an <see cref="ErrorEval"/>, never <c>null</c>.
/// <b>Note</b> - Excel uses the error code <i>#NUM!</i> instead of IEEE <i>NaN</i>, so when
/// numeric functions evaluate to <see cref="double.NaN"/> be sure to translate the result to
/// <see cref="ErrorEval.NUM_ERROR"/>. </returns>
public ValueEval EvaluateTwoArrayArgs(ValueEval arg0, ValueEval arg1, int srcRowIndex, int srcColumnIndex,
Func<ValueEval, ValueEval, ValueEval> evalFunc)
{
return _evaluateTwoArrayArgs(arg0, arg1, srcRowIndex, srcColumnIndex, evalFunc);
}
public ValueEval EvaluateOneArrayArg(ValueEval arg0, int srcRowIndex, int srcColumnIndex,
Func<ValueEval, ValueEval> evalFunc)
{
return _evaluateOneArrayArg(arg0, srcRowIndex, srcColumnIndex, evalFunc);
}
internal static ValueEval _evaluateTwoArrayArgs(ValueEval arg0, ValueEval arg1, int srcRowIndex, int srcColumnIndex,
Func<ValueEval, ValueEval, ValueEval> evalFunc)
{
int w1, w2, h1, h2;
int a1FirstCol = 0, a1FirstRow = 0;
if(arg0 is AreaEval)
{
AreaEval ae = (AreaEval)arg0;
w1 = ae.Width;
h1 = ae.Height;
a1FirstCol = ae.FirstColumn;
a1FirstRow = ae.FirstRow;
}
else if(arg0 is RefEval)
{
RefEval ref1 = (RefEval)arg0;
w1 = 1;
h1 = 1;
a1FirstCol = ref1.Column;
a1FirstRow = ref1.Row;
}
else
{
w1 = 1;
h1 = 1;
}
int a2FirstCol = 0, a2FirstRow = 0;
if(arg1 is AreaEval)
{
AreaEval ae = (AreaEval)arg1;
w2 = ae.Width;
h2 = ae.Height;
a2FirstCol = ae.FirstColumn;
a2FirstRow = ae.FirstRow;
}
else if(arg1 is RefEval)
{
RefEval ref1 = (RefEval)arg1;
w2 = 1;
h2 = 1;
a2FirstCol = ref1.Column;
a2FirstRow = ref1.Row;
}
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();
}
catch(RuntimeException e)
{
if(e.Message.StartsWith("Don't know how to evaluate name"))
{
vA = ErrorEval.NAME_INVALID;
}
else
{
throw;
}
}
ValueEval vB;
try
{
vB = OperandResolver.GetSingleValue(arg1, a2FirstRow + i, a2FirstCol + j);
}
catch(FormulaParseException e)
{
vB = ErrorEval.NAME_INVALID;
}
catch(EvaluationException e)
{
vB = e.GetErrorEval();
}
catch(RuntimeException e)
{
if(e.Message.StartsWith("Don't know how to evaluate name"))
{
vB = ErrorEval.NAME_INVALID;
}
else
{
throw;
}
}
if(vA is ErrorEval)
{
vals[idx++] = vA;
}
else if(vB is ErrorEval)
{
vals[idx++] = vB;
}
else
{
vals[idx++] = evalFunc.Invoke(vA, vB);
}
}
}
if(vals.Length == 1)
{
return vals[0];
}
return new CacheAreaEval(srcRowIndex, srcColumnIndex, srcRowIndex + height - 1, srcColumnIndex + width - 1, vals);
}
internal static ValueEval _evaluateOneArrayArg(ValueEval arg0, int srcRowIndex, int srcColumnIndex,
Func<ValueEval, ValueEval> evalFunc)
{
int w1, w2, h1, h2;
int a1FirstCol = 0, a1FirstRow = 0;
if(arg0 is AreaEval)
{
AreaEval ae = (AreaEval)arg0;
w1 = ae.Width;
h1 = ae.Height;
a1FirstCol = ae.FirstColumn;
a1FirstRow = ae.FirstRow;
}
else if(arg0 is RefEval)
{
RefEval ref1 = (RefEval)arg0;
w1 = 1;
h1 = 1;
a1FirstCol = ref1.Column;
a1FirstRow = ref1.Row;
}
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();
}
catch(RuntimeException e)
{
if(e.Message.StartsWith("Don't know how to evaluate name"))
{
vA = ErrorEval.NAME_INVALID;
}
else
{
throw;
}
}
vals[idx++] = evalFunc.Invoke(vA);
}
}
if(vals.Length == 1)
{
return vals[0];
}
return new CacheAreaEval(srcRowIndex, srcColumnIndex, srcRowIndex + height - 1, srcColumnIndex + width - 1, vals);
}
}
}

73
main/SS/Formula/Functions/Boolean/BooleanFunction.cs

@ -87,11 +87,7 @@ namespace NPOI.SS.Formula.Functions
}
continue;
}
//else if (arg is ValueEval)
//{
// ValueEval ve = (ValueEval)arg;
// tempVe = OperandResolver.CoerceValueToBoolean(ve, false);
//}
if (arg == MissingArgEval.instance)
{
tempVe = false; // missing parameters are treated as FALSE
@ -133,5 +129,72 @@ namespace NPOI.SS.Formula.Functions
}
return BoolEval.ValueOf(boolResult);
}
public static Function FALSE = new FALSEFunction();
public static Function TRUE = new TRUEFunction();
public static Function NOT = new NOTFunction();
public ValueEval EvaluateArray(ValueEval[] args, int srcRowIndex, int srcColumnIndex)
{
return Evaluate(args, srcRowIndex, srcColumnIndex);
}
public class FALSEFunction : Function
{
public ValueEval Evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex)
{
return EvaluateFalse(args, srcRowIndex, srcColumnIndex);
}
}
public class TRUEFunction : Function
{
public ValueEval Evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex)
{
return EvaluateTrue(args, srcRowIndex, srcColumnIndex);
}
}
public class NOTFunction : Function
{
public ValueEval Evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex)
{
return EvaluateNot(args, srcRowIndex, srcColumnIndex);
}
}
private static ValueEval EvaluateFalse(ValueEval[] args, int srcRowIndex, int srcColumnIndex)
{
return args.Length != 0 ? ErrorEval.VALUE_INVALID : BoolEval.FALSE;
}
private static ValueEval EvaluateTrue(ValueEval[] args, int srcRowIndex, int srcColumnIndex)
{
return args.Length != 0 ? ErrorEval.VALUE_INVALID : BoolEval.TRUE;
}
private static ValueEval EvaluateNot(ValueEval[] args, int srcRowIndex, int srcColumnIndex)
{
if (args.Length != 1)
{
return ErrorEval.VALUE_INVALID;
}
Func<ValueEval, ValueEval> notInner = (va) => {
try
{
ValueEval ve = OperandResolver.GetSingleValue(va, srcRowIndex, srcColumnIndex);
bool? b = OperandResolver.CoerceValueToBoolean(ve, false);
bool boolArgVal = b.HasValue && b.Value;
return BoolEval.ValueOf(!boolArgVal);
} catch (EvaluationException e) {
return e.GetErrorEval();
}
};
return ArrayFunction._evaluateOneArrayArg(args[0], srcRowIndex, srcColumnIndex, notInner);
}
}
}

5
main/SS/Formula/OperationEvaluatorFactory.cs

@ -116,8 +116,9 @@ namespace NPOI.SS.Formula
if (result != null)
{
if (result is ArrayFunction func)
if (result is IArrayFunction)
{
IArrayFunction func = (IArrayFunction)result;
ValueEval eval = EvaluateArrayFunction(func, args, ec);
if (eval != null)
{
@ -132,7 +133,7 @@ namespace NPOI.SS.Formula
}
throw new Exception("Unexpected operation ptg class (" + ptg.GetType().Name + ")");
}
public static ValueEval EvaluateArrayFunction(ArrayFunction func, ValueEval[] args,
public static ValueEval EvaluateArrayFunction(IArrayFunction func, ValueEval[] args,
OperationEvaluationContext ec)
{
IEvaluationSheet evalSheet = ec.GetWorkbook().GetSheet(ec.SheetIndex);

3
main/SS/Formula/UserDefinedFunction.cs

@ -41,7 +41,8 @@ namespace NPOI.SS.Formula
int nOutGoingArgs = nIncomingArgs - 1;
ValueEval[] outGoingArgs = new ValueEval[nOutGoingArgs];
Array.Copy(args, 1, outGoingArgs, 0, nOutGoingArgs);
if (targetFunc is ArrayFunction func) {
if (targetFunc is IArrayFunction) {
IArrayFunction func = (IArrayFunction)targetFunc;
ValueEval eval = OperationEvaluatorFactory.EvaluateArrayFunction(func, outGoingArgs, ec);
if (eval != null)
{

153
testcases/main/SS/Formula/Functions/TestOrFunction.cs

@ -0,0 +1,153 @@
/* ====================================================================
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.
==================================================================== */
namespace TestCases.SS.Formula.Functions
{
using NPOI.HSSF.UserModel;
using NPOI.SS.Formula;
using NPOI.SS.Util;
using NUnit.Framework;
using NUnit.Framework.Legacy;
using TestCases.SS.Util;
/// <summary>
/// Tests for <see cref="NPOI.SS.Formula.Functions.Or" />
/// </summary>
[TestFixture]
public class TestOrFunction
{
private static readonly OperationEvaluationContext ec = new OperationEvaluationContext(null, null, 0, 0, 0, null);
[Test]
public void TestMicrosoftExample0()
{
//https://support.microsoft.com/en-us/office/or-function-7d17ad14-8700-4281-b308-00b131e22af0
using(HSSFWorkbook wb = new HSSFWorkbook())
{
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
HSSFSheet sheet = wb.CreateSheet() as HSSFSheet;
HSSFRow row = sheet.CreateRow(0) as HSSFRow;
HSSFCell cell = row.CreateCell(0) as HSSFCell;
Utils.AssertBoolean(fe, cell, "OR(TRUE,TRUE)", true);
Utils.AssertBoolean(fe, cell, "OR(TRUE,FALSE)", true);
Utils.AssertBoolean(fe, cell, "OR(1=1,2=2,3=3)", true);
Utils.AssertBoolean(fe, cell, "OR(1=2,2=3,3=4)", false);
}
}
[Test]
public void TestMicrosoftExample1()
{
//https://support.microsoft.com/en-us/office/or-function-7d17ad14-8700-4281-b308-00b131e22af0
using(HSSFWorkbook wb = InitWorkbook1())
{
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
HSSFCell cell = wb.GetSheetAt(0).GetRow(0).CreateCell(100) as HSSFCell;
Utils.AssertBoolean(fe, cell, "OR(A2>1,A2<100)", true);
Utils.AssertDouble(fe, cell, "IF(OR(A2>1,A2<100),A3,\"The value is out of range\")", 100);
Utils.AssertString(fe, cell, "IF(OR(A2<0,A2>50),A2,\"The value is out of range\")", "The value is out of range");
}
}
[Test]
public void TestMicrosoftExample2()
{
//https://support.microsoft.com/en-us/office/or-function-7d17ad14-8700-4281-b308-00b131e22af0
using(HSSFWorkbook wb = InitWorkbook2())
{
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
HSSFCell cell = wb.GetSheetAt(0).GetRow(13).CreateCell(3) as HSSFCell;
Utils.AssertDouble(fe, cell, "IF(OR(B14>=$B$4,C14>=$B$5),B14*$B$6,0)", 314);
}
}
[Test]
public void TestBug65915()
{
//https://bz.apache.org/bugzilla/show_bug.cgi?id=65915
using(HSSFWorkbook wb = new HSSFWorkbook())
{
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
HSSFSheet sheet = wb.CreateSheet() as HSSFSheet;
HSSFRow row = sheet.CreateRow(0) as HSSFRow;
HSSFCell cell = row.CreateCell(0) as HSSFCell;
Utils.AssertDouble(fe, cell, "INDEX({1},1,IF(OR(FALSE,FALSE),1,1))", 1.0);
}
}
[Test]
public void TestBug65915ArrayFunction()
{
//https://bz.apache.org/bugzilla/show_bug.cgi?id=65915
using(HSSFWorkbook wb = new HSSFWorkbook())
{
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
HSSFSheet sheet = wb.CreateSheet() as HSSFSheet;
HSSFRow row = sheet.CreateRow(0) as HSSFRow;
HSSFCell cell = row.CreateCell(0) as HSSFCell;
sheet.SetArrayFormula("INDEX({1},1,IF(OR(FALSE,FALSE),0,1))", new CellRangeAddress(0, 0, 0, 0));
fe.EvaluateAll();
ClassicAssert.AreEqual(1.0, cell.NumericCellValue);
}
}
[Test]
public void TestBug65915Array3Function()
{
//https://bz.apache.org/bugzilla/show_bug.cgi?id=65915
using(HSSFWorkbook wb = new HSSFWorkbook())
{
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
HSSFSheet sheet = wb.CreateSheet() as HSSFSheet;
HSSFRow row = sheet.CreateRow(0) as HSSFRow;
HSSFCell cell = row.CreateCell(0) as HSSFCell;
sheet.SetArrayFormula("INDEX({1},1,IF(OR(1=2,2=3,3=4),0,1))", new CellRangeAddress(0, 0, 0, 0));
fe.EvaluateAll();
ClassicAssert.AreEqual(1.0, cell.NumericCellValue);
}
}
private static HSSFWorkbook InitWorkbook1()
{
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.CreateSheet() as HSSFSheet;
Utils.AddRow(sheet, 0, "Values");
Utils.AddRow(sheet, 1, 50);
Utils.AddRow(sheet, 2, 100);
return wb;
}
private static HSSFWorkbook InitWorkbook2()
{
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.CreateSheet() as HSSFSheet;
Utils.AddRow(sheet, 0, "Goals");
Utils.AddRow(sheet, 2, "Criteria", "Amount");
Utils.AddRow(sheet, 3, "Sales Goal", 8500);
Utils.AddRow(sheet, 4, "Account Goal", 5);
Utils.AddRow(sheet, 5, "Commission Rate", 0.02);
Utils.AddRow(sheet, 6, "Bonus Goal", 12500);
Utils.AddRow(sheet, 7, "Bonus %", 0.015);
Utils.AddRow(sheet, 9, "Commission Calculations");
Utils.AddRow(sheet, 11, "Salesperson", "Total Sales", "Accounts", "Commission", "Bonus");
Utils.AddRow(sheet, 12, "Millicent Shelton", 10260, 9);
Utils.AddRow(sheet, 13, "Miguel Ferrari", 15700, 7);
return wb;
}
}
}
Loading…
Cancel
Save