From c4895b94687cd79b91a7d026ea1aed1b061385fe Mon Sep 17 00:00:00 2001 From: Owen Leibman Date: Fri, 17 Jan 2020 17:52:16 -0800 Subject: [PATCH] MATCH with a static array should return the position of the found value based on the values submitted. Returns #N/A, unless the element searched for is at the end of the array. The problem is in Calculation.php line 4231: if (!is_array($functionCall)) { foreach ($args as &$arg) { $arg = Functions::flattenSingleValue($arg); } unset($arg); } I believe this code is intended to handle functions where PhpSpreadsheet just passes the call on to PHP without implementing the code on its own, e.g. for atan or acos. In the bug report, the following code fails: $flat_rate = "=MATCH(6,{4,5,6,2}, 0)"; $sheet->getCell('A1')->setValue($flat_rate); The expected value is 3, but the actual result is "#N/A". The reason for this result is that the parser replaces the braces with calls to the MKMATRIX internal function, whose value for functioncall was: 'self::MKMATRIX'. Since this isn't an array, the flattening code is executed, and the unintended result occurs. The fix is to change the definition for functioncall in that case to [__CLASS__, 'mkMatrix'], avoiding the flattening. However, there is also another part to this bug. The flattening should be returning the first entry in the array, but is in fact returning the last. This explains why the bug report specified "unless ... end of the array". I confirmed that Excel does use the first item in the array rather than the last, e.g. =atan({1,2,3}) entered into a cell will return atan(1), not atan(3). The problem here is that flattenSingleValue, which says in its comments that it is supposed to be returning the first item, uses array_pop rather than array_shift. I have changed that as well. The same mistake was also present in Cell.php function getCalculatedValue. The correct behavior can be verified by entering =minverse({-2.5,1.5;2,-1}) into an Excel cell' Excel flattens the result ({2,3;4,5}) to 2, and so should PhpSpreadsheet. Fixes #1271 Closes #1332 --- CHANGELOG.md | 1 + .../Calculation/Calculation.php | 2 +- src/PhpSpreadsheet/Calculation/Functions.php | 2 +- src/PhpSpreadsheet/Cell/Cell.php | 2 +- .../Calculation/FormulaAsStringTest.php | 46 +++++++++++++++++++ tests/data/Calculation/FunctionsAsString.php | 24 ++++++++++ 6 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Calculation/FormulaAsStringTest.php create mode 100644 tests/data/Calculation/FunctionsAsString.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 391115a6..d4fae5c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Fix ROUNDUP and ROUNDDOWN for floating-point rounding error [#1404](https://github.com/PHPOffice/PhpSpreadsheet/pull/1404) - Fix loading styles from vmlDrawings when containing whitespace [#1347](https://github.com/PHPOffice/PhpSpreadsheet/issues/1347) - Fix incorrect behavior when removing last row [#1365](https://github.com/PHPOffice/PhpSpreadsheet/pull/1365) +- MATCH with a static array should return the position of the found value based on the values submitted [#1332](https://github.com/PHPOffice/PhpSpreadsheet/pull/1332) ## [1.11.0] - 2020-03-02 diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index f428c9b4..ae86a963 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -2235,7 +2235,7 @@ class Calculation private static $controlFunctions = [ 'MKMATRIX' => [ 'argumentCount' => '*', - 'functionCall' => 'self::mkMatrix', + 'functionCall' => [__CLASS__, 'mkMatrix'], ], ]; diff --git a/src/PhpSpreadsheet/Calculation/Functions.php b/src/PhpSpreadsheet/Calculation/Functions.php index bb2170be..1862b008 100644 --- a/src/PhpSpreadsheet/Calculation/Functions.php +++ b/src/PhpSpreadsheet/Calculation/Functions.php @@ -646,7 +646,7 @@ class Functions public static function flattenSingleValue($value = '') { while (is_array($value)) { - $value = array_pop($value); + $value = array_shift($value); } return $value; diff --git a/src/PhpSpreadsheet/Cell/Cell.php b/src/PhpSpreadsheet/Cell/Cell.php index bae8261e..e618436e 100644 --- a/src/PhpSpreadsheet/Cell/Cell.php +++ b/src/PhpSpreadsheet/Cell/Cell.php @@ -266,7 +266,7 @@ class Cell // We don't yet handle array returns if (is_array($result)) { while (is_array($result)) { - $result = array_pop($result); + $result = array_shift($result); } } } catch (Exception $ex) { diff --git a/tests/PhpSpreadsheetTests/Calculation/FormulaAsStringTest.php b/tests/PhpSpreadsheetTests/Calculation/FormulaAsStringTest.php new file mode 100644 index 00000000..c51e520a --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/FormulaAsStringTest.php @@ -0,0 +1,46 @@ +getActiveSheet(); + $workSheet->setCellValue('A1', 10); + $workSheet->setCellValue('A2', 20); + $workSheet->setCellValue('A3', 30); + $workSheet->setCellValue('A4', 40); + $spreadsheet->addNamedRange(new \PhpOffice\PhpSpreadsheet\NamedRange('namedCell', $workSheet, 'A4')); + $workSheet->setCellValue('B1', 'uPPER'); + $workSheet->setCellValue('B2', '=TRUE()'); + $workSheet->setCellValue('B3', '=FALSE()'); + + $ws2 = $spreadsheet->createSheet(); + $ws2->setCellValue('A1', 100); + $ws2->setCellValue('A2', 200); + $ws2->setTitle('Sheet2'); + $spreadsheet->addNamedRange(new \PhpOffice\PhpSpreadsheet\NamedRange('A2B', $ws2, 'A2')); + + $spreadsheet->setActiveSheetIndex(0); + $cell2 = $workSheet->getCell('D1'); + $cell2->setValue($formula); + $result = $cell2->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public function providerFunctionsAsString() + { + return require 'data/Calculation/FunctionsAsString.php'; + } +} diff --git a/tests/data/Calculation/FunctionsAsString.php b/tests/data/Calculation/FunctionsAsString.php new file mode 100644 index 00000000..85953472 --- /dev/null +++ b/tests/data/Calculation/FunctionsAsString.php @@ -0,0 +1,24 @@ +