diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 8a8886c21e..ca2feb429a 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -21,7 +21,7 @@ 'braces' => true, 'cast_spaces' => true, 'class_attributes_separation' => ['elements' => ['method' => 'one', 'property' => 'one']], // const are often grouped with other related const - 'class_definition' => true, + 'class_definition' => false, 'class_keyword_remove' => false, // ::class keyword gives us better support in IDE 'combine_consecutive_issets' => true, 'combine_consecutive_unsets' => true, diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d20a9cc4e..35ed928e67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Added -- Implementation of the FILTER(), SORT(), SORTBY() and UNIQUE() Lookup/Reference (array) functions +- Introduced CellAddress, CellRange, RowRange and ColumnRange value objects that can be used as an alternative to a string value (e.g. `'C5'`, `'B2:D4'`, `'2:2'` or `'B:C'`) in appropriate contexts. +- Implementation of the FILTER(), SORT(), SORTBY() and UNIQUE() Lookup/Reference (array) functions. - Implementation of the ISREF() Information function. - Added support for reading "formatted" numeric values from Csv files; although default behaviour of reading these values as strings is preserved. diff --git a/src/PhpSpreadsheet/Cell/AddressRange.php b/src/PhpSpreadsheet/Cell/AddressRange.php new file mode 100644 index 0000000000..5475231769 --- /dev/null +++ b/src/PhpSpreadsheet/Cell/AddressRange.php @@ -0,0 +1,22 @@ +cellAddress = str_replace('$', '', $cellAddress); [$this->columnName, $rowId] = Coordinate::coordinateFromString($cellAddress); diff --git a/src/PhpSpreadsheet/Cell/CellRange.php b/src/PhpSpreadsheet/Cell/CellRange.php index 4880fa6ca7..908a0d076a 100644 --- a/src/PhpSpreadsheet/Cell/CellRange.php +++ b/src/PhpSpreadsheet/Cell/CellRange.php @@ -5,7 +5,7 @@ use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; -class CellRange +class CellRange implements AddressRange { /** * @var CellAddress @@ -34,8 +34,8 @@ private function validateFromTo(CellAddress $from, CellAddress $to): void $toWorksheet = $to->worksheet(); $this->validateWorksheets($fromWorksheet, $toWorksheet); - $this->from = CellAddress::fromColumnAndRow($firstColumn, $firstRow, $fromWorksheet); - $this->to = CellAddress::fromColumnAndRow($lastColumn, $lastRow, $toWorksheet); + $this->from = $this->cellAddressWrapper($firstColumn, $firstRow, $fromWorksheet); + $this->to = $this->cellAddressWrapper($lastColumn, $lastRow, $toWorksheet); } private function validateWorksheets(?Worksheet $fromWorksheet, ?Worksheet $toWorksheet): void @@ -54,18 +54,76 @@ private function validateWorksheets(?Worksheet $fromWorksheet, ?Worksheet $toWor } } + private function cellAddressWrapper(int $column, int $row, ?Worksheet $worksheet = null): CellAddress + { + $cellAddress = Coordinate::stringFromColumnIndex($column) . (string) $row; + + return new class ($cellAddress, $worksheet) extends CellAddress { + public function nextRow(int $offset = 1): CellAddress + { + /** @var CellAddress $result */ + $result = parent::nextRow($offset); + $this->rowId = $result->rowId; + $this->cellAddress = $result->cellAddress; + + return $this; + } + + public function previousRow(int $offset = 1): CellAddress + { + /** @var CellAddress $result */ + $result = parent::previousRow($offset); + $this->rowId = $result->rowId; + $this->cellAddress = $result->cellAddress; + + return $this; + } + + public function nextColumn(int $offset = 1): CellAddress + { + /** @var CellAddress $result */ + $result = parent::nextColumn($offset); + $this->columnId = $result->columnId; + $this->columnName = $result->columnName; + $this->cellAddress = $result->cellAddress; + + return $this; + } + + public function previousColumn(int $offset = 1): CellAddress + { + /** @var CellAddress $result */ + $result = parent::previousColumn($offset); + $this->columnId = $result->columnId; + $this->columnName = $result->columnName; + $this->cellAddress = $result->cellAddress; + + return $this; + } + }; + } + public function from(): CellAddress { + // Re-order from/to in case the cell addresses have been modified + $this->validateFromTo($this->from, $this->to); + return $this->from; } public function to(): CellAddress { + // Re-order from/to in case the cell addresses have been modified + $this->validateFromTo($this->from, $this->to); + return $this->to; } public function __toString(): string { + // Re-order from/to in case the cell addresses have been modified + $this->validateFromTo($this->from, $this->to); + if ($this->from->cellAddress() === $this->to->cellAddress()) { return "{$this->from->fullCellAddress()}"; } diff --git a/src/PhpSpreadsheet/Cell/ColumnRange.php b/src/PhpSpreadsheet/Cell/ColumnRange.php index dbd91bb663..1e521a1319 100644 --- a/src/PhpSpreadsheet/Cell/ColumnRange.php +++ b/src/PhpSpreadsheet/Cell/ColumnRange.php @@ -4,10 +4,8 @@ use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; -class ColumnRange +class ColumnRange implements AddressRange { - private const MAX_ROW = 1048576; - /** * @var ?Worksheet */ @@ -107,7 +105,7 @@ public function toCellRange(): CellRange { return new CellRange( CellAddress::fromColumnAndRow($this->from, 1, $this->worksheet), - CellAddress::fromColumnAndRow($this->to, self::MAX_ROW) + CellAddress::fromColumnAndRow($this->to, AddressRange::MAX_ROW) ); } diff --git a/src/PhpSpreadsheet/Cell/RowRange.php b/src/PhpSpreadsheet/Cell/RowRange.php index bef971115a..38e6c1415b 100644 --- a/src/PhpSpreadsheet/Cell/RowRange.php +++ b/src/PhpSpreadsheet/Cell/RowRange.php @@ -4,10 +4,8 @@ use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; -class RowRange +class RowRange implements AddressRange { - private const MAX_COLUMN = 'XFD'; - /** * @var ?Worksheet */ @@ -78,7 +76,7 @@ public function toCellRange(): CellRange { return new CellRange( CellAddress::fromColumnAndRow(Coordinate::columnIndexFromString('A'), $this->from, $this->worksheet), - CellAddress::fromColumnAndRow(Coordinate::columnIndexFromString(self::MAX_COLUMN), $this->to) + CellAddress::fromColumnAndRow(Coordinate::columnIndexFromString(AddressRange::MAX_COLUMN), $this->to) ); } diff --git a/tests/PhpSpreadsheetTests/Cell/CellRangeTest.php b/tests/PhpSpreadsheetTests/Cell/CellRangeTest.php index 856f0e5099..1557a69827 100644 --- a/tests/PhpSpreadsheetTests/Cell/CellRangeTest.php +++ b/tests/PhpSpreadsheetTests/Cell/CellRangeTest.php @@ -110,4 +110,44 @@ public function testCreateCellRangeWithMismatchedSpreadsheets(): void $to = CellAddress::fromCellAddress('E2', $worksheet2); new CellRange($from, $to); } + + public function testShiftRangeTo(): void + { + $from = CellAddress::fromCellAddress('B5'); + $to = CellAddress::fromCellAddress('E2'); + $cellRange = new CellRange($from, $to); + self::assertSame('B2:E5', (string) $cellRange); + + $cellRange->to() + ->nextColumn(2) + ->nextRow(2); + + self::assertSame('B2', (string) $cellRange->from()); + self::assertSame('G7', (string) $cellRange->to()); + self::assertSame('B2:G7', (string) $cellRange); + + $cellRange->to() + ->previousColumn() + ->previousRow(); + + self::assertSame('B2', (string) $cellRange->from()); + self::assertSame('F6', (string) $cellRange->to()); + self::assertSame('B2:F6', (string) $cellRange); + } + + public function testShiftRangeFrom(): void + { + $from = CellAddress::fromCellAddress('B5'); + $to = CellAddress::fromCellAddress('E2'); + $cellRange = new CellRange($from, $to); + self::assertSame('B2:E5', (string) $cellRange); + + $cellRange->from() + ->nextColumn(5) + ->nextRow(5); + + self::assertSame('E5', (string) $cellRange->from()); + self::assertSame('G7', (string) $cellRange->to()); + self::assertSame('E5:G7', (string) $cellRange); + } }