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 * @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) public static function NOT($logical = false)
{ {

View File

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

View File

@ -2,11 +2,14 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Logical; namespace PhpOffice\PhpSpreadsheet\Calculation\Logical;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Operations class Operations
{ {
use ArrayEnabled;
/** /**
* LOGICAL_AND. * 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 * 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 * @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) 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)) { if (is_string($logical)) {
$logical = mb_strtoupper($logical, 'UTF-8'); $logical = mb_strtoupper($logical, 'UTF-8');

View File

@ -55,6 +55,22 @@ class ArrayFormulaTest extends TestCase
'=SUM(SEQUENCE(3,3,0,1))', '=SUM(SEQUENCE(3,3,0,1))',
36, 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; namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Logical;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Logical; use PhpOffice\PhpSpreadsheet\Calculation\Logical;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
@ -30,4 +31,32 @@ class IfErrorTest extends TestCase
{ {
return require 'tests/data/Calculation/Logical/IFERROR.php'; 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; namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Logical;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Logical; use PhpOffice\PhpSpreadsheet\Calculation\Logical;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
@ -30,4 +31,32 @@ class IfNaTest extends TestCase
{ {
return require 'tests/data/Calculation/Logical/IFNA.php'; 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; namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Logical;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Logical; use PhpOffice\PhpSpreadsheet\Calculation\Logical;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
@ -28,4 +29,26 @@ class NotTest extends TestCase
{ {
return require 'tests/data/Calculation/Logical/NOT.php'; 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, true,
'ABC', 'ABC',
], ],
'array return' => [
[[4, 5, 6]],
false,
[[1, 2, 3]],
true,
[[4, 5, 6]],
],
]; ];

View File

@ -39,6 +39,24 @@ return [
'DEF', 'DEF',
'Z', '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 // Must be value - no parameter
[ [
'#VALUE!', '#VALUE!',