diff --git a/main/SS/Formula/Atp/XLookupFunction.cs b/main/SS/Formula/Atp/XLookupFunction.cs index f064114f9..0e0284ae4 100644 --- a/main/SS/Formula/Atp/XLookupFunction.cs +++ b/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); diff --git a/main/SS/Formula/Eval/RelationalOperationEval/RelationalOperationEval.cs b/main/SS/Formula/Eval/RelationalOperationEval/RelationalOperationEval.cs index 6ce1ed892..c6959402c 100644 --- a/main/SS/Formula/Eval/RelationalOperationEval/RelationalOperationEval.cs +++ b/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) { diff --git a/main/SS/Formula/Eval/TwoOperandNumeric/TwoOperandNumericOperation.cs b/main/SS/Formula/Eval/TwoOperandNumeric/TwoOperandNumericOperation.cs index 62a7a79ed..779fbbc07 100644 --- a/main/SS/Formula/Eval/TwoOperandNumeric/TwoOperandNumericOperation.cs +++ b/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) { diff --git a/main/SS/Formula/Functions/ArrayFunction.cs b/main/SS/Formula/Functions/ArrayFunction.cs index f6f1aaff9..c601104ef 100644 --- a/main/SS/Formula/Functions/ArrayFunction.cs +++ b/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 { /// /// - 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 /// The evaluated result, possibly an ErrorEval, never null ValueEval EvaluateArray(ValueEval[] args, int srcRowIndex, int srcColumnIndex); } + + public class ArrayFunction + { + /// + /// Evaluate an array function with two arguments. + /// + /// the first function argument. Empty values are represented with or , never null + /// the second function argument. Empty values are represented with or , never null + /// row index of the cell containing the formula under evaluation + /// column index of the cell containing the formula under evaluation + /// + /// The evaluated result, possibly an , never null. + /// Note - Excel uses the error code #NUM! instead of IEEE NaN, so when + /// numeric functions evaluate to be sure to translate the result to + /// . + public ValueEval EvaluateTwoArrayArgs(ValueEval arg0, ValueEval arg1, int srcRowIndex, int srcColumnIndex, + Func evalFunc) + { + return _evaluateTwoArrayArgs(arg0, arg1, srcRowIndex, srcColumnIndex, evalFunc); + } + + public ValueEval EvaluateOneArrayArg(ValueEval arg0, int srcRowIndex, int srcColumnIndex, + Func evalFunc) + { + return _evaluateOneArrayArg(arg0, srcRowIndex, srcColumnIndex, evalFunc); + } + + internal static ValueEval _evaluateTwoArrayArgs(ValueEval arg0, ValueEval arg1, int srcRowIndex, int srcColumnIndex, + Func 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 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); + } + } } diff --git a/main/SS/Formula/Functions/Boolean/BooleanFunction.cs b/main/SS/Formula/Functions/Boolean/BooleanFunction.cs index 18956cd7d..bd0512575 100644 --- a/main/SS/Formula/Functions/Boolean/BooleanFunction.cs +++ b/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 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); + } } } \ No newline at end of file diff --git a/main/SS/Formula/OperationEvaluatorFactory.cs b/main/SS/Formula/OperationEvaluatorFactory.cs index e532f5d86..509983a23 100644 --- a/main/SS/Formula/OperationEvaluatorFactory.cs +++ b/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); diff --git a/main/SS/Formula/UserDefinedFunction.cs b/main/SS/Formula/UserDefinedFunction.cs index 8d1c08a4c..c64ae8bdb 100644 --- a/main/SS/Formula/UserDefinedFunction.cs +++ b/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) { diff --git a/testcases/main/SS/Formula/Functions/TestOrFunction.cs b/testcases/main/SS/Formula/Functions/TestOrFunction.cs new file mode 100644 index 000000000..bd6addf14 --- /dev/null +++ b/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; + + /// + /// Tests for + /// + [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; + } + } +} \ No newline at end of file