Lookup ref further tests and examples (#1918)

* Extract LookupRef\INDEX() into index() method of LookupRef\Matrix class
Additional tests
* Bugfix for returning a column using INDEX()
* Some improvements to ROW() and COLUMN()
* Simplify some of the INDEX() logic, eliminating redundant code
This commit is contained in:
Mark Baker 2021-03-11 22:34:47 +01:00 committed by GitHub
parent 499ce61cf7
commit 2259de578b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 343 additions and 86 deletions

View File

@ -0,0 +1,22 @@
<?php
use PhpOffice\PhpSpreadsheet\Spreadsheet;
require __DIR__ . '/../../Header.php';
$helper->log('Returns a text reference to a single cell in a worksheet.');
// Create new PhpSpreadsheet object
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$worksheet->getCell('A1')->setValue('=ADDRESS(2,3)');
$worksheet->getCell('A2')->setValue('=ADDRESS(2,3,2)');
$worksheet->getCell('A3')->setValue('=ADDRESS(2,3,2,FALSE)');
$worksheet->getCell('A4')->setValue('=ADDRESS(2,3,1,FALSE,"[Book1]Sheet1")');
$worksheet->getCell('A5')->setValue('=ADDRESS(2,3,1,FALSE,"EXCEL SHEET")');
for ($row = 1; $row <= 5; ++$row) {
$cell = $worksheet->getCell("A{$row}");
$helper->log("A{$row}: {$cell->getValue()} => {$cell->getCalculatedValue()}");
}

View File

@ -0,0 +1,23 @@
<?php
use PhpOffice\PhpSpreadsheet\Spreadsheet;
require __DIR__ . '/../../Header.php';
$helper->log('Returns the column index of a cell.');
// Create new PhpSpreadsheet object
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$worksheet->getCell('A1')->setValue('=COLUMN(C13)');
$worksheet->getCell('A2')->setValue('=COLUMN(E13:G15)');
$worksheet->getCell('F1')->setValue('=COLUMN()');
for ($row = 1; $row <= 2; ++$row) {
$cell = $worksheet->getCell("A{$row}");
$helper->log("A{$row}: {$cell->getValue()} => {$cell->getCalculatedValue()}");
}
$cell = $worksheet->getCell('F1');
$helper->log("F1: {$cell->getValue()} => {$cell->getCalculatedValue()}");

View File

@ -0,0 +1,21 @@
<?php
use PhpOffice\PhpSpreadsheet\Spreadsheet;
require __DIR__ . '/../../Header.php';
$helper->log('Returns the number of columns in an array or reference.');
// Create new PhpSpreadsheet object
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$worksheet->getCell('A1')->setValue('=COLUMNS(C1:G4)');
$worksheet->getCell('A2')->setValue('=COLUMNS({1,2,3;4,5,6})');
$worksheet->getCell('A3')->setValue('=ROWS(C1:E4 D3:G5)');
$worksheet->getCell('A4')->setValue('=COLUMNS(1:1)');
for ($row = 1; $row <= 4; ++$row) {
$cell = $worksheet->getCell("A{$row}");
$helper->log("A{$row}: {$cell->getValue()} => {$cell->getCalculatedValue()}");
}

View File

@ -0,0 +1,39 @@
<?php
use PhpOffice\PhpSpreadsheet\Spreadsheet;
require __DIR__ . '/../../Header.php';
$helper->log('Returns the row index of a cell.');
// Create new PhpSpreadsheet object
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$data1 = [
['Apples', 'Lemons'],
['Bananas', 'Pears'],
];
$data2 = [
[4, 6],
[5, 3],
[6, 9],
[7, 5],
[8, 3],
];
$worksheet->fromArray($data1, null, 'A1');
$worksheet->fromArray($data2, null, 'C1');
$worksheet->getCell('A11')->setValue('=INDEX(A1:B2, 2, 2)');
$worksheet->getCell('A12')->setValue('=INDEX(A1:B2, 2, 1)');
$worksheet->getCell('A13')->setValue('=INDEX({1,2;3,4}, 0, 2)');
$worksheet->getCell('A14')->setValue('=INDEX(C1:C5, 5)');
$worksheet->getCell('A15')->setValue('=INDEX(C1:D5, 5, 2)');
$worksheet->getCell('A16')->setValue('=SUM(INDEX(C1:D5, 5, 0))');
for ($row = 11; $row <= 16; ++$row) {
$cell = $worksheet->getCell("A{$row}");
$helper->log("A{$row}: {$cell->getValue()} => {$cell->getCalculatedValue()}");
}

View File

@ -0,0 +1,20 @@
<?php
use PhpOffice\PhpSpreadsheet\Spreadsheet;
require __DIR__ . '/../../Header.php';
$helper->log('Returns the row index of a cell.');
// Create new PhpSpreadsheet object
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$worksheet->getCell('A1')->setValue('=ROW(C13)');
$worksheet->getCell('A2')->setValue('=ROW(E19:G21)');
$worksheet->getCell('A3')->setValue('=ROW()');
for ($row = 1; $row <= 3; ++$row) {
$cell = $worksheet->getCell("A{$row}");
$helper->log("A{$row}: {$cell->getValue()} => {$cell->getCalculatedValue()}");
}

View File

@ -0,0 +1,20 @@
<?php
use PhpOffice\PhpSpreadsheet\Spreadsheet;
require __DIR__ . '/../../Header.php';
$helper->log('Returns the row index of a cell.');
// Create new PhpSpreadsheet object
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$worksheet->getCell('A1')->setValue('=ROWS(C1:E4)');
$worksheet->getCell('A2')->setValue('=ROWS({1,2,3;4,5,6})');
$worksheet->getCell('A3')->setValue('=ROWS(C1:E4 D3:G5)');
for ($row = 1; $row <= 3; ++$row) {
$cell = $worksheet->getCell("A{$row}");
$helper->log("A{$row}: {$cell->getValue()} => {$cell->getCalculatedValue()}");
}

View File

@ -529,59 +529,21 @@ class LookupRef
* Excel Function: * Excel Function:
* =INDEX(range_array, row_num, [column_num]) * =INDEX(range_array, row_num, [column_num])
* *
* @param mixed $arrayValues A range of cells or an array constant * @Deprecated 1.18.0
* @param mixed $rowNum The row in array from which to return a value. If row_num is omitted, column_num is required. *
* @param mixed $columnNum The column in array from which to return a value. If column_num is omitted, row_num is required. * @see Use the index() method in the LookupRef\Matrix class instead
*
* @param mixed $rowNum The row in the array or range from which to return a value.
* If row_num is omitted, column_num is required.
* @param mixed $columnNum The column in the array or range from which to return a value.
* If column_num is omitted, row_num is required.
* @param mixed $matrix
* *
* @return mixed the value of a specified cell or array of cells * @return mixed the value of a specified cell or array of cells
*/ */
public static function INDEX($arrayValues, $rowNum = 0, $columnNum = 0) public static function INDEX($matrix, $rowNum = 0, $columnNum = 0)
{ {
$rowNum = Functions::flattenSingleValue($rowNum); return Matrix::index($matrix, $rowNum, $columnNum);
$columnNum = Functions::flattenSingleValue($columnNum);
if (($rowNum < 0) || ($columnNum < 0)) {
return Functions::VALUE();
}
if (!is_array($arrayValues) || ($rowNum > count($arrayValues))) {
return Functions::REF();
}
$rowKeys = array_keys($arrayValues);
$columnKeys = @array_keys($arrayValues[$rowKeys[0]]);
if ($columnNum > count($columnKeys)) {
return Functions::VALUE();
} elseif ($columnNum == 0) {
if ($rowNum == 0) {
return $arrayValues;
}
$rowNum = $rowKeys[--$rowNum];
$returnArray = [];
foreach ($arrayValues as $arrayColumn) {
if (is_array($arrayColumn)) {
if (isset($arrayColumn[$rowNum])) {
$returnArray[] = $arrayColumn[$rowNum];
} else {
return [$rowNum => $arrayValues[$rowNum]];
}
} else {
return $arrayValues[$rowNum];
}
}
return $returnArray;
}
$columnNum = $columnKeys[--$columnNum];
if ($rowNum > count($rowKeys)) {
return Functions::VALUE();
} elseif ($rowNum == 0) {
return $arrayValues[$columnNum];
}
$rowNum = $rowKeys[--$rowNum];
return $arrayValues[$rowNum][$columnNum];
} }
/** /**

View File

@ -2,6 +2,8 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef; namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Matrix class Matrix
{ {
/** /**
@ -30,4 +32,73 @@ class Matrix
return $returnMatrix; return $returnMatrix;
} }
/**
* INDEX.
*
* Uses an index to choose a value from a reference or array
*
* Excel Function:
* =INDEX(range_array, row_num, [column_num])
*
* @param mixed $matrix A range of cells or an array constant
* @param mixed $rowNum The row in the array or range from which to return a value.
* If row_num is omitted, column_num is required.
* @param mixed $columnNum The column in the array or range from which to return a value.
* If column_num is omitted, row_num is required.
*
* @return mixed the value of a specified cell or array of cells
*/
public static function index($matrix, $rowNum = 0, $columnNum = 0)
{
$rowNum = Functions::flattenSingleValue($rowNum);
$columnNum = Functions::flattenSingleValue($columnNum);
if (!is_numeric($rowNum) || !is_numeric($columnNum) || ($rowNum < 0) || ($columnNum < 0)) {
return Functions::VALUE();
}
if (!is_array($matrix) || ($rowNum > count($matrix))) {
return Functions::REF();
}
$rowKeys = array_keys($matrix);
$columnKeys = @array_keys($matrix[$rowKeys[0]]);
if ($columnNum > count($columnKeys)) {
return Functions::REF();
}
if ($columnNum == 0) {
return self::extractRowValue($matrix, $rowKeys, $rowNum);
}
$columnNum = $columnKeys[--$columnNum];
if ($rowNum == 0) {
return array_map(
function ($value) {
return [$value];
},
array_column($matrix, $columnNum)
);
}
$rowNum = $rowKeys[--$rowNum];
return $matrix[$rowNum][$columnNum];
}
private static function extractRowValue(array $matrix, array $rowKeys, int $rowNum)
{
if ($rowNum == 0) {
return $matrix;
}
$rowNum = $rowKeys[--$rowNum];
$row = $matrix[$rowNum];
if (is_array($row)) {
return [$rowNum => $row];
}
return $row;
}
} }

View File

@ -39,23 +39,23 @@ class RowColumnInformation
return (int) Coordinate::columnIndexFromString($columnKey); return (int) Coordinate::columnIndexFromString($columnKey);
} }
} else {
[, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
if (strpos($cellAddress, ':') !== false) {
[$startAddress, $endAddress] = explode(':', $cellAddress);
$startAddress = preg_replace('/[^a-z]/i', '', $startAddress);
$endAddress = preg_replace('/[^a-z]/i', '', $endAddress);
$returnValue = [];
do {
$returnValue[] = (int) Coordinate::columnIndexFromString($startAddress);
} while ($startAddress++ != $endAddress);
return $returnValue;
}
$cellAddress = preg_replace('/[^a-z]/i', '', $cellAddress);
return (int) Coordinate::columnIndexFromString($cellAddress);
} }
[, $cellAddress] = Worksheet::extractSheetTitle((string) $cellAddress, true);
if (strpos($cellAddress, ':') !== false) {
[$startAddress, $endAddress] = explode(':', $cellAddress);
$startAddress = preg_replace('/[^a-z]/i', '', $startAddress);
$endAddress = preg_replace('/[^a-z]/i', '', $endAddress);
return range(
(int) Coordinate::columnIndexFromString($startAddress),
(int) Coordinate::columnIndexFromString($endAddress)
);
}
$cellAddress = preg_replace('/[^a-z]/i', '', $cellAddress);
return (int) Coordinate::columnIndexFromString($cellAddress);
} }
/** /**
@ -73,7 +73,7 @@ class RowColumnInformation
*/ */
public static function COLUMNS($cellAddress = null) public static function COLUMNS($cellAddress = null)
{ {
if ($cellAddress === null || $cellAddress === '') { if ($cellAddress === null || (is_string($cellAddress) && trim($cellAddress) === '')) {
return 1; return 1;
} elseif (!is_array($cellAddress)) { } elseif (!is_array($cellAddress)) {
return Functions::VALUE(); return Functions::VALUE();
@ -114,28 +114,29 @@ class RowColumnInformation
} }
if (is_array($cellAddress)) { if (is_array($cellAddress)) {
foreach ($cellAddress as $columnKey => $rowValue) { foreach ($cellAddress as $rowKey => $rowValue) {
foreach ($rowValue as $rowKey => $cellValue) { foreach ($rowValue as $columnKey => $cellValue) {
return (int) preg_replace('/\D/', '', $rowKey); return (int) preg_replace('/\D/', '', $rowKey);
} }
} }
} else {
[, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
if (strpos($cellAddress, ':') !== false) {
[$startAddress, $endAddress] = explode(':', $cellAddress);
$startAddress = preg_replace('/\D/', '', $startAddress);
$endAddress = preg_replace('/\D/', '', $endAddress);
$returnValue = [];
do {
$returnValue[][] = (int) $startAddress;
} while ($startAddress++ != $endAddress);
return $returnValue;
}
[$cellAddress] = explode(':', $cellAddress);
return (int) preg_replace('/\D/', '', $cellAddress);
} }
[, $cellAddress] = Worksheet::extractSheetTitle((string) $cellAddress, true);
if (strpos($cellAddress, ':') !== false) {
[$startAddress, $endAddress] = explode(':', $cellAddress);
$startAddress = preg_replace('/\D/', '', $startAddress);
$endAddress = preg_replace('/\D/', '', $endAddress);
return array_map(
function ($value) {
return [$value];
},
range($startAddress, $endAddress)
);
}
[$cellAddress] = explode(':', $cellAddress);
return (int) preg_replace('/\D/', '', $cellAddress);
} }
/** /**
@ -153,7 +154,7 @@ class RowColumnInformation
*/ */
public static function ROWS($cellAddress = null) public static function ROWS($cellAddress = null)
{ {
if ($cellAddress === null || $cellAddress === '') { if ($cellAddress === null || (is_string($cellAddress) && trim($cellAddress) === '')) {
return 1; return 1;
} elseif (!is_array($cellAddress)) { } elseif (!is_array($cellAddress)) {
return Functions::VALUE(); return Functions::VALUE();

View File

@ -21,6 +21,7 @@ class IndexTest extends TestCase
public function testINDEX($expectedResult, ...$args): void public function testINDEX($expectedResult, ...$args): void
{ {
$result = LookupRef::INDEX(...$args); $result = LookupRef::INDEX(...$args);
// var_dump($result);
self::assertEquals($expectedResult, $result); self::assertEquals($expectedResult, $result);
} }

View File

@ -54,7 +54,7 @@ return [
-1, -1,
], ],
[ [
'#VALUE!', // Expected '#REF!', // Expected
// Input // Input
[ [
'20' => ['R' => 1, 'S' => 3], '20' => ['R' => 1, 'S' => 3],
@ -63,6 +63,16 @@ return [
2, 2,
10, 10,
], ],
[
'#REF!', // Expected
// Input
[
'20' => ['R' => 1, 'S' => 3],
'21' => ['R' => 2, 'S' => 4],
],
10,
2,
],
[ [
4, // Expected 4, // Expected
// Input // Input
@ -87,4 +97,64 @@ return [
'21' => ['R' => 2], '21' => ['R' => 2],
], ],
], ],
[
'Pears',
[
['Apples', 'Lemons'],
['Bananas', 'Pears'],
],
2,
2,
],
[
'Bananas',
[
['Apples', 'Lemons'],
['Bananas', 'Pears'],
],
2,
1,
],
[
3,
[
[4, 6],
[5, 3],
[6, 9],
[7, 5],
[8, 3],
],
5,
2,
],
[
[4 => [8, 3]],
[
[4, 6],
[5, 3],
[6, 9],
[7, 5],
[8, 3],
],
5,
0,
],
[
[
[6],
[3],
[9],
[5],
[3],
],
[
[4, 6],
[5, 3],
[6, 9],
[7, 5],
[8, 3],
],
0,
2,
],
]; ];

View File

@ -17,6 +17,13 @@ return [
[[10], [11], [12]], [[10], [11], [12]],
'C10:D12', 'C10:D12',
], ],
[
4,
[
4 => ['B' => 'B5', 'C' => 'C5', 'D' => 'D5'],
5 => ['B' => 'B5', 'C' => 'C5', 'D' => 'D5'],
],
],
[ [
[[10], [11], [12]], [[10], [11], [12]],
'Sheet1!C10:C12', 'Sheet1!C10:C12',