Browse Source

Add more test cases of DGet function.

Bug 63700: Make D* functions work with numeric result column
bug-66087: make D* functions case insensitive
github-692: D* functions are incompatible with the diamond operator.
pull/1504/head
Antony Liu 5 months ago
parent
commit
3a87c3ae0c
  1. 26
      main/SS/Formula/Functions/DGet.cs
  2. 148
      main/SS/Formula/Functions/DStarRunner.cs
  3. 170
      testcases/main/SS/Formula/Functions/TestDGet.cs

26
main/SS/Formula/Functions/DGet.cs

@ -33,14 +33,25 @@ namespace NPOI.SS.Formula.Functions
public bool ProcessMatch(ValueEval eval)
{
if (result == null) // First match, just Set the value.
if(result == null) // First match, just Set the value.
{
result = eval;
}
else // There was a previous match, since there is only exactly one allowed, bail out1.
{
result = ErrorEval.NUM_ERROR;
return false;
if(result is BlankEval)
{
result = eval;
}
else
{
// We have a previous filled result.
if(!(eval is BlankEval))
{
result = ErrorEval.NUM_ERROR;
return false;
}
}
}
return true;
@ -50,17 +61,18 @@ namespace NPOI.SS.Formula.Functions
{
get
{
if (result == null)
if(result == null)
{
return ErrorEval.VALUE_INVALID;
}
if (result is BlankEval) {
if(result is BlankEval)
{
return ErrorEval.VALUE_INVALID;
}
else
try
{
if (OperandResolver.CoerceValueToString(OperandResolver.GetSingleValue(result, 0, 0)).Equals(""))
if(OperandResolver.CoerceValueToString(OperandResolver.GetSingleValue(result, 0, 0)).Equals(""))
{
return ErrorEval.VALUE_INVALID;
}
@ -69,7 +81,7 @@ namespace NPOI.SS.Formula.Functions
return result;
}
}
catch (EvaluationException e)
catch(EvaluationException e)
{
return e.GetErrorEval();
}

148
main/SS/Formula/Functions/DStarRunner.cs

@ -18,7 +18,7 @@
namespace NPOI.SS.Formula.Functions
{
using System;
using System.Text.RegularExpressions;
using NPOI.SS.Formula;
using NPOI.SS.Formula.Eval;
using NPOI.SS.Util;
@ -69,6 +69,20 @@ namespace NPOI.SS.Formula.Functions
AreaEval db = (AreaEval)database;
AreaEval cdb = (AreaEval)conditionDatabase;
// Create an algorithm runner.
IDStarAlgorithm algorithm = null;
switch(algoType)
{
case DStarAlgorithmEnum.DGET:
algorithm = new DGet();
break;
case DStarAlgorithmEnum.DMIN:
algorithm = new DMin();
break;
default:
throw new InvalidOperationException("Unexpected algorithm type " + algoType + " encountered.");
}
try
{
filterColumn = OperandResolver.GetSingleValue(filterColumn, srcRowIndex, srcColumnIndex);
@ -81,26 +95,24 @@ namespace NPOI.SS.Formula.Functions
int fc;
try
{
fc = GetColumnForName(filterColumn, db);
if(filterColumn is NumericValueEval)
{
//fc is zero based while Excel uses 1 based column numbering
fc = (int) Math.Round(((NumericValueEval) filterColumn).NumberValue) - 1;
}
else
fc = GetColumnForName(filterColumn, db);
}
catch (EvaluationException)
{
return ErrorEval.VALUE_INVALID;
}
if (fc == -1)
if (fc == -1 && !algorithm.AllowEmptyMatchField)
{ // column not found
return ErrorEval.VALUE_INVALID;
}
// Create an algorithm runner.
IDStarAlgorithm algorithm = null;
switch (algoType)
{
case DStarAlgorithmEnum.DGET: algorithm = new DGet(); break;
case DStarAlgorithmEnum.DMIN: algorithm = new DMin(); break;
default:
throw new InvalidOperationException("Unexpected algorithm type " + algoType + " encountered.");
}
// Iterate over all db entries.
int height = db.Height;
@ -119,6 +131,10 @@ namespace NPOI.SS.Formula.Functions
if (matches)
{
ValueEval currentValueEval = ResolveReference(db, row, fc);
if(fc < 0 && algorithm.AllowEmptyMatchField && !(currentValueEval is NumericValueEval))
{
currentValueEval = NumberEval.ZERO;
}
// Pass the match to the algorithm and conditionally abort the search.
bool shouldContinue = algorithm.ProcessMatch(currentValueEval);
if (!shouldContinue)
@ -138,7 +154,8 @@ namespace NPOI.SS.Formula.Functions
largerEqualThan,
smallerThan,
smallerEqualThan,
equal
equal,
notEqual,
}
/**
@ -179,7 +196,7 @@ namespace NPOI.SS.Formula.Functions
continue;
}
String columnName = OperandResolver.CoerceValueToString(columnNameValueEval);
if (name.Equals(columnName))
if (name.Equals(columnName, StringComparison.OrdinalIgnoreCase))
{
resultColumn = column;
break;
@ -278,11 +295,24 @@ namespace NPOI.SS.Formula.Functions
if (conditionString.StartsWith("<"))
{ // It's a </<= condition.
String number = conditionString.Substring(1);
if (number.StartsWith("="))
if(number.StartsWith("="))
{
number = number.Substring(1);
return testNumericCondition(value, Operator.smallerEqualThan, number);
}
else if(number.StartsWith(">"))
{
number = number.Substring(1);
bool itsANumber = IsNumber(number);
if(itsANumber)
{
return testNumericCondition(value, Operator.notEqual, number);
}
else
{
return testStringCondition(value, Operator.notEqual, number);
}
}
else
{
return testNumericCondition(value, Operator.smallerThan, number);
@ -310,32 +340,14 @@ namespace NPOI.SS.Formula.Functions
return value is BlankEval;
}
// Distinguish between string and number.
bool itsANumber = false;
try
{
int.Parse(stringOrNumber);
itsANumber = true;
}
catch (FormatException)
{ // It's not an int.
try
{
Double.Parse(stringOrNumber);
itsANumber = true;
}
catch (FormatException)
{ // It's a string.
itsANumber = false;
}
}
bool itsANumber = IsNumber(stringOrNumber);
if (itsANumber)
{
return testNumericCondition(value, Operator.equal, stringOrNumber);
}
else
{ // It's a string.
String valueString = value is BlankEval ? "" : OperandResolver.CoerceValueToString(value);
return stringOrNumber.Equals(valueString);
return testStringCondition(value, Operator.equal, stringOrNumber);
}
}
else
@ -347,7 +359,17 @@ namespace NPOI.SS.Formula.Functions
else
{
String valueString = value is BlankEval ? "" : OperandResolver.CoerceValueToString(value);
return valueString.StartsWith(conditionString);
String lowerValue = valueString.ToLower(LocaleUtil.GetUserLocale());
String lowerCondition = conditionString.ToLower(LocaleUtil.GetUserLocale());
Regex pattern = Countif.StringMatcher.GetWildCardPattern(lowerCondition);
if(pattern == null)
{
return lowerValue.StartsWith(lowerCondition);
}
else
{
return pattern.IsMatch(lowerValue);
}
}
}
}
@ -423,6 +445,30 @@ namespace NPOI.SS.Formula.Functions
return result <= 0;
case Operator.equal:
return result == 0;
case Operator.notEqual:
return result != 0;
}
return false; // Can not be reached.
}
/**
* Test whether a value matches a text condition.
* @param valueEval Value to check.
* @param op Comparator to use.
* @param condition Value to check against.
* @return whether the condition holds.
*/
private static bool testStringCondition(
ValueEval valueEval, Operator op, String condition)
{
String valueString = valueEval is BlankEval ? "" : OperandResolver.CoerceValueToString(valueEval);
switch(op)
{
case Operator.equal:
return valueString.Equals(condition, StringComparison.OrdinalIgnoreCase);
case Operator.notEqual:
return !valueString.Equals(condition, StringComparison.OrdinalIgnoreCase);
}
return false; // Can not be reached.
}
@ -469,6 +515,36 @@ namespace NPOI.SS.Formula.Functions
return e.GetErrorEval();
}
}
/**
* Determines whether a given string represents a valid number.
*
* @param value The string to be checked if it represents a number.
* @return {@code true} if the string can be parsed as either an integer or
* a double; {@code false} otherwise.
*/
private static bool IsNumber(String value)
{
bool itsANumber;
try
{
int.Parse(value);
itsANumber = true;
}
catch(FormatException)
{ // It's not an int.
try
{
double.Parse(value);
itsANumber = true;
}
catch(FormatException)
{ // It's a string.
itsANumber = false;
}
}
return itsANumber;
}
}
}

170
testcases/main/SS/Formula/Functions/TestDGet.cs

@ -0,0 +1,170 @@

/* ====================================================================
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.UserModel;
using NUnit.Framework;
using TestCases.SS.Util;
/// <summary>
/// Testcase for function DGET()
/// </summary>
[TestFixture]
public class TestDGet
{
//https://support.microsoft.com/en-us/office/dget-function-455568bf-4eef-45f7-90f0-ec250d00892e
[Test]
public void TestMicrosoftExample1()
{
using(HSSFWorkbook wb = initWorkbook1(false, "=Apple"))
{
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
HSSFCell cell = wb.GetSheetAt(0).GetRow(0).CreateCell(100) as HSSFCell;
Utils.AssertError(fe, cell, "DGET(A5:E11, \"Yield\", A1:A3)", FormulaError.NUM);
Utils.AssertDouble(fe, cell, "DGET(A5:E11, \"Yield\", A1:F3)", 10);
Utils.AssertDouble(fe, cell, "DGET(A5:E11, 4, A1:F3)", 10);
}
}
[Test]
public void TestMicrosoftExample1CaseInsensitive()
{
using(HSSFWorkbook wb = initWorkbook1(false, "=apple"))
{
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
HSSFCell cell = wb.GetSheetAt(0).GetRow(0).CreateCell(100) as HSSFCell;
Utils.AssertError(fe, cell, "DGET(A5:E11, \"Yield\", A1:A3)", FormulaError.NUM);
Utils.AssertDouble(fe, cell, "DGET(A5:E11, \"Yield\", A1:F3)", 10);
Utils.AssertDouble(fe, cell, "DGET(A5:E11, 4, A1:F3)", 10);
}
}
[Test]
public void TestMicrosoftExample1StartsWith()
{
using(HSSFWorkbook wb = initWorkbook1(false, "App"))
{
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
HSSFCell cell = wb.GetSheetAt(0).GetRow(0).CreateCell(100) as HSSFCell;
Utils.AssertError(fe, cell, "DGET(A5:E11, \"Yield\", A1:A3)", FormulaError.NUM);
Utils.AssertDouble(fe, cell, "DGET(A5:E11, \"Yield\", A1:F3)", 10);
Utils.AssertDouble(fe, cell, "DGET(A5:E11, 4, A1:F3)", 10);
}
}
[Test]
public void TestMicrosoftExample1StartsWithLowercase()
{
using(HSSFWorkbook wb = initWorkbook1(false, "app"))
{
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
HSSFCell cell = wb.GetSheetAt(0).GetRow(0).CreateCell(100) as HSSFCell;
Utils.AssertError(fe, cell, "DGET(A5:E11, \"Yield\", A1:A3)", FormulaError.NUM);
Utils.AssertDouble(fe, cell, "DGET(A5:E11, \"Yield\", A1:F3)", 10);
Utils.AssertDouble(fe, cell, "DGET(A5:E11, 4, A1:F3)", 10);
}
}
[Test]
public void TestMicrosoftExample1Wildcard()
{
using(HSSFWorkbook wb = initWorkbook1(false, "A*le"))
{
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
HSSFCell cell = wb.GetSheetAt(0).GetRow(0).CreateCell(100) as HSSFCell;
Utils.AssertError(fe, cell, "DGET(A5:E11, \"Yield\", A1:A3)", FormulaError.NUM);
Utils.AssertDouble(fe, cell, "DGET(A5:E11, \"Yield\", A1:F3)", 10);
Utils.AssertDouble(fe, cell, "DGET(A5:E11, 4, A1:F3)", 10);
}
}
[Test]
public void TestMicrosoftExample1WildcardLowercase()
{
using(HSSFWorkbook wb = initWorkbook1(false, "a*le"))
{
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
HSSFCell cell = wb.GetSheetAt(0).GetRow(0).CreateCell(100) as HSSFCell;
Utils.AssertError(fe, cell, "DGET(A5:E11, \"Yield\", A1:A3)", FormulaError.NUM);
Utils.AssertDouble(fe, cell, "DGET(A5:E11, \"Yield\", A1:F3)", 10);
Utils.AssertDouble(fe, cell, "DGET(A5:E11, 4, A1:F3)", 10);
}
}
[Test]
public void TestMicrosoftExample1AppleWildcardNoMatch()
{
using(HSSFWorkbook wb = initWorkbook1(false, "A*x"))
{
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
HSSFCell cell = wb.GetSheetAt(0).GetRow(0).CreateCell(100) as HSSFCell;
Utils.AssertError(fe, cell, "DGET(A5:E11, \"Yield\", A1:A3)", FormulaError.NUM);
Utils.AssertError(fe, cell, "DGET(A5:E11, \"Yield\", A1:F3)", FormulaError.VALUE);
}
}
[Test]
public void TestMicrosoftExample1Variant()
{
using(HSSFWorkbook wb = initWorkbook1(true, "=Apple"))
{
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
HSSFCell cell = wb.GetSheetAt(0).GetRow(0).CreateCell(100) as HSSFCell;
Utils.AssertDouble(fe, cell, "DGET(A5:E11, \"Yield\", A1:F3)", 6);
Utils.AssertDouble(fe, cell, "DGET(A5:E11, 4, A1:F3)", 6);
}
}
private HSSFWorkbook initWorkbook1(bool adjustAppleCondition, string appleCondition)
{
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.CreateSheet() as HSSFSheet;
Utils.AddRow(sheet, 0, "Tree", "Height", "Age", "Yield", "Profit", "Height");
if(adjustAppleCondition)
{
Utils.AddRow(sheet, 1, appleCondition, ">=8", null, null, null, "<12");
}
else
{
Utils.AddRow(sheet, 1, appleCondition, ">10", null, null, null, "<16");
}
Utils.AddRow(sheet, 2, "Pear", ">12");
Utils.AddRow(sheet, 3);
Utils.AddRow(sheet, 4, "Tree", "Height", "Age", "Yield", "Profit");
Utils.AddRow(sheet, 5, "Apple", 18, 20, 14, 105);
Utils.AddRow(sheet, 6, "Pear", 12, 12, 10, 96);
Utils.AddRow(sheet, 7, "Cherry", 13, 14, 9, 105);
Utils.AddRow(sheet, 8, "Apple", 14, null, 10, 75);
Utils.AddRow(sheet, 9, "Pear", 9, 8, 8, 77);
Utils.AddRow(sheet, 10, "Apple", 8, 9, 6, 45);
return wb;
}
}
}
Loading…
Cancel
Save