Skip to content

Commit 09cf6ab

Browse files
author
Mark Baker
authored
Merge pull request #2675 from PHPOffice/Refactoring-ReferenceHelper
Refactoring work on the ReferenceHelper
2 parents 004de10 + 750e42c commit 09cf6ab

File tree

3 files changed

+236
-121
lines changed

3 files changed

+236
-121
lines changed

src/PhpSpreadsheet/ReferenceHelper.php

Lines changed: 172 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
66
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
77
use PhpOffice\PhpSpreadsheet\Cell\DataType;
8+
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter;
89
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
910

1011
class ReferenceHelper
@@ -358,8 +359,12 @@ protected function adjustRowDimensions(Worksheet $worksheet, $beforeCellAddress,
358359
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
359360
* @param Worksheet $worksheet The worksheet that we're editing
360361
*/
361-
public function insertNewBefore($beforeCellAddress, $numberOfColumns, $numberOfRows, Worksheet $worksheet): void
362-
{
362+
public function insertNewBefore(
363+
string $beforeCellAddress,
364+
int $numberOfColumns,
365+
int $numberOfRows,
366+
Worksheet $worksheet
367+
): void {
363368
$remove = ($numberOfColumns < 0 || $numberOfRows < 0);
364369
$allCoordinates = $worksheet->getCoordinates();
365370

@@ -372,30 +377,12 @@ public function insertNewBefore($beforeCellAddress, $numberOfColumns, $numberOfR
372377

373378
// 1. Clear column strips if we are removing columns
374379
if ($numberOfColumns < 0 && $beforeColumn - 2 + $numberOfColumns > 0) {
375-
for ($i = 1; $i <= $highestRow - 1; ++$i) {
376-
for ($j = $beforeColumn - 1 + $numberOfColumns; $j <= $beforeColumn - 2; ++$j) {
377-
$coordinate = Coordinate::stringFromColumnIndex($j + 1) . $i;
378-
$worksheet->removeConditionalStyles($coordinate);
379-
if ($worksheet->cellExists($coordinate)) {
380-
$worksheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL);
381-
$worksheet->getCell($coordinate)->setXfIndex(0);
382-
}
383-
}
384-
}
380+
$this->clearColumnStrips($highestRow, $beforeColumn, $numberOfColumns, $worksheet);
385381
}
386382

387383
// 2. Clear row strips if we are removing rows
388384
if ($numberOfRows < 0 && $beforeRow - 1 + $numberOfRows > 0) {
389-
for ($i = $beforeColumn - 1; $i <= Coordinate::columnIndexFromString($highestColumn) - 1; ++$i) {
390-
for ($j = $beforeRow + $numberOfRows; $j <= $beforeRow - 1; ++$j) {
391-
$coordinate = Coordinate::stringFromColumnIndex($i + 1) . $j;
392-
$worksheet->removeConditionalStyles($coordinate);
393-
if ($worksheet->cellExists($coordinate)) {
394-
$worksheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL);
395-
$worksheet->getCell($coordinate)->setXfIndex(0);
396-
}
397-
}
398-
}
385+
$this->clearRowStrips($highestColumn, $beforeColumn, $beforeRow, $numberOfRows, $worksheet);
399386
}
400387

401388
// Find missing coordinates. This is important when inserting column before the last column
@@ -466,47 +453,11 @@ function ($coordinate) use ($allCoordinates) {
466453
$highestRow = $worksheet->getHighestRow();
467454

468455
if ($numberOfColumns > 0 && $beforeColumn - 2 > 0) {
469-
for ($i = $beforeRow; $i <= $highestRow - 1; ++$i) {
470-
// Style
471-
$coordinate = Coordinate::stringFromColumnIndex($beforeColumn - 1) . $i;
472-
if ($worksheet->cellExists($coordinate)) {
473-
$xfIndex = $worksheet->getCell($coordinate)->getXfIndex();
474-
$conditionalStyles = $worksheet->conditionalStylesExists($coordinate) ?
475-
$worksheet->getConditionalStyles($coordinate) : false;
476-
for ($j = $beforeColumn; $j <= $beforeColumn - 1 + $numberOfColumns; ++$j) {
477-
$worksheet->getCellByColumnAndRow($j, $i)->setXfIndex($xfIndex);
478-
if ($conditionalStyles) {
479-
$cloned = [];
480-
foreach ($conditionalStyles as $conditionalStyle) {
481-
$cloned[] = clone $conditionalStyle;
482-
}
483-
$worksheet->setConditionalStyles(Coordinate::stringFromColumnIndex($j) . $i, $cloned);
484-
}
485-
}
486-
}
487-
}
456+
$this->duplicateStylesByColumn($worksheet, $beforeColumn, $beforeRow, $highestRow, $numberOfColumns);
488457
}
489458

490459
if ($numberOfRows > 0 && $beforeRow - 1 > 0) {
491-
for ($i = $beforeColumn; $i <= Coordinate::columnIndexFromString($highestColumn); ++$i) {
492-
// Style
493-
$coordinate = Coordinate::stringFromColumnIndex($i) . ($beforeRow - 1);
494-
if ($worksheet->cellExists($coordinate)) {
495-
$xfIndex = $worksheet->getCell($coordinate)->getXfIndex();
496-
$conditionalStyles = $worksheet->conditionalStylesExists($coordinate) ?
497-
$worksheet->getConditionalStyles($coordinate) : false;
498-
for ($j = $beforeRow; $j <= $beforeRow - 1 + $numberOfRows; ++$j) {
499-
$worksheet->getCell(Coordinate::stringFromColumnIndex($i) . $j)->setXfIndex($xfIndex);
500-
if ($conditionalStyles) {
501-
$cloned = [];
502-
foreach ($conditionalStyles as $conditionalStyle) {
503-
$cloned[] = clone $conditionalStyle;
504-
}
505-
$worksheet->setConditionalStyles(Coordinate::stringFromColumnIndex($i) . $j, $cloned);
506-
}
507-
}
508-
}
509-
}
460+
$this->duplicateStylesByRow($worksheet, $beforeColumn, $beforeRow, $highestColumn, $numberOfRows);
510461
}
511462

512463
// Update worksheet: column dimensions
@@ -534,59 +485,7 @@ function ($coordinate) use ($allCoordinates) {
534485
$this->adjustProtectedCells($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
535486

536487
// Update worksheet: autofilter
537-
$autoFilter = $worksheet->getAutoFilter();
538-
$autoFilterRange = $autoFilter->getRange();
539-
if (!empty($autoFilterRange)) {
540-
if ($numberOfColumns != 0) {
541-
$autoFilterColumns = $autoFilter->getColumns();
542-
if (count($autoFilterColumns) > 0) {
543-
$column = '';
544-
$row = 0;
545-
sscanf($beforeCellAddress, '%[A-Z]%d', $column, $row);
546-
$columnIndex = Coordinate::columnIndexFromString($column);
547-
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($autoFilterRange);
548-
if ($columnIndex <= $rangeEnd[0]) {
549-
if ($numberOfColumns < 0) {
550-
// If we're actually deleting any columns that fall within the autofilter range,
551-
// then we delete any rules for those columns
552-
$deleteColumn = $columnIndex + $numberOfColumns - 1;
553-
$deleteCount = abs($numberOfColumns);
554-
for ($i = 1; $i <= $deleteCount; ++$i) {
555-
if (isset($autoFilterColumns[Coordinate::stringFromColumnIndex($deleteColumn + 1)])) {
556-
$autoFilter->clearColumn(Coordinate::stringFromColumnIndex($deleteColumn + 1));
557-
}
558-
++$deleteColumn;
559-
}
560-
}
561-
$startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0];
562-
563-
// Shuffle columns in autofilter range
564-
if ($numberOfColumns > 0) {
565-
$startColRef = $startCol;
566-
$endColRef = $rangeEnd[0];
567-
$toColRef = $rangeEnd[0] + $numberOfColumns;
568-
569-
do {
570-
$autoFilter->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef));
571-
--$endColRef;
572-
--$toColRef;
573-
} while ($startColRef <= $endColRef);
574-
} else {
575-
// For delete, we shuffle from beginning to end to avoid overwriting
576-
$startColID = Coordinate::stringFromColumnIndex($startCol);
577-
$toColID = Coordinate::stringFromColumnIndex($startCol + $numberOfColumns);
578-
$endColID = Coordinate::stringFromColumnIndex($rangeEnd[0] + 1);
579-
do {
580-
$autoFilter->shiftColumn($startColID, $toColID);
581-
++$startColID;
582-
++$toColID;
583-
} while ($startColID != $endColID);
584-
}
585-
}
586-
}
587-
}
588-
$worksheet->setAutoFilter($this->updateCellReference($autoFilterRange, $beforeCellAddress, $numberOfColumns, $numberOfRows));
589-
}
488+
$this->adjustAutoFilter($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
590489

591490
// Update worksheet: freeze pane
592491
if ($worksheet->getFreezePane()) {
@@ -601,7 +500,9 @@ function ($coordinate) use ($allCoordinates) {
601500

602501
// Page setup
603502
if ($worksheet->getPageSetup()->isPrintAreaSet()) {
604-
$worksheet->getPageSetup()->setPrintArea($this->updateCellReference($worksheet->getPageSetup()->getPrintArea(), $beforeCellAddress, $numberOfColumns, $numberOfRows));
503+
$worksheet->getPageSetup()->setPrintArea(
504+
$this->updateCellReference($worksheet->getPageSetup()->getPrintArea(), $beforeCellAddress, $numberOfColumns, $numberOfRows)
505+
);
605506
}
606507

607508
// Update worksheet: drawings
@@ -948,7 +849,7 @@ public function updateNamedFormulas(Spreadsheet $spreadsheet, $oldName = '', $ne
948849
foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
949850
foreach ($sheet->getCoordinates(false) as $coordinate) {
950851
$cell = $sheet->getCell($coordinate);
951-
if (($cell !== null) && ($cell->getDataType() == DataType::TYPE_FORMULA)) {
852+
if (($cell !== null) && ($cell->getDataType() === DataType::TYPE_FORMULA)) {
952853
$formula = $cell->getValue();
953854
if (strpos($formula, $oldName) !== false) {
954855
$formula = str_replace("'" . $oldName . "'!", "'" . $newName . "'!", $formula);
@@ -1038,6 +939,162 @@ private function updateSingleCellReference($cellReference = 'A1', $beforeCellAdd
1038939
return $newColumn . $newRow;
1039940
}
1040941

942+
private function clearColumnStrips(int $highestRow, int $beforeColumn, int $numberOfColumns, Worksheet $worksheet): void
943+
{
944+
for ($i = 1; $i <= $highestRow - 1; ++$i) {
945+
for ($j = $beforeColumn - 1 + $numberOfColumns; $j <= $beforeColumn - 2; ++$j) {
946+
$coordinate = Coordinate::stringFromColumnIndex($j + 1) . $i;
947+
$worksheet->removeConditionalStyles($coordinate);
948+
if ($worksheet->cellExists($coordinate)) {
949+
$worksheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL);
950+
$worksheet->getCell($coordinate)->setXfIndex(0);
951+
}
952+
}
953+
}
954+
}
955+
956+
private function clearRowStrips(string $highestColumn, int $beforeColumn, int $beforeRow, int $numberOfRows, Worksheet $worksheet): void
957+
{
958+
$lastColumnIndex = Coordinate::columnIndexFromString($highestColumn) - 1;
959+
960+
for ($i = $beforeColumn - 1; $i <= $lastColumnIndex; ++$i) {
961+
for ($j = $beforeRow + $numberOfRows; $j <= $beforeRow - 1; ++$j) {
962+
$coordinate = Coordinate::stringFromColumnIndex($i + 1) . $j;
963+
$worksheet->removeConditionalStyles($coordinate);
964+
if ($worksheet->cellExists($coordinate)) {
965+
$worksheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL);
966+
$worksheet->getCell($coordinate)->setXfIndex(0);
967+
}
968+
}
969+
}
970+
}
971+
972+
private function adjustAutoFilter(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void
973+
{
974+
$autoFilter = $worksheet->getAutoFilter();
975+
$autoFilterRange = $autoFilter->getRange();
976+
if (!empty($autoFilterRange)) {
977+
if ($numberOfColumns !== 0) {
978+
$autoFilterColumns = $autoFilter->getColumns();
979+
if (count($autoFilterColumns) > 0) {
980+
$column = '';
981+
$row = 0;
982+
sscanf($beforeCellAddress, '%[A-Z]%d', $column, $row);
983+
$columnIndex = Coordinate::columnIndexFromString($column);
984+
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($autoFilterRange);
985+
if ($columnIndex <= $rangeEnd[0]) {
986+
if ($numberOfColumns < 0) {
987+
$this->adjustAutoFilterDeleteRules($columnIndex, $numberOfColumns, $autoFilterColumns, $autoFilter);
988+
}
989+
$startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0];
990+
991+
// Shuffle columns in autofilter range
992+
if ($numberOfColumns > 0) {
993+
$this->adjustAutoFilterInsert($startCol, $numberOfColumns, $rangeEnd[0], $autoFilter);
994+
} else {
995+
$this->adjustAutoFilterDelete($startCol, $numberOfColumns, $rangeEnd[0], $autoFilter);
996+
}
997+
}
998+
}
999+
}
1000+
1001+
$worksheet->setAutoFilter(
1002+
$this->updateCellReference($autoFilterRange, $beforeCellAddress, $numberOfColumns, $numberOfRows)
1003+
);
1004+
}
1005+
}
1006+
1007+
private function adjustAutoFilterDeleteRules(int $columnIndex, int $numberOfColumns, array $autoFilterColumns, AutoFilter $autoFilter): void
1008+
{
1009+
// If we're actually deleting any columns that fall within the autofilter range,
1010+
// then we delete any rules for those columns
1011+
$deleteColumn = $columnIndex + $numberOfColumns - 1;
1012+
$deleteCount = abs($numberOfColumns);
1013+
1014+
for ($i = 1; $i <= $deleteCount; ++$i) {
1015+
$columnName = Coordinate::stringFromColumnIndex($deleteColumn + 1);
1016+
if (isset($autoFilterColumns[$columnName])) {
1017+
$autoFilter->clearColumn($columnName);
1018+
}
1019+
++$deleteColumn;
1020+
}
1021+
}
1022+
1023+
private function adjustAutoFilterInsert(int $startCol, int $numberOfColumns, int $rangeEnd, AutoFilter $autoFilter): void
1024+
{
1025+
$startColRef = $startCol;
1026+
$endColRef = $rangeEnd;
1027+
$toColRef = $rangeEnd + $numberOfColumns;
1028+
1029+
do {
1030+
$autoFilter->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef));
1031+
--$endColRef;
1032+
--$toColRef;
1033+
} while ($startColRef <= $endColRef);
1034+
}
1035+
1036+
private function adjustAutoFilterDelete(int $startCol, int $numberOfColumns, int $rangeEnd, AutoFilter $autoFilter): void
1037+
{
1038+
// For delete, we shuffle from beginning to end to avoid overwriting
1039+
$startColID = Coordinate::stringFromColumnIndex($startCol);
1040+
$toColID = Coordinate::stringFromColumnIndex($startCol + $numberOfColumns);
1041+
$endColID = Coordinate::stringFromColumnIndex($rangeEnd + 1);
1042+
1043+
do {
1044+
$autoFilter->shiftColumn($startColID, $toColID);
1045+
++$startColID;
1046+
++$toColID;
1047+
} while ($startColID !== $endColID);
1048+
}
1049+
1050+
private function duplicateStylesByColumn(Worksheet $worksheet, int $beforeColumn, int $beforeRow, int $highestRow, int $numberOfColumns): void
1051+
{
1052+
$beforeColumnName = Coordinate::stringFromColumnIndex($beforeColumn - 1);
1053+
for ($i = $beforeRow; $i <= $highestRow - 1; ++$i) {
1054+
// Style
1055+
$coordinate = $beforeColumnName . $i;
1056+
if ($worksheet->cellExists($coordinate)) {
1057+
$xfIndex = $worksheet->getCell($coordinate)->getXfIndex();
1058+
$conditionalStyles = $worksheet->conditionalStylesExists($coordinate) ?
1059+
$worksheet->getConditionalStyles($coordinate) : false;
1060+
for ($j = $beforeColumn; $j <= $beforeColumn - 1 + $numberOfColumns; ++$j) {
1061+
$worksheet->getCellByColumnAndRow($j, $i)->setXfIndex($xfIndex);
1062+
if ($conditionalStyles) {
1063+
$cloned = [];
1064+
foreach ($conditionalStyles as $conditionalStyle) {
1065+
$cloned[] = clone $conditionalStyle;
1066+
}
1067+
$worksheet->setConditionalStyles(Coordinate::stringFromColumnIndex($j) . $i, $cloned);
1068+
}
1069+
}
1070+
}
1071+
}
1072+
}
1073+
1074+
private function duplicateStylesByRow(Worksheet $worksheet, int $beforeColumn, int $beforeRow, string $highestColumn, int $numberOfRows): void
1075+
{
1076+
$highestColumnIndex = Coordinate::columnIndexFromString($highestColumn);
1077+
for ($i = $beforeColumn; $i <= $highestColumnIndex; ++$i) {
1078+
// Style
1079+
$coordinate = Coordinate::stringFromColumnIndex($i) . ($beforeRow - 1);
1080+
if ($worksheet->cellExists($coordinate)) {
1081+
$xfIndex = $worksheet->getCell($coordinate)->getXfIndex();
1082+
$conditionalStyles = $worksheet->conditionalStylesExists($coordinate) ?
1083+
$worksheet->getConditionalStyles($coordinate) : false;
1084+
for ($j = $beforeRow; $j <= $beforeRow - 1 + $numberOfRows; ++$j) {
1085+
$worksheet->getCell(Coordinate::stringFromColumnIndex($i) . $j)->setXfIndex($xfIndex);
1086+
if ($conditionalStyles) {
1087+
$cloned = [];
1088+
foreach ($conditionalStyles as $conditionalStyle) {
1089+
$cloned[] = clone $conditionalStyle;
1090+
}
1091+
$worksheet->setConditionalStyles(Coordinate::stringFromColumnIndex($i) . $j, $cloned);
1092+
}
1093+
}
1094+
}
1095+
}
1096+
}
1097+
10411098
/**
10421099
* __clone implementation. Cloning should not be allowed in a Singleton!
10431100
*/

src/PhpSpreadsheet/Worksheet/Worksheet.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2219,25 +2219,25 @@ public function removeColumn($column, $numberOfColumns = 1)
22192219
$highestColumnIndex = Coordinate::columnIndexFromString($highestColumn);
22202220
$pColumnIndex = Coordinate::columnIndexFromString($column);
22212221

2222-
if ($pColumnIndex > $highestColumnIndex) {
2223-
return $this;
2224-
}
2225-
22262222
$holdColumnDimensions = $this->removeColumnDimensions($pColumnIndex, $numberOfColumns);
22272223

22282224
$column = Coordinate::stringFromColumnIndex($pColumnIndex + $numberOfColumns);
22292225
$objReferenceHelper = ReferenceHelper::getInstance();
22302226
$objReferenceHelper->insertNewBefore($column . '1', -$numberOfColumns, 0, $this);
22312227

2228+
$this->columnDimensions = $holdColumnDimensions;
2229+
2230+
if ($pColumnIndex > $highestColumnIndex) {
2231+
return $this;
2232+
}
2233+
22322234
$maxPossibleColumnsToBeRemoved = $highestColumnIndex - $pColumnIndex + 1;
22332235

22342236
for ($c = 0, $n = min($maxPossibleColumnsToBeRemoved, $numberOfColumns); $c < $n; ++$c) {
22352237
$this->getCellCollection()->removeColumn($highestColumn);
22362238
$highestColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($highestColumn) - 1);
22372239
}
22382240

2239-
$this->columnDimensions = $holdColumnDimensions;
2240-
22412241
$this->garbageCollect();
22422242

22432243
return $this;

0 commit comments

Comments
 (0)