Skip to content

Commit 851da61

Browse files
author
MarkBaker
committed
Start work on array-enabling the Lookup and Reference functions
Requires a new method (`evaluateArrayArgumentsSubsetFrom()`) in the `ArrayEnabled` Trait to handle functions where the arguments that need special array handling are trailing rather than leading arguments
1 parent 0371ccb commit 851da61

File tree

15 files changed

+224
-28
lines changed

15 files changed

+224
-28
lines changed

src/PhpSpreadsheet/Calculation/ArrayEnabled.php

+21
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,25 @@ protected static function evaluateArrayArgumentsSubset(callable $method, int $li
5353

5454
return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
5555
}
56+
57+
/**
58+
* @param mixed ...$arguments
59+
*/
60+
protected static function evaluateArrayArgumentsSubsetFrom(callable $method, int $start, ...$arguments): array
61+
{
62+
$arrayArgumentsSubset = array_combine(
63+
range($start, count($arguments) - $start),
64+
array_slice($arguments, $start)
65+
);
66+
if ($arrayArgumentsSubset === false) {
67+
return ['#VALUE!'];
68+
}
69+
70+
self::initialiseHelper($arrayArgumentsSubset);
71+
$leadingArguments = array_slice($arguments, 0, $start);
72+
$arguments = self::$arrayArgumentHelper->arguments();
73+
$arguments = array_merge($leadingArguments, $arguments);
74+
75+
return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
76+
}
5677
}

src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php

+11-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66

77
class ArrayArgumentHelper
88
{
9+
/**
10+
* @var int
11+
*/
12+
protected $indexStart = 0;
13+
914
/**
1015
* @var array
1116
*/
@@ -28,6 +33,8 @@ class ArrayArgumentHelper
2833

2934
public function initialise(array $arguments): void
3035
{
36+
$keys = array_keys($arguments);
37+
$this->indexStart = (int) array_shift($keys);
3138
$this->rows = $this->rows($arguments);
3239
$this->columns = $this->columns($arguments);
3340

@@ -57,7 +64,7 @@ public function getFirstArrayArgumentNumber(): int
5764
$rowArrays = $this->filterArray($this->rows);
5865
$columnArrays = $this->filterArray($this->columns);
5966

60-
for ($index = 0; $index < $this->argumentCount; ++$index) {
67+
for ($index = $this->indexStart; $index < $this->argumentCount; ++$index) {
6168
if (isset($rowArrays[$index]) || isset($columnArrays[$index])) {
6269
return ++$index;
6370
}
@@ -76,7 +83,7 @@ public function getSingleRowVector(): ?int
7683
private function getRowVectors(): array
7784
{
7885
$rowVectors = [];
79-
for ($index = 0; $index < $this->argumentCount; ++$index) {
86+
for ($index = $this->indexStart; $index < ($this->indexStart + $this->argumentCount); ++$index) {
8087
if ($this->rows[$index] === 1 && $this->columns[$index] > 1) {
8188
$rowVectors[] = $index;
8289
}
@@ -95,7 +102,7 @@ public function getSingleColumnVector(): ?int
95102
private function getColumnVectors(): array
96103
{
97104
$columnVectors = [];
98-
for ($index = 0; $index < $this->argumentCount; ++$index) {
105+
for ($index = $this->indexStart; $index < ($this->indexStart + $this->argumentCount); ++$index) {
99106
if ($this->rows[$index] > 1 && $this->columns[$index] === 1) {
100107
$columnVectors[] = $index;
101108
}
@@ -106,7 +113,7 @@ private function getColumnVectors(): array
106113

107114
public function getMatrixPair(): array
108115
{
109-
for ($i = 0; $i < ($this->argumentCount - 1); ++$i) {
116+
for ($i = $this->indexStart; $i < ($this->indexStart + $this->argumentCount - 1); ++$i) {
110117
for ($j = $i + 1; $j < $this->argumentCount; ++$j) {
111118
if (isset($this->rows[$i], $this->rows[$j])) {
112119
return [$i, $j];

src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentProcessor.php

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public static function processArguments(
3333

3434
$singleRowVectorIndex = self::$arrayArgumentHelper->getSingleRowVector();
3535
$singleColumnVectorIndex = self::$arrayArgumentHelper->getSingleColumnVector();
36+
3637
if ($singleRowVectorIndex !== null && $singleColumnVectorIndex !== null) {
3738
// Basic logic for a single row vector and a single column vector
3839
return self::evaluateVectorPair($method, $singleRowVectorIndex, $singleColumnVectorIndex, ...$arguments);

src/PhpSpreadsheet/Calculation/LookupRef.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ class LookupRef
4141
* @param bool $referenceStyle A logical value that specifies the A1 or R1C1 reference style.
4242
* TRUE or omitted CELL_ADDRESS returns an A1-style reference
4343
* FALSE CELL_ADDRESS returns an R1C1-style reference
44-
* @param string $sheetText Optional Name of worksheet to use
44+
* @param array|string $sheetText Optional Name of worksheet to use
4545
*
46-
* @return string
46+
* @return array|string
4747
*/
4848
public static function cellAddress($row, $column, $relativity = 1, $referenceStyle = true, $sheetText = '')
4949
{
@@ -277,7 +277,7 @@ public static function CHOOSE(...$chooseArgs)
277277
* @param mixed $matchType The number -1, 0, or 1. -1 means above, 0 means exact match, 1 means below.
278278
* If match_type is 1 or -1, the list has to be ordered.
279279
*
280-
* @return int|string The relative position of the found item
280+
* @return array|int|string The relative position of the found item
281281
*/
282282
public static function MATCH($lookupValue, $lookupArray, $matchType = 1)
283283
{

src/PhpSpreadsheet/Calculation/LookupRef/Address.php

+27-6
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22

33
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
44

5+
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
56
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
67
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
78

89
class Address
910
{
11+
use ArrayEnabled;
12+
1013
public const ADDRESS_ABSOLUTE = 1;
1114
public const ADDRESS_COLUMN_RELATIVE = 2;
1215
public const ADDRESS_ROW_RELATIVE = 3;
@@ -24,26 +27,44 @@ class Address
2427
* =ADDRESS(row, column, [relativity], [referenceStyle], [sheetText])
2528
*
2629
* @param mixed $row Row number (integer) to use in the cell reference
30+
* Or can be an array of values
2731
* @param mixed $column Column number (integer) to use in the cell reference
32+
* Or can be an array of values
2833
* @param mixed $relativity Integer flag indicating the type of reference to return
2934
* 1 or omitted Absolute
3035
* 2 Absolute row; relative column
3136
* 3 Relative row; absolute column
3237
* 4 Relative
38+
* Or can be an array of values
3339
* @param mixed $referenceStyle A logical (boolean) value that specifies the A1 or R1C1 reference style.
3440
* TRUE or omitted ADDRESS returns an A1-style reference
3541
* FALSE ADDRESS returns an R1C1-style reference
42+
* Or can be an array of values
3643
* @param mixed $sheetName Optional Name of worksheet to use
44+
* Or can be an array of values
3745
*
38-
* @return string
46+
* @return array|string
47+
* If an array of values is passed as the $testValue argument, then the returned result will also be
48+
* an array with the same dimensions
3949
*/
4050
public static function cell($row, $column, $relativity = 1, $referenceStyle = true, $sheetName = '')
4151
{
42-
$row = Functions::flattenSingleValue($row);
43-
$column = Functions::flattenSingleValue($column);
44-
$relativity = ($relativity === null) ? 1 : Functions::flattenSingleValue($relativity);
45-
$referenceStyle = ($referenceStyle === null) ? true : Functions::flattenSingleValue($referenceStyle);
46-
$sheetName = Functions::flattenSingleValue($sheetName);
52+
if (
53+
is_array($row) || is_array($column) ||
54+
is_array($relativity) || is_array($referenceStyle) || is_array($sheetName)
55+
) {
56+
return self::evaluateArrayArguments(
57+
[self::class, __FUNCTION__],
58+
$row,
59+
$column,
60+
$relativity,
61+
$referenceStyle,
62+
$sheetName
63+
);
64+
}
65+
66+
$relativity = $relativity ?? 1;
67+
$referenceStyle = $referenceStyle ?? true;
4768

4869
if (($row < 1) || ($column < 1)) {
4970
return Functions::VALUE();

src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php

+9-5
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22

33
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
44

5+
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
56
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
67
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
78
use PhpOffice\PhpSpreadsheet\Calculation\Internal\WildcardMatch;
89
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
910

1011
class ExcelMatch
1112
{
13+
use ArrayEnabled;
14+
1215
public const MATCHTYPE_SMALLEST_VALUE = -1;
1316
public const MATCHTYPE_FIRST_VALUE = 0;
1417
public const MATCHTYPE_LARGEST_VALUE = 1;
@@ -26,15 +29,16 @@ class ExcelMatch
2629
* @param mixed $matchType The number -1, 0, or 1. -1 means above, 0 means exact match, 1 means below.
2730
* If match_type is 1 or -1, the list has to be ordered.
2831
*
29-
* @return int|string The relative position of the found item
32+
* @return array|int|string The relative position of the found item
3033
*/
3134
public static function MATCH($lookupValue, $lookupArray, $matchType = self::MATCHTYPE_LARGEST_VALUE)
3235
{
36+
if (is_array($lookupValue)) {
37+
return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $lookupValue, $lookupArray, $matchType);
38+
}
39+
3340
$lookupArray = Functions::flattenArray($lookupArray);
34-
$lookupValue = Functions::flattenSingleValue($lookupValue);
35-
$matchType = ($matchType === null)
36-
? self::MATCHTYPE_LARGEST_VALUE
37-
: (int) Functions::flattenSingleValue($matchType);
41+
$matchType = (int) ($matchType ?? self::MATCHTYPE_LARGEST_VALUE);
3842

3943
try {
4044
// Input validation

src/PhpSpreadsheet/Calculation/LookupRef/Lookup.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22

33
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
44

5+
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
56
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
67
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
78

89
class Lookup
910
{
11+
use ArrayEnabled;
12+
1013
/**
1114
* LOOKUP
1215
* The LOOKUP function searches for value either from a one-row or one-column range or from an array.
@@ -19,7 +22,9 @@ class Lookup
1922
*/
2023
public static function lookup($lookupValue, $lookupVector, $resultVector = null)
2124
{
22-
$lookupValue = Functions::flattenSingleValue($lookupValue);
25+
if (is_array($lookupValue)) {
26+
return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $lookupValue, $lookupVector, $resultVector);
27+
}
2328

2429
if (!is_array($lookupVector)) {
2530
return Functions::NA();

src/PhpSpreadsheet/Calculation/LookupRef/Matrix.php

+13-2
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22

33
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
44

5+
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
56
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
67
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
78

89
class Matrix
910
{
11+
use ArrayEnabled;
12+
1013
/**
1114
* TRANSPOSE.
1215
*
@@ -45,17 +48,25 @@ public static function transpose($matrixData)
4548
* @param mixed $matrix A range of cells or an array constant
4649
* @param mixed $rowNum The row in the array or range from which to return a value.
4750
* If row_num is omitted, column_num is required.
51+
* Or can be an array of values
4852
* @param mixed $columnNum The column in the array or range from which to return a value.
4953
* If column_num is omitted, row_num is required.
54+
* Or can be an array of values
5055
*
5156
* TODO Provide support for area_num, currently not supported
5257
*
5358
* @return mixed the value of a specified cell or array of cells
59+
* If an array of values is passed as the $rowNum and/or $columnNum arguments, then the returned result
60+
* will also be an array with the same dimensions
5461
*/
5562
public static function index($matrix, $rowNum = 0, $columnNum = 0)
5663
{
57-
$rowNum = ($rowNum === null) ? 0 : Functions::flattenSingleValue($rowNum);
58-
$columnNum = ($columnNum === null) ? 0 : Functions::flattenSingleValue($columnNum);
64+
if (is_array($rowNum) || is_array($columnNum)) {
65+
return self::evaluateArrayArgumentsSubsetFrom([self::class, __FUNCTION__], 1, $matrix, $rowNum, $columnNum);
66+
}
67+
68+
$rowNum = $rowNum ?? 0;
69+
$columnNum = $columnNum ?? 0;
5970

6071
try {
6172
$rowNum = LookupRefValidations::validatePositiveInt($rowNum);

src/PhpSpreadsheet/Calculation/LookupRef/Selection.php

+9-5
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22

33
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
44

5+
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
56
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
67

78
class Selection
89
{
10+
use ArrayEnabled;
11+
912
/**
1013
* CHOOSE.
1114
*
@@ -15,18 +18,19 @@ class Selection
1518
* Excel Function:
1619
* =CHOOSE(index_num, value1, [value2], ...)
1720
*
21+
* @param mixed $chosenEntry The entry to select from the list (indexed from 1)
1822
* @param mixed ...$chooseArgs Data values
1923
*
2024
* @return mixed The selected value
2125
*/
22-
public static function choose(...$chooseArgs)
26+
public static function choose($chosenEntry, ...$chooseArgs)
2327
{
24-
$chosenEntry = Functions::flattenArray(array_shift($chooseArgs));
25-
$entryCount = count($chooseArgs) - 1;
26-
2728
if (is_array($chosenEntry)) {
28-
$chosenEntry = array_shift($chosenEntry);
29+
return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $chosenEntry, ...$chooseArgs);
2930
}
31+
32+
$entryCount = count($chooseArgs) - 1;
33+
3034
if (is_numeric($chosenEntry)) {
3135
--$chosenEntry;
3236
} else {

tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/AddressTest.php

+24
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
44

5+
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
56
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
67
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
78
use PHPUnit\Framework\TestCase;
@@ -28,4 +29,27 @@ public function providerADDRESS(): array
2829
{
2930
return require 'tests/data/Calculation/LookupRef/ADDRESS.php';
3031
}
32+
33+
/**
34+
* @dataProvider providerAddressArray
35+
*/
36+
public function testAddressArray(array $expectedResult, string $argument1, string $argument2): void
37+
{
38+
$calculation = Calculation::getInstance();
39+
40+
$formula = "=ADDRESS({$argument1}, {$argument2}, 4)";
41+
$result = $calculation->_calculateFormulaValue($formula);
42+
self::assertEquals($expectedResult, $result);
43+
}
44+
45+
public function providerAddressArray(): array
46+
{
47+
return [
48+
'row/column vectors' => [
49+
[['A1', 'B1', 'C1'], ['A2', 'B2', 'C2'], ['A3', 'B3', 'C3']],
50+
'{1; 2; 3}',
51+
'{1, 2, 3}',
52+
],
53+
];
54+
}
3155
}

tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ChooseTest.php

+25
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
44

5+
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
56
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
67
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
78
use PHPUnit\Framework\TestCase;
@@ -28,4 +29,28 @@ public function providerCHOOSE(): array
2829
{
2930
return require 'tests/data/Calculation/LookupRef/CHOOSE.php';
3031
}
32+
33+
/**
34+
* @dataProvider providerChooseArray
35+
*/
36+
public function testChooseArray(array $expectedResult, string $values, array $selections): void
37+
{
38+
$calculation = Calculation::getInstance();
39+
40+
$selections = implode(',', $selections);
41+
$formula = "=CHOOSE({$values}, {$selections})";
42+
$result = $calculation->_calculateFormulaValue($formula);
43+
self::assertEquals($expectedResult, $result);
44+
}
45+
46+
public function providerChooseArray(): array
47+
{
48+
return [
49+
'row vector' => [
50+
[['Orange', 'Blue', 'Yellow']],
51+
'{2, 5, 3}',
52+
['"Red"', '"Orange"', '"Yellow"', '"Green"', '"Blue"'],
53+
],
54+
];
55+
}
3156
}

0 commit comments

Comments
 (0)