From 6852504558f0808c0b12b193753f21c22f6f5dd8 Mon Sep 17 00:00:00 2001 From: Tony Qu Date: Thu, 21 Apr 2022 06:15:53 +0800 Subject: [PATCH] implement XMatch function --- main/SS/Formula/Atp/AnalysisToolPak.cs | 1 + main/SS/Formula/Atp/XMatchFunction.cs | 99 +++++++++++++++ .../main/SS/Formula/Atp/TestXMatchFunction.cs | 119 ++++++++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 main/SS/Formula/Atp/XMatchFunction.cs create mode 100644 testcases/main/SS/Formula/Atp/TestXMatchFunction.cs diff --git a/main/SS/Formula/Atp/AnalysisToolPak.cs b/main/SS/Formula/Atp/AnalysisToolPak.cs index 8f3510e39..e4c3744cd 100644 --- a/main/SS/Formula/Atp/AnalysisToolPak.cs +++ b/main/SS/Formula/Atp/AnalysisToolPak.cs @@ -180,6 +180,7 @@ namespace NPOI.SS.Formula.Atp { r(m, "WORKDAY", WorkdayFunction.instance); r(m, "XIRR", null); r(m, "XLOOKUP", XLookupFunction.instance); + r(m, "XMATCH", XMatchFunction.instance); r(m, "XNPV", null); r(m, "YEARFRAC", YearFrac.instance); r(m, "YIELD", null); diff --git a/main/SS/Formula/Atp/XMatchFunction.cs b/main/SS/Formula/Atp/XMatchFunction.cs new file mode 100644 index 000000000..431542b36 --- /dev/null +++ b/main/SS/Formula/Atp/XMatchFunction.cs @@ -0,0 +1,99 @@ +using NPOI.SS.Formula.Eval; +using NPOI.SS.Formula.Functions; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NPOI.SS.Formula.Atp +{ + public class XMatchFunction : FreeRefFunction + { + + public static FreeRefFunction instance = new XMatchFunction(ArgumentsEvaluator.instance); + + private ArgumentsEvaluator evaluator; + + private XMatchFunction(ArgumentsEvaluator anEvaluator) + { + // enforces singleton + this.evaluator = anEvaluator; + } + public ValueEval Evaluate(ValueEval[] args, OperationEvaluationContext ec) + { + int srcRowIndex = ec.RowIndex; + int srcColumnIndex = ec.ColumnIndex; + return _evaluate(args, srcRowIndex, srcColumnIndex); + } + + private ValueEval _evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) + { + if (args.Length < 2) + { + return ErrorEval.VALUE_INVALID; + } + LookupUtils.MatchMode matchMode = LookupUtils.MatchMode.ExactMatch; + if (args.Length > 2) + { + try + { + ValueEval matchModeValue = OperandResolver.GetSingleValue(args[2], srcRowIndex, srcColumnIndex); + int matchInt = OperandResolver.CoerceValueToInt(matchModeValue); + matchMode = LookupUtils.GetMatchMode(matchInt); + } + catch (EvaluationException e) + { + return e.GetErrorEval(); + } + catch (Exception e) + { + return ErrorEval.VALUE_INVALID; + } + } + LookupUtils.SearchMode searchMode = LookupUtils.SearchMode.IterateForward; + if (args.Length > 3) + { + try + { + ValueEval searchModeValue = OperandResolver.GetSingleValue(args[3], srcRowIndex, srcColumnIndex); + int searchInt = OperandResolver.CoerceValueToInt(searchModeValue); + searchMode = LookupUtils.GetSearchMode(searchInt); + } + catch (EvaluationException e) + { + return e.GetErrorEval(); + } + catch (Exception e) + { + return ErrorEval.VALUE_INVALID; + } + } + return evaluate(srcRowIndex, srcColumnIndex, args[0], args[1], matchMode, searchMode); + } + + private ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval lookupEval, ValueEval indexEval, + LookupUtils.MatchMode matchMode, LookupUtils.SearchMode searchMode) + { + try + { + ValueEval lookupValue = OperandResolver.GetSingleValue(lookupEval, srcRowIndex, srcColumnIndex); + TwoDEval tableArray = LookupUtils.ResolveTableArrayArg(indexEval); + ValueVector vector; + if (tableArray.IsColumn) + { + vector = LookupUtils.CreateColumnVector(tableArray, 0); + } + else + { + vector = LookupUtils.CreateRowVector(tableArray, 0); + } + int matchedIdx = LookupUtils.XlookupIndexOfValue(lookupValue, vector, matchMode, searchMode); + return new NumberEval((double)matchedIdx + 1); + } + catch (EvaluationException e) + { + return e.GetErrorEval(); + } + } + } +} + diff --git a/testcases/main/SS/Formula/Atp/TestXMatchFunction.cs b/testcases/main/SS/Formula/Atp/TestXMatchFunction.cs new file mode 100644 index 000000000..da5e60ca4 --- /dev/null +++ b/testcases/main/SS/Formula/Atp/TestXMatchFunction.cs @@ -0,0 +1,119 @@ +using NPOI.HSSF.UserModel; +using NPOI.SS.UserModel; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Text; + +namespace TestCases.SS.Formula.Atp +{ + [TestFixture] + public class TestXMatchFunction + { + [Test] + public void TestMicrosoftExample0() + { + HSSFWorkbook wb = initNumWorkbook("Grape"); + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); + ICell cell = wb.GetSheetAt(0).GetRow(2).CreateCell(5); + Util.Utils.AssertDouble(fe, cell, "XMATCH(E3,C3:C7)", 2); + Util.Utils.AssertError(fe, cell, "XMATCH(\"Gra\",C3:C7)", FormulaError.NA); + } + + [Test] + public void TestMicrosoftExample1() + { + HSSFWorkbook wb = initNumWorkbook("Gra?"); + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); + ICell cell = wb.GetSheetAt(0).GetRow(2).CreateCell(5); + Util.Utils.AssertDouble(fe, cell, "XMATCH(E3,C3:C7,1)", 2); + Util.Utils.AssertDouble(fe, cell, "XMATCH(E3,C3:C7,-1)", 5); + Util.Utils.AssertDouble(fe, cell, "XMATCH(\"Gra\",C3:C7,1)", 2); + Util.Utils.AssertDouble(fe, cell, "XMATCH(\"Graz\",C3:C7,1)", 3); + Util.Utils.AssertDouble(fe, cell, "XMATCH(\"Graz\",C3:C7,-1)", 2); + } + + [Test] + public void TestMicrosoftExample2() + { + //the result in this example is correct but the description seems wrong from my testing + //the result is based on the position and not a count + HSSFWorkbook wb = initWorkbook2(); + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); + ICell cell = wb.GetSheetAt(0).GetRow(3).CreateCell(5); + Util.Utils.AssertDouble(fe, cell, "XMATCH(F2,C3:C9,1)", 4); + Util.Utils.AssertDouble(fe, cell, "XMATCH(F2,C3:C9,-1)", 5); + Util.Utils.AssertError(fe, cell, "XMATCH(F2,C3:C9,2)", FormulaError.NA); + Util.Utils.AssertDouble(fe, cell, "XMATCH(35000,C3:C9,1)", 2); + Util.Utils.AssertDouble(fe, cell, "XMATCH(36000,C3:C9,1)", 1); + } + + [Test] + public void TestMicrosoftExample3() + { + HSSFWorkbook wb = initWorkbook3(); + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); + ICell cell = wb.GetSheetAt(0).GetRow(2).CreateCell(3); + Util.Utils.AssertDouble(fe, cell, "INDEX(C6:E12,XMATCH(B3,B6:B12),XMATCH(C3,C5:E5))", 8492); + + } + + [Test] + public void TestMicrosoftExample4() + { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); + ICell cell = wb.CreateSheet().CreateRow(0).CreateCell(0); + Util.Utils.AssertDouble(fe, cell, "XMATCH(4,{5,4,3,2,1})", 2); + Util.Utils.AssertDouble(fe, cell, "XMATCH(4.5,{5,4,3,2,1},1)", 1); + } + private HSSFWorkbook initNumWorkbook(String lookup) + { + HSSFWorkbook wb = new HSSFWorkbook(); + ISheet sheet = wb.CreateSheet(); + SS.Util.Utils.AddRow(sheet, 0); + SS.Util.Utils.AddRow(sheet, 1, null, null, "Product", null, "Product", "Position"); + SS.Util.Utils.AddRow(sheet, 2, null, null, "Apple", null, lookup); + SS.Util.Utils.AddRow(sheet, 3, null, null, "Grape"); + SS.Util.Utils.AddRow(sheet, 4, null, null, "Pear"); + SS.Util.Utils.AddRow(sheet, 5, null, null, "Banana"); + SS.Util.Utils.AddRow(sheet, 6, null, null, "Cherry"); + return wb; + } + + private HSSFWorkbook initWorkbook2() + { + HSSFWorkbook wb = new HSSFWorkbook(); + ISheet sheet = wb.CreateSheet(); + SS.Util.Utils.AddRow(sheet, 0); + SS.Util.Utils.AddRow(sheet, 1, null, "Sales Rep", "Total Sales", null, "Bonus", 15000); + SS.Util.Utils.AddRow(sheet, 2, null, "Michael Neipper", 42000); + SS.Util.Utils.AddRow(sheet, 3, null, "Jan Kotas", 35000); + SS.Util.Utils.AddRow(sheet, 4, null, "Nancy Freehafer", 25000); + SS.Util.Utils.AddRow(sheet, 5, null, "Andrew Cencini", 15901); + SS.Util.Utils.AddRow(sheet, 6, null, "Anne Hellung-Larsen", 13801); + SS.Util.Utils.AddRow(sheet, 7, null, "Nancy Freehafer", 12181); + SS.Util.Utils.AddRow(sheet, 8, null, "Mariya Sergienko", 9201); + return wb; + } + + private HSSFWorkbook initWorkbook3() + { + HSSFWorkbook wb = new HSSFWorkbook(); + ISheet sheet = wb.CreateSheet(); + SS.Util.Utils.AddRow(sheet, 0); + SS.Util.Utils.AddRow(sheet, 1, null, "Sales Rep", "Month", "Total"); + SS.Util.Utils.AddRow(sheet, 2, null, "Andrew Cencini", "Feb"); + SS.Util.Utils.AddRow(sheet, 3); + SS.Util.Utils.AddRow(sheet, 4, null, "Sales Rep", "Jan", "Feb", "Mar"); + SS.Util.Utils.AddRow(sheet, 5, null, "Michael Neipper", 3174, 6804, 4713); + SS.Util.Utils.AddRow(sheet, 6, null, "Jan Kotas", 1656, 8643, 3445); + SS.Util.Utils.AddRow(sheet, 7, null, "Nancy Freehafer", 2706, 2310, 6606); + SS.Util.Utils.AddRow(sheet, 8, null, "Andrew Cencini", 4930, 8492, 4474); + SS.Util.Utils.AddRow(sheet, 9, null, "Anne Hellung-Larsen", 6394, 9846, 4368); + SS.Util.Utils.AddRow(sheet, 10, null, "Nancy Freehafer", 2539, 8996, 4084); + SS.Util.Utils.AddRow(sheet, 11, null, "Mariya Sergienko", 4468, 5206, 7343); + return wb; + } + } +} \ No newline at end of file