diff --git a/samples/Calculations/LookupRef/ADDRESS.php b/samples/Calculations/LookupRef/ADDRESS.php new file mode 100644 index 0000000000..2377541d80 --- /dev/null +++ b/samples/Calculations/LookupRef/ADDRESS.php @@ -0,0 +1,22 @@ +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()}"); +} diff --git a/samples/Calculations/LookupRef/COLUMN.php b/samples/Calculations/LookupRef/COLUMN.php new file mode 100644 index 0000000000..e9e5846608 --- /dev/null +++ b/samples/Calculations/LookupRef/COLUMN.php @@ -0,0 +1,23 @@ +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()}"); diff --git a/samples/Calculations/LookupRef/COLUMNS.php b/samples/Calculations/LookupRef/COLUMNS.php new file mode 100644 index 0000000000..4d7f8d10e6 --- /dev/null +++ b/samples/Calculations/LookupRef/COLUMNS.php @@ -0,0 +1,21 @@ +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()}"); +} diff --git a/samples/Calculations/LookupRef/INDEX.php b/samples/Calculations/LookupRef/INDEX.php new file mode 100644 index 0000000000..9ef0b94562 --- /dev/null +++ b/samples/Calculations/LookupRef/INDEX.php @@ -0,0 +1,39 @@ +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()}"); +} diff --git a/samples/Calculations/LookupRef/ROW.php b/samples/Calculations/LookupRef/ROW.php new file mode 100644 index 0000000000..560639a516 --- /dev/null +++ b/samples/Calculations/LookupRef/ROW.php @@ -0,0 +1,20 @@ +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()}"); +} diff --git a/samples/Calculations/LookupRef/ROWS.php b/samples/Calculations/LookupRef/ROWS.php new file mode 100644 index 0000000000..3cdf085bd9 --- /dev/null +++ b/samples/Calculations/LookupRef/ROWS.php @@ -0,0 +1,20 @@ +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()}"); +} diff --git a/src/PhpSpreadsheet/Calculation/LookupRef.php b/src/PhpSpreadsheet/Calculation/LookupRef.php index 39823a205d..f1a147f18c 100644 --- a/src/PhpSpreadsheet/Calculation/LookupRef.php +++ b/src/PhpSpreadsheet/Calculation/LookupRef.php @@ -529,59 +529,21 @@ public static function MATCH($lookupValue, $lookupArray, $matchType = 1) * Excel Function: * =INDEX(range_array, row_num, [column_num]) * - * @param mixed $arrayValues A range of cells or an array constant - * @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. + * @Deprecated 1.18.0 + * + * @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 */ - public static function INDEX($arrayValues, $rowNum = 0, $columnNum = 0) + public static function INDEX($matrix, $rowNum = 0, $columnNum = 0) { - $rowNum = Functions::flattenSingleValue($rowNum); - $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]; + return Matrix::index($matrix, $rowNum, $columnNum); } /** diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/Matrix.php b/src/PhpSpreadsheet/Calculation/LookupRef/Matrix.php index 99fd6e6e74..8859a28757 100644 --- a/src/PhpSpreadsheet/Calculation/LookupRef/Matrix.php +++ b/src/PhpSpreadsheet/Calculation/LookupRef/Matrix.php @@ -2,6 +2,8 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef; +use PhpOffice\PhpSpreadsheet\Calculation\Functions; + class Matrix { /** @@ -30,4 +32,73 @@ public static function transpose($matrixData) 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; + } } diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php b/src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php index 0cce806a5a..19d0d5ff8a 100644 --- a/src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php +++ b/src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php @@ -39,23 +39,23 @@ public static function COLUMN($cellAddress = null, ?Cell $cell = null) 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 @@ public static function COLUMN($cellAddress = null, ?Cell $cell = null) */ public static function COLUMNS($cellAddress = null) { - if ($cellAddress === null || $cellAddress === '') { + if ($cellAddress === null || (is_string($cellAddress) && trim($cellAddress) === '')) { return 1; } elseif (!is_array($cellAddress)) { return Functions::VALUE(); @@ -114,28 +114,29 @@ public static function ROW($cellAddress = null, ?Cell $pCell = null) } if (is_array($cellAddress)) { - foreach ($cellAddress as $columnKey => $rowValue) { - foreach ($rowValue as $rowKey => $cellValue) { + foreach ($cellAddress as $rowKey => $rowValue) { + foreach ($rowValue as $columnKey => $cellValue) { 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 @@ public static function ROW($cellAddress = null, ?Cell $pCell = null) */ public static function ROWS($cellAddress = null) { - if ($cellAddress === null || $cellAddress === '') { + if ($cellAddress === null || (is_string($cellAddress) && trim($cellAddress) === '')) { return 1; } elseif (!is_array($cellAddress)) { return Functions::VALUE(); diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndexTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndexTest.php index 8ff6693169..c84a504de2 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndexTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndexTest.php @@ -21,6 +21,7 @@ protected function setUp(): void public function testINDEX($expectedResult, ...$args): void { $result = LookupRef::INDEX(...$args); +// var_dump($result); self::assertEquals($expectedResult, $result); } diff --git a/tests/data/Calculation/LookupRef/INDEX.php b/tests/data/Calculation/LookupRef/INDEX.php index 55206270e0..157794ab18 100644 --- a/tests/data/Calculation/LookupRef/INDEX.php +++ b/tests/data/Calculation/LookupRef/INDEX.php @@ -54,7 +54,7 @@ -1, ], [ - '#VALUE!', // Expected + '#REF!', // Expected // Input [ '20' => ['R' => 1, 'S' => 3], @@ -63,6 +63,16 @@ 2, 10, ], + [ + '#REF!', // Expected + // Input + [ + '20' => ['R' => 1, 'S' => 3], + '21' => ['R' => 2, 'S' => 4], + ], + 10, + 2, + ], [ 4, // Expected // Input @@ -87,4 +97,64 @@ '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, + ], ]; diff --git a/tests/data/Calculation/LookupRef/ROW.php b/tests/data/Calculation/LookupRef/ROW.php index 89566011ef..62545c7674 100644 --- a/tests/data/Calculation/LookupRef/ROW.php +++ b/tests/data/Calculation/LookupRef/ROW.php @@ -17,6 +17,13 @@ [[10], [11], [12]], 'C10:D12', ], + [ + 4, + [ + 4 => ['B' => 'B5', 'C' => 'C5', 'D' => 'D5'], + 5 => ['B' => 'B5', 'C' => 'C5', 'D' => 'D5'], + ], + ], [ [[10], [11], [12]], 'Sheet1!C10:C12',