Convert all relevant Logical functions to support array arguments (#2600)

This commit is contained in:
Mark Baker 2022-02-18 02:56:23 +01:00 committed by GitHub
parent e580f10c46
commit 0371ccb686
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 162 additions and 10 deletions

View File

@ -163,7 +163,7 @@ class Logical
*
* @param mixed $logical A value or expression that can be evaluated to TRUE or FALSE
*
* @return bool|string the boolean inverse of the argument
* @return array|bool|string the boolean inverse of the argument
*/
public static function NOT($logical = false)
{

View File

@ -2,11 +2,14 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Logical;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Conditional
{
use ArrayEnabled;
/**
* STATEMENT_IF.
*
@ -34,7 +37,9 @@ class Conditional
*
* @param mixed $condition Condition to evaluate
* @param mixed $returnIfTrue Value to return when condition is true
* Note that this can be an array value
* @param mixed $returnIfFalse Optional value to return when condition is false
* Note that this can be an array value
*
* @return mixed The value of returnIfTrue or returnIfFalse determined by condition
*/
@ -45,8 +50,8 @@ class Conditional
}
$condition = ($condition === null) ? true : (bool) Functions::flattenSingleValue($condition);
$returnIfTrue = ($returnIfTrue === null) ? 0 : Functions::flattenSingleValue($returnIfTrue);
$returnIfFalse = ($returnIfFalse === null) ? false : Functions::flattenSingleValue($returnIfFalse);
$returnIfTrue = $returnIfTrue ?? 0;
$returnIfFalse = $returnIfFalse ?? false;
return ($condition) ? $returnIfTrue : $returnIfFalse;
}
@ -67,9 +72,11 @@ class Conditional
* result1, result2, ... result_n
* A list of results. The SWITCH function returns the corresponding result when a value
* matches expression.
* Note that these can be array values to be returned
* default
* Optional. It is the default to return if expression does not match any of the values
* (value1, value2, ... value_n).
* Note that this can be an array value to be returned
*
* @param mixed $arguments Statement arguments
*
@ -113,14 +120,21 @@ class Conditional
* =IFERROR(testValue,errorpart)
*
* @param mixed $testValue Value to check, is also the value returned when no error
* Or can be an array of values
* @param mixed $errorpart Value to return when testValue is an error condition
* Note that this can be an array value to be returned
*
* @return mixed The value of errorpart or testValue determined by error condition
* If an array of values is passed as the $testValue argument, then the returned result will also be
* an array with the same dimensions
*/
public static function IFERROR($testValue = '', $errorpart = '')
{
$testValue = ($testValue === null) ? '' : Functions::flattenSingleValue($testValue);
$errorpart = ($errorpart === null) ? '' : Functions::flattenSingleValue($errorpart);
if (is_array($testValue)) {
return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $testValue, $errorpart);
}
$errorpart = $errorpart ?? '';
return self::statementIf(Functions::isError($testValue), $errorpart, $testValue);
}
@ -132,14 +146,21 @@ class Conditional
* =IFNA(testValue,napart)
*
* @param mixed $testValue Value to check, is also the value returned when not an NA
* Or can be an array of values
* @param mixed $napart Value to return when testValue is an NA condition
* Note that this can be an array value to be returned
*
* @return mixed The value of errorpart or testValue determined by error condition
* If an array of values is passed as the $testValue argument, then the returned result will also be
* an array with the same dimensions
*/
public static function IFNA($testValue = '', $napart = '')
{
$testValue = ($testValue === null) ? '' : Functions::flattenSingleValue($testValue);
$napart = ($napart === null) ? '' : Functions::flattenSingleValue($napart);
if (is_array($testValue)) {
return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $testValue, $napart);
}
$napart = $napart ?? '';
return self::statementIf(Functions::isNa($testValue), $napart, $testValue);
}
@ -156,6 +177,7 @@ class Conditional
* Value returned if corresponding testValue (nth) was true
*
* @param mixed ...$arguments Statement arguments
* Note that this can be an array value to be returned
*
* @return mixed|string The value of returnIfTrue_n, if testValue_n was true. #N/A if none of testValues was true
*/
@ -170,7 +192,7 @@ class Conditional
$falseValueException = new Exception();
for ($i = 0; $i < $argumentCount; $i += 2) {
$testValue = ($arguments[$i] === null) ? '' : Functions::flattenSingleValue($arguments[$i]);
$returnIfTrue = ($arguments[$i + 1] === null) ? '' : Functions::flattenSingleValue($arguments[$i + 1]);
$returnIfTrue = ($arguments[$i + 1] === null) ? '' : $arguments[$i + 1];
$result = self::statementIf($testValue, $returnIfTrue, $falseValueException);
if ($result !== $falseValueException) {

View File

@ -2,11 +2,14 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Logical;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Operations
{
use ArrayEnabled;
/**
* LOGICAL_AND.
*
@ -146,12 +149,17 @@ class Operations
* holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value
*
* @param mixed $logical A value or expression that can be evaluated to TRUE or FALSE
* Or can be an array of values
*
* @return bool|string the boolean inverse of the argument
* @return array|bool|string the boolean inverse of the argument
* If an array of values is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function NOT($logical = false)
{
$logical = Functions::flattenSingleValue($logical);
if (is_array($logical)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $logical);
}
if (is_string($logical)) {
$logical = mb_strtoupper($logical, 'UTF-8');

View File

@ -55,6 +55,22 @@ class ArrayFormulaTest extends TestCase
'=SUM(SEQUENCE(3,3,0,1))',
36,
],
[
'=IFERROR({5/2, 5/0}, MAX(ABS({-2,4,-6})))',
[[2.5, 6]],
],
[
'=MAX(IFERROR({5/2, 5/0}, 2.1))',
2.5,
],
[
'=IF(FALSE,{1,2,3},{4,5,6})',
[[4, 5, 6]],
],
[
'=IFS(FALSE, {1,2,3}, TRUE, {4,5,6})',
[[4, 5, 6]],
],
];
}
}

View File

@ -2,6 +2,7 @@
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Logical;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Logical;
use PHPUnit\Framework\TestCase;
@ -30,4 +31,32 @@ class IfErrorTest extends TestCase
{
return require 'tests/data/Calculation/Logical/IFERROR.php';
}
/**
* @dataProvider providerIfErrorArray
*/
public function testIfErrorArray(array $expectedResult, string $argument1, string $argument2): void
{
$calculation = Calculation::getInstance();
$formula = "=IFERROR({$argument1}, {$argument2})";
$result = $calculation->_calculateFormulaValue($formula);
self::assertEquals($expectedResult, $result);
}
public function providerIfErrorArray(): array
{
return [
'vector' => [
[[2.5, 6]],
'{5/2, 5/0}',
'MAX(ABS({-2,4,-6}))',
],
'return value' => [
[[2.5, [[2, 3, 4]]]],
'{5/2, 5/0}',
'{2,3,4}',
],
];
}
}

View File

@ -2,6 +2,7 @@
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Logical;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Logical;
use PHPUnit\Framework\TestCase;
@ -30,4 +31,32 @@ class IfNaTest extends TestCase
{
return require 'tests/data/Calculation/Logical/IFNA.php';
}
/**
* @dataProvider providerIfNaArray
*/
public function testIfNaArray(array $expectedResult, string $argument1, string $argument2): void
{
$calculation = Calculation::getInstance();
$formula = "=IFNA({$argument1}, {$argument2})";
$result = $calculation->_calculateFormulaValue($formula);
self::assertEquals($expectedResult, $result);
}
public function providerIfNaArray(): array
{
return [
'vector' => [
[[2.5, '#DIV/0!', 6]],
'{5/2, 5/0, "#N/A"}',
'MAX(ABS({-2,4,-6}))',
],
'return value' => [
[[2.5, '#DIV/0!', [[2, 3, 4]]]],
'{5/2, 5/0, "#N/A"}',
'{2,3,4}',
],
];
}
}

View File

@ -2,6 +2,7 @@
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Logical;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Logical;
use PHPUnit\Framework\TestCase;
@ -28,4 +29,26 @@ class NotTest extends TestCase
{
return require 'tests/data/Calculation/Logical/NOT.php';
}
/**
* @dataProvider providerNotArray
*/
public function testNotArray(array $expectedResult, string $argument1): void
{
$calculation = Calculation::getInstance();
$formula = "=NOT({$argument1})";
$result = $calculation->_calculateFormulaValue($formula);
self::assertEquals($expectedResult, $result);
}
public function providerNotArray(): array
{
return [
'vector' => [
[[false, true, true, false]],
'{TRUE, FALSE, FALSE, TRUE}',
],
];
}
}

View File

@ -47,4 +47,11 @@ return [
true,
'ABC',
],
'array return' => [
[[4, 5, 6]],
false,
[[1, 2, 3]],
true,
[[4, 5, 6]],
],
];

View File

@ -39,6 +39,24 @@ return [
'DEF',
'Z',
],
'Array return' => [
[[4, 5, 6]],
2,
1,
[[1, 2, 3]],
2,
[[4, 5, 6]],
[[7, 8, 9]],
],
'Array return as default' => [
[[7, 8, 9]],
3,
1,
[[1, 2, 3]],
2,
[[4, 5, 6]],
[[7, 8, 9]],
],
// Must be value - no parameter
[
'#VALUE!',