Skip to content

Commit 750e42c

Browse files
author
MarkBaker
committed
More refactoring of the reference helper; and unit tests to validate insertion/removal of rows/columns from an autofilter range
1 parent a8d9cd7 commit 750e42c

File tree

2 files changed

+170
-74
lines changed

2 files changed

+170
-74
lines changed

src/PhpSpreadsheet/ReferenceHelper.php

Lines changed: 112 additions & 74 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

@@ -448,49 +453,11 @@ function ($coordinate) use ($allCoordinates) {
448453
$highestRow = $worksheet->getHighestRow();
449454

450455
if ($numberOfColumns > 0 && $beforeColumn - 2 > 0) {
451-
$beforeColumnName = Coordinate::stringFromColumnIndex($beforeColumn - 1);
452-
for ($i = $beforeRow; $i <= $highestRow - 1; ++$i) {
453-
// Style
454-
$coordinate = $beforeColumnName . $i;
455-
if ($worksheet->cellExists($coordinate)) {
456-
$xfIndex = $worksheet->getCell($coordinate)->getXfIndex();
457-
$conditionalStyles = $worksheet->conditionalStylesExists($coordinate) ?
458-
$worksheet->getConditionalStyles($coordinate) : false;
459-
for ($j = $beforeColumn; $j <= $beforeColumn - 1 + $numberOfColumns; ++$j) {
460-
$worksheet->getCellByColumnAndRow($j, $i)->setXfIndex($xfIndex);
461-
if ($conditionalStyles) {
462-
$cloned = [];
463-
foreach ($conditionalStyles as $conditionalStyle) {
464-
$cloned[] = clone $conditionalStyle;
465-
}
466-
$worksheet->setConditionalStyles(Coordinate::stringFromColumnIndex($j) . $i, $cloned);
467-
}
468-
}
469-
}
470-
}
456+
$this->duplicateStylesByColumn($worksheet, $beforeColumn, $beforeRow, $highestRow, $numberOfColumns);
471457
}
472458

473459
if ($numberOfRows > 0 && $beforeRow - 1 > 0) {
474-
$highestColumnIndex = Coordinate::columnIndexFromString($highestColumn);
475-
for ($i = $beforeColumn; $i <= $highestColumnIndex; ++$i) {
476-
// Style
477-
$coordinate = Coordinate::stringFromColumnIndex($i) . ($beforeRow - 1);
478-
if ($worksheet->cellExists($coordinate)) {
479-
$xfIndex = $worksheet->getCell($coordinate)->getXfIndex();
480-
$conditionalStyles = $worksheet->conditionalStylesExists($coordinate) ?
481-
$worksheet->getConditionalStyles($coordinate) : false;
482-
for ($j = $beforeRow; $j <= $beforeRow - 1 + $numberOfRows; ++$j) {
483-
$worksheet->getCell(Coordinate::stringFromColumnIndex($i) . $j)->setXfIndex($xfIndex);
484-
if ($conditionalStyles) {
485-
$cloned = [];
486-
foreach ($conditionalStyles as $conditionalStyle) {
487-
$cloned[] = clone $conditionalStyle;
488-
}
489-
$worksheet->setConditionalStyles(Coordinate::stringFromColumnIndex($i) . $j, $cloned);
490-
}
491-
}
492-
}
493-
}
460+
$this->duplicateStylesByRow($worksheet, $beforeColumn, $beforeRow, $highestColumn, $numberOfRows);
494461
}
495462

496463
// Update worksheet: column dimensions
@@ -533,7 +500,9 @@ function ($coordinate) use ($allCoordinates) {
533500

534501
// Page setup
535502
if ($worksheet->getPageSetup()->isPrintAreaSet()) {
536-
$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+
);
537506
}
538507

539508
// Update worksheet: drawings
@@ -880,7 +849,7 @@ public function updateNamedFormulas(Spreadsheet $spreadsheet, $oldName = '', $ne
880849
foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
881850
foreach ($sheet->getCoordinates(false) as $coordinate) {
882851
$cell = $sheet->getCell($coordinate);
883-
if (($cell !== null) && ($cell->getDataType() == DataType::TYPE_FORMULA)) {
852+
if (($cell !== null) && ($cell->getDataType() === DataType::TYPE_FORMULA)) {
884853
$formula = $cell->getValue();
885854
if (strpos($formula, $oldName) !== false) {
886855
$formula = str_replace("'" . $oldName . "'!", "'" . $newName . "'!", $formula);
@@ -1005,7 +974,7 @@ private function adjustAutoFilter(Worksheet $worksheet, string $beforeCellAddres
1005974
$autoFilter = $worksheet->getAutoFilter();
1006975
$autoFilterRange = $autoFilter->getRange();
1007976
if (!empty($autoFilterRange)) {
1008-
if ($numberOfColumns != 0) {
977+
if ($numberOfColumns !== 0) {
1009978
$autoFilterColumns = $autoFilter->getColumns();
1010979
if (count($autoFilterColumns) > 0) {
1011980
$column = '';
@@ -1015,45 +984,114 @@ private function adjustAutoFilter(Worksheet $worksheet, string $beforeCellAddres
1015984
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($autoFilterRange);
1016985
if ($columnIndex <= $rangeEnd[0]) {
1017986
if ($numberOfColumns < 0) {
1018-
// If we're actually deleting any columns that fall within the autofilter range,
1019-
// then we delete any rules for those columns
1020-
$deleteColumn = $columnIndex + $numberOfColumns - 1;
1021-
$deleteCount = abs($numberOfColumns);
1022-
for ($i = 1; $i <= $deleteCount; ++$i) {
1023-
if (isset($autoFilterColumns[Coordinate::stringFromColumnIndex($deleteColumn + 1)])) {
1024-
$autoFilter->clearColumn(Coordinate::stringFromColumnIndex($deleteColumn + 1));
1025-
}
1026-
++$deleteColumn;
1027-
}
987+
$this->adjustAutoFilterDeleteRules($columnIndex, $numberOfColumns, $autoFilterColumns, $autoFilter);
1028988
}
1029989
$startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0];
1030990

1031991
// Shuffle columns in autofilter range
1032992
if ($numberOfColumns > 0) {
1033-
$startColRef = $startCol;
1034-
$endColRef = $rangeEnd[0];
1035-
$toColRef = $rangeEnd[0] + $numberOfColumns;
1036-
1037-
do {
1038-
$autoFilter->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef));
1039-
--$endColRef;
1040-
--$toColRef;
1041-
} while ($startColRef <= $endColRef);
993+
$this->adjustAutoFilterInsert($startCol, $numberOfColumns, $rangeEnd[0], $autoFilter);
1042994
} else {
1043-
// For delete, we shuffle from beginning to end to avoid overwriting
1044-
$startColID = Coordinate::stringFromColumnIndex($startCol);
1045-
$toColID = Coordinate::stringFromColumnIndex($startCol + $numberOfColumns);
1046-
$endColID = Coordinate::stringFromColumnIndex($rangeEnd[0] + 1);
1047-
do {
1048-
$autoFilter->shiftColumn($startColID, $toColID);
1049-
++$startColID;
1050-
++$toColID;
1051-
} while ($startColID != $endColID);
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;
10521090
}
1091+
$worksheet->setConditionalStyles(Coordinate::stringFromColumnIndex($i) . $j, $cloned);
10531092
}
10541093
}
10551094
}
1056-
$worksheet->setAutoFilter($this->updateCellReference($autoFilterRange, $beforeCellAddress, $numberOfColumns, $numberOfRows));
10571095
}
10581096
}
10591097

tests/PhpSpreadsheetTests/Worksheet/AutoFilter/AutoFilterTest.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,64 @@ public function testGetColumnOffset(): void
134134
}
135135
}
136136

137+
public function testRemoveColumns(): void
138+
{
139+
$sheet = $this->getSheet();
140+
$sheet->fromArray(range('H', 'O'), null, 'H2');
141+
$autoFilter = $sheet->getAutoFilter();
142+
$autoFilter->setRange(self::INITIAL_RANGE);
143+
$autoFilter->getColumn('L')->addRule((new Column\Rule())->setValue(5));
144+
145+
$sheet->removeColumn('K', 2);
146+
$result = $autoFilter->getRange();
147+
self::assertEquals('H2:M256', $result);
148+
149+
// Check that the rule that was set for column L is no longer set
150+
self::assertEmpty($autoFilter->getColumn('L')->getRule(0)->getValue());
151+
}
152+
153+
public function testRemoveRows(): void
154+
{
155+
$sheet = $this->getSheet();
156+
$sheet->fromArray(range('H', 'O'), null, 'H2');
157+
$autoFilter = $sheet->getAutoFilter();
158+
$autoFilter->setRange(self::INITIAL_RANGE);
159+
160+
$sheet->removeRow(42, 128);
161+
$result = $autoFilter->getRange();
162+
self::assertEquals('H2:O128', $result);
163+
}
164+
165+
public function testInsertColumns(): void
166+
{
167+
$sheet = $this->getSheet();
168+
$sheet->fromArray(range('H', 'O'), null, 'H2');
169+
$autoFilter = $sheet->getAutoFilter();
170+
$autoFilter->setRange(self::INITIAL_RANGE);
171+
$autoFilter->getColumn('N')->addRule((new Column\Rule())->setValue(5));
172+
173+
$sheet->insertNewColumnBefore('N', 3);
174+
$result = $autoFilter->getRange();
175+
self::assertEquals('H2:R256', $result);
176+
177+
// Check that column N no longer has a rule set
178+
self::assertEmpty($autoFilter->getColumn('N')->getRule(0)->getValue());
179+
// Check that the rule originally set in column N has been moved to column Q
180+
self::assertSame(5, $autoFilter->getColumn('Q')->getRule(0)->getValue());
181+
}
182+
183+
public function testInsertRows(): void
184+
{
185+
$sheet = $this->getSheet();
186+
$sheet->fromArray(range('H', 'O'), null, 'H2');
187+
$autoFilter = $sheet->getAutoFilter();
188+
$autoFilter->setRange(self::INITIAL_RANGE);
189+
190+
$sheet->insertNewRowBefore(3, 4);
191+
$result = $autoFilter->getRange();
192+
self::assertEquals('H2:O260', $result);
193+
}
194+
137195
public function testGetInvalidColumnOffset(): void
138196
{
139197
$this->expectException(PhpSpreadsheetException::class);

0 commit comments

Comments
 (0)