diff --git a/CHANGELOG.md b/CHANGELOG.md index ca0ce021..cc26e624 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Fixed +- Allow `INDIRECT()` to accept row/column ranges as well as cell ranges [PR #2687](https://github.com/PHPOffice/PhpSpreadsheet/pull/2687) - Fix bug when deleting cells with hyperlinks, where the hyperlink was then being "inherited" by whatever cell moved to that cell address. - Fix bug in Conditional Formatting in the Xls Writer that resulted in a broken file when there were multiple conditional ranges in a worksheet. - Fix Conditional Formatting in the Xls Writer to work with rules that contain string literals, cell references and formulae. diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php b/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php index d0c13a5c..417a1f79 100644 --- a/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php +++ b/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php @@ -72,11 +72,17 @@ class Indirect [$cellAddress, $worksheet, $sheetName] = Helpers::extractWorksheet($cellAddress, $cell); + if (preg_match('/^' . Calculation::CALCULATION_REGEXP_COLUMNRANGE_RELATIVE . '$/miu', $cellAddress, $matches)) { + $cellAddress = self::handleRowColumnRanges($worksheet, ...explode(':', $cellAddress)); + } elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_ROWRANGE_RELATIVE . '$/miu', $cellAddress, $matches)) { + $cellAddress = self::handleRowColumnRanges($worksheet, ...explode(':', $cellAddress)); + } + [$cellAddress1, $cellAddress2, $cellAddress] = Helpers::extractCellAddresses($cellAddress, $a1, $cell->getWorkSheet(), $sheetName); if ( - (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress1, $matches)) || - (($cellAddress2 !== null) && (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress2, $matches))) + (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $cellAddress1, $matches)) || + (($cellAddress2 !== null) && (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $cellAddress2, $matches))) ) { return ExcelError::REF(); } @@ -95,4 +101,22 @@ class Indirect return Calculation::getInstance($worksheet !== null ? $worksheet->getParent() : null) ->extractCellRange($cellAddress, $worksheet, false); } + + private static function handleRowColumnRanges(?Worksheet $worksheet, string $start, string $end): string + { + // Being lazy, we're only checking a single row/column to get the max + if (ctype_digit($start) && $start <= 1048576) { + // Max 16,384 columns for Excel2007 + $endColRef = ($worksheet !== null) ? $worksheet->getHighestDataColumn((int) $start) : 'XFD'; + + return "A{$start}:{$endColRef}{$end}"; + } elseif (ctype_alpha($start) && strlen($start) <= 3) { + // Max 1,048,576 rows for Excel2007 + $endRowRef = ($worksheet !== null) ? $worksheet->getHighestDataRow($start) : 1048576; + + return "{$start}1:{$end}{$endRowRef}"; + } + + return "{$start}:{$end}"; + } } diff --git a/tests/data/Calculation/LookupRef/INDIRECT.php b/tests/data/Calculation/LookupRef/INDIRECT.php index b8519657..7dc2515a 100644 --- a/tests/data/Calculation/LookupRef/INDIRECT.php +++ b/tests/data/Calculation/LookupRef/INDIRECT.php @@ -34,4 +34,8 @@ return [ 'supply a1 argument as int' => [900, 'A2:A4', 1], 'supply a1 argument as float' => [900, 'A2:A4', 7.3], 'supply a1 argument as string not permitted' => ['#VALUE!', 'A2:A4', '1'], + 'row range' => [600, '1:3'], + 'column range' => [1500, 'A:C'], + 'row range on different sheet' => [66, 'OtherSheet!1:3'], + 'column range on different sheet' => [165, 'OtherSheet!A:C'], ];