Merge pull request #2888 from PHPOffice/ReferenceHelper-Performance-Improvements

Reference helper performance improvements
This commit is contained in:
Mark Baker 2022-06-15 13:23:51 +02:00 committed by GitHub
commit 1a404e8dcf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 51 additions and 25 deletions

View File

@ -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. - 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 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 ### Deprecated

View File

@ -1348,7 +1348,7 @@ Removing a merge can be done using the unmergeCells method:
$spreadsheet->getActiveSheet()->unmergeCells('A18:E22'); $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 You can insert/remove rows/columns at a specific position. The following
code inserts 2 new rows, right before row 7: code inserts 2 new rows, right before row 7:
@ -1356,6 +1356,23 @@ code inserts 2 new rows, right before row 7:
```php ```php
$spreadsheet->getActiveSheet()->insertNewRowBefore(7, 2); $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 ## Add a drawing to a worksheet

View File

@ -367,7 +367,6 @@ class ReferenceHelper
Worksheet $worksheet Worksheet $worksheet
): void { ): void {
$remove = ($numberOfColumns < 0 || $numberOfRows < 0); $remove = ($numberOfColumns < 0 || $numberOfRows < 0);
$allCoordinates = $worksheet->getCoordinates();
if ( if (
$this->cellReferenceHelper === null || $this->cellReferenceHelper === null ||
@ -394,12 +393,13 @@ class ReferenceHelper
} }
// Find missing coordinates. This is important when inserting column before the last column // Find missing coordinates. This is important when inserting column before the last column
$cellCollection = $worksheet->getCellCollection();
$missingCoordinates = array_filter( $missingCoordinates = array_filter(
array_map(function ($row) use ($highestColumn) { array_map(function ($row) use ($highestColumn) {
return $highestColumn . $row; return $highestColumn . $row;
}, range(1, $highestRow)), }, range(1, $highestRow)),
function ($coordinate) use ($allCoordinates) { function ($coordinate) use ($cellCollection) {
return in_array($coordinate, $allCoordinates, true) === false; return $cellCollection->has($coordinate) === false;
} }
); );
@ -408,16 +408,15 @@ class ReferenceHelper
foreach ($missingCoordinates as $coordinate) { foreach ($missingCoordinates as $coordinate) {
$worksheet->createNewCell($coordinate); $worksheet->createNewCell($coordinate);
} }
// Refresh all coordinates
$allCoordinates = $worksheet->getCoordinates();
} }
// Loop through cells, bottom-up, and change cell coordinate $allCoordinates = $worksheet->getCoordinates();
if ($remove) { if ($remove) {
// It's faster to reverse and pop than to use unshift, especially with large cell collections // It's faster to reverse and pop than to use unshift, especially with large cell collections
$allCoordinates = array_reverse($allCoordinates); $allCoordinates = array_reverse($allCoordinates);
} }
// Loop through cells, bottom-up, and change cell coordinate
while ($coordinate = array_pop($allCoordinates)) { while ($coordinate = array_pop($allCoordinates)) {
$cell = $worksheet->getCell($coordinate); $cell = $worksheet->getCell($coordinate);
$cellIndex = Coordinate::columnIndexFromString($cell->getColumn()); $cellIndex = Coordinate::columnIndexFromString($cell->getColumn());
@ -924,33 +923,42 @@ class ReferenceHelper
private function clearColumnStrips(int $highestRow, int $beforeColumn, int $numberOfColumns, Worksheet $worksheet): void private function clearColumnStrips(int $highestRow, int $beforeColumn, int $numberOfColumns, Worksheet $worksheet): void
{ {
for ($i = 1; $i <= $highestRow - 1; ++$i) { $startColumnId = Coordinate::stringFromColumnIndex($beforeColumn + $numberOfColumns);
for ($j = $beforeColumn - 1 + $numberOfColumns; $j <= $beforeColumn - 2; ++$j) { $endColumnId = Coordinate::stringFromColumnIndex($beforeColumn);
$coordinate = Coordinate::stringFromColumnIndex($j + 1) . $i;
$worksheet->removeConditionalStyles($coordinate); for ($row = 1; $row <= $highestRow - 1; ++$row) {
if ($worksheet->cellExists($coordinate)) { for ($column = $startColumnId; $column !== $endColumnId; ++$column) {
$worksheet->getCell($coordinate)->setValueExplicit(null, DataType::TYPE_NULL); $coordinate = $column . $row;
$worksheet->getCell($coordinate)->setXfIndex(0); $this->clearStripCell($worksheet, $coordinate);
}
} }
} }
} }
private function clearRowStrips(string $highestColumn, int $beforeColumn, int $beforeRow, int $numberOfRows, Worksheet $worksheet): void 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 ($column = $startColumnId; $column !== $highestColumn; ++$column) {
for ($j = $beforeRow + $numberOfRows; $j <= $beforeRow - 1; ++$j) { for ($row = $beforeRow + $numberOfRows; $row <= $beforeRow - 1; ++$row) {
$coordinate = Coordinate::stringFromColumnIndex($i + 1) . $j; $coordinate = $column . $row;
$this->clearStripCell($worksheet, $coordinate);
}
}
}
private function clearStripCell(Worksheet $worksheet, string $coordinate): void
{
$worksheet->removeConditionalStyles($coordinate); $worksheet->removeConditionalStyles($coordinate);
$worksheet->setHyperlink($coordinate);
$worksheet->setDataValidation($coordinate);
$worksheet->removeComment($coordinate);
if ($worksheet->cellExists($coordinate)) { if ($worksheet->cellExists($coordinate)) {
$worksheet->getCell($coordinate)->setValueExplicit(null, DataType::TYPE_NULL); $worksheet->getCell($coordinate)->setValueExplicit(null, DataType::TYPE_NULL);
$worksheet->getCell($coordinate)->setXfIndex(0); $worksheet->getCell($coordinate)->setXfIndex(0);
} }
} }
}
}
private function adjustAutoFilter(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns): void private function adjustAutoFilter(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns): void
{ {