diff --git a/CHANGELOG.md b/CHANGELOG.md index 4664f049..a6736bfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Memory and speed improvements, particularly for the Cell Collection, and the Writers. See [the Discussion section on github](https://github.com/PHPOffice/PhpSpreadsheet/discussions/2821) for details of performance across versions +- Improved performance for removing rows/columns from a worksheet ### Deprecated diff --git a/docs/topics/recipes.md b/docs/topics/recipes.md index f25a9119..e4313382 100644 --- a/docs/topics/recipes.md +++ b/docs/topics/recipes.md @@ -1348,7 +1348,7 @@ Removing a merge can be done using the unmergeCells method: $spreadsheet->getActiveSheet()->unmergeCells('A18:E22'); ``` -## Inserting rows/columns +## Inserting or Removing rows/columns You can insert/remove rows/columns at a specific position. The following code inserts 2 new rows, right before row 7: @@ -1356,6 +1356,23 @@ code inserts 2 new rows, right before row 7: ```php $spreadsheet->getActiveSheet()->insertNewRowBefore(7, 2); ``` +while +```php +$spreadsheet->getActiveSheet()->removeRow(7, 2); +``` +will remove 2 rows starting at row number 7 (ie. rows 7 and 8). + +Equivalent methods exist for inserting/removing columns: + +```php +$spreadsheet->getActiveSheet()->removeColumn('C', 2); +``` + +All subsequent rows (or columns) will be moved to allow the insertion (or removal) with all formulae referencing thise cells adjusted accordingly. + +Note that this is a fairly intensive process, particularly with large worksheets, and especially if you are inserting/removing rows/columns from near beginning of the worksheet. + +If you need to insert/remove several consecutive rows/columns, always use the second argument rather than making multiple calls to insert/remove a single row/column if possible. ## Add a drawing to a worksheet diff --git a/src/PhpSpreadsheet/ReferenceHelper.php b/src/PhpSpreadsheet/ReferenceHelper.php index 36c85edf..8caaab18 100644 --- a/src/PhpSpreadsheet/ReferenceHelper.php +++ b/src/PhpSpreadsheet/ReferenceHelper.php @@ -367,7 +367,6 @@ class ReferenceHelper Worksheet $worksheet ): void { $remove = ($numberOfColumns < 0 || $numberOfRows < 0); - $allCoordinates = $worksheet->getCoordinates(); if ( $this->cellReferenceHelper === null || @@ -394,12 +393,13 @@ class ReferenceHelper } // Find missing coordinates. This is important when inserting column before the last column + $cellCollection = $worksheet->getCellCollection(); $missingCoordinates = array_filter( array_map(function ($row) use ($highestColumn) { return $highestColumn . $row; }, range(1, $highestRow)), - function ($coordinate) use ($allCoordinates) { - return in_array($coordinate, $allCoordinates, true) === false; + function ($coordinate) use ($cellCollection) { + return $cellCollection->has($coordinate) === false; } ); @@ -408,16 +408,15 @@ class ReferenceHelper foreach ($missingCoordinates as $coordinate) { $worksheet->createNewCell($coordinate); } - - // Refresh all coordinates - $allCoordinates = $worksheet->getCoordinates(); } - // Loop through cells, bottom-up, and change cell coordinate + $allCoordinates = $worksheet->getCoordinates(); if ($remove) { // It's faster to reverse and pop than to use unshift, especially with large cell collections $allCoordinates = array_reverse($allCoordinates); } + + // Loop through cells, bottom-up, and change cell coordinate while ($coordinate = array_pop($allCoordinates)) { $cell = $worksheet->getCell($coordinate); $cellIndex = Coordinate::columnIndexFromString($cell->getColumn()); @@ -924,34 +923,43 @@ class ReferenceHelper private function clearColumnStrips(int $highestRow, int $beforeColumn, int $numberOfColumns, Worksheet $worksheet): void { - for ($i = 1; $i <= $highestRow - 1; ++$i) { - for ($j = $beforeColumn - 1 + $numberOfColumns; $j <= $beforeColumn - 2; ++$j) { - $coordinate = Coordinate::stringFromColumnIndex($j + 1) . $i; - $worksheet->removeConditionalStyles($coordinate); - if ($worksheet->cellExists($coordinate)) { - $worksheet->getCell($coordinate)->setValueExplicit(null, DataType::TYPE_NULL); - $worksheet->getCell($coordinate)->setXfIndex(0); - } + $startColumnId = Coordinate::stringFromColumnIndex($beforeColumn + $numberOfColumns); + $endColumnId = Coordinate::stringFromColumnIndex($beforeColumn); + + for ($row = 1; $row <= $highestRow - 1; ++$row) { + for ($column = $startColumnId; $column !== $endColumnId; ++$column) { + $coordinate = $column . $row; + $this->clearStripCell($worksheet, $coordinate); } } } private function clearRowStrips(string $highestColumn, int $beforeColumn, int $beforeRow, int $numberOfRows, Worksheet $worksheet): void { - $lastColumnIndex = Coordinate::columnIndexFromString($highestColumn) - 1; + $startColumnId = Coordinate::stringFromColumnIndex($beforeColumn); + ++$highestColumn; - for ($i = $beforeColumn - 1; $i <= $lastColumnIndex; ++$i) { - for ($j = $beforeRow + $numberOfRows; $j <= $beforeRow - 1; ++$j) { - $coordinate = Coordinate::stringFromColumnIndex($i + 1) . $j; - $worksheet->removeConditionalStyles($coordinate); - if ($worksheet->cellExists($coordinate)) { - $worksheet->getCell($coordinate)->setValueExplicit(null, DataType::TYPE_NULL); - $worksheet->getCell($coordinate)->setXfIndex(0); - } + for ($column = $startColumnId; $column !== $highestColumn; ++$column) { + for ($row = $beforeRow + $numberOfRows; $row <= $beforeRow - 1; ++$row) { + $coordinate = $column . $row; + $this->clearStripCell($worksheet, $coordinate); } } } + private function clearStripCell(Worksheet $worksheet, string $coordinate): void + { + $worksheet->removeConditionalStyles($coordinate); + $worksheet->setHyperlink($coordinate); + $worksheet->setDataValidation($coordinate); + $worksheet->removeComment($coordinate); + + if ($worksheet->cellExists($coordinate)) { + $worksheet->getCell($coordinate)->setValueExplicit(null, DataType::TYPE_NULL); + $worksheet->getCell($coordinate)->setXfIndex(0); + } + } + private function adjustAutoFilter(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns): void { $autoFilter = $worksheet->getAutoFilter();