Skip to content

Commit bc6ec19

Browse files
committed
Auto adjust table range using ReferenceHelper
Automatically adjusts table range on insertion and deletion of rows and columns within table range
1 parent 50b91e8 commit bc6ec19

File tree

3 files changed

+171
-0
lines changed

3 files changed

+171
-0
lines changed

src/PhpSpreadsheet/ReferenceHelper.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PhpOffice\PhpSpreadsheet\Cell\DataType;
88
use PhpOffice\PhpSpreadsheet\Style\Conditional;
99
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter;
10+
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
1011
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
1112

1213
class ReferenceHelper
@@ -497,6 +498,9 @@ function ($coordinate) use ($allCoordinates) {
497498
// Update worksheet: autofilter
498499
$this->adjustAutoFilter($worksheet, $beforeCellAddress, $numberOfColumns);
499500

501+
// Update worksheet: table
502+
$this->adjustTable($worksheet, $beforeCellAddress, $numberOfColumns);
503+
500504
// Update worksheet: freeze pane
501505
if ($worksheet->getFreezePane()) {
502506
$splitCell = $worksheet->getFreezePane() ?? '';
@@ -1026,6 +1030,85 @@ private function adjustAutoFilterDelete(int $startCol, int $numberOfColumns, int
10261030
} while ($startColID !== $endColID);
10271031
}
10281032

1033+
private function adjustTable(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns): void
1034+
{
1035+
$tableCollection = $worksheet->getTableCollection();
1036+
1037+
foreach ($tableCollection as $table) {
1038+
$tableRange = $table->getRange();
1039+
if (!empty($tableRange)) {
1040+
if ($numberOfColumns !== 0) {
1041+
$tableColumns = $table->getColumns();
1042+
if (count($tableColumns) > 0) {
1043+
$column = '';
1044+
$row = 0;
1045+
sscanf($beforeCellAddress, '%[A-Z]%d', $column, $row);
1046+
$columnIndex = Coordinate::columnIndexFromString($column);
1047+
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($tableRange);
1048+
if ($columnIndex <= $rangeEnd[0]) {
1049+
if ($numberOfColumns < 0) {
1050+
$this->adjustTableDeleteRules($columnIndex, $numberOfColumns, $tableColumns, $table);
1051+
}
1052+
$startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0];
1053+
1054+
// Shuffle columns in table range
1055+
if ($numberOfColumns > 0) {
1056+
$this->adjustTableInsert($startCol, $numberOfColumns, $rangeEnd[0], $table);
1057+
} else {
1058+
$this->adjustTableDelete($startCol, $numberOfColumns, $rangeEnd[0], $table);
1059+
}
1060+
}
1061+
}
1062+
}
1063+
1064+
$table->setRange($this->updateCellReference($tableRange));
1065+
}
1066+
}
1067+
}
1068+
1069+
private function adjustTableDeleteRules(int $columnIndex, int $numberOfColumns, array $tableColumns, Table $table): void
1070+
{
1071+
// If we're actually deleting any columns that fall within the table range,
1072+
// then we delete any rules for those columns
1073+
$deleteColumn = $columnIndex + $numberOfColumns - 1;
1074+
$deleteCount = abs($numberOfColumns);
1075+
1076+
for ($i = 1; $i <= $deleteCount; ++$i) {
1077+
$columnName = Coordinate::stringFromColumnIndex($deleteColumn + 1);
1078+
if (isset($tableColumns[$columnName])) {
1079+
$table->clearColumn($columnName);
1080+
}
1081+
++$deleteColumn;
1082+
}
1083+
}
1084+
1085+
private function adjustTableInsert(int $startCol, int $numberOfColumns, int $rangeEnd, Table $table): void
1086+
{
1087+
$startColRef = $startCol;
1088+
$endColRef = $rangeEnd;
1089+
$toColRef = $rangeEnd + $numberOfColumns;
1090+
1091+
do {
1092+
$table->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef));
1093+
--$endColRef;
1094+
--$toColRef;
1095+
} while ($startColRef <= $endColRef);
1096+
}
1097+
1098+
private function adjustTableDelete(int $startCol, int $numberOfColumns, int $rangeEnd, Table $table): void
1099+
{
1100+
// For delete, we shuffle from beginning to end to avoid overwriting
1101+
$startColID = Coordinate::stringFromColumnIndex($startCol);
1102+
$toColID = Coordinate::stringFromColumnIndex($startCol + $numberOfColumns);
1103+
$endColID = Coordinate::stringFromColumnIndex($rangeEnd + 1);
1104+
1105+
do {
1106+
$table->shiftColumn($startColID, $toColID);
1107+
++$startColID;
1108+
++$toColID;
1109+
} while ($startColID !== $endColID);
1110+
}
1111+
10291112
private function duplicateStylesByColumn(Worksheet $worksheet, int $beforeColumn, int $beforeRow, int $highestRow, int $numberOfColumns): void
10301113
{
10311114
$beforeColumnName = Coordinate::stringFromColumnIndex($beforeColumn - 1);

src/PhpSpreadsheet/Worksheet/Table.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,36 @@ public function clearColumn($column)
380380
return $this;
381381
}
382382

383+
/**
384+
* Shift an Table Column Rule to a different column.
385+
*
386+
* Note: This method bypasses validation of the destination column to ensure it is within this Table range.
387+
* Nor does it verify whether any column rule already exists at $toColumn, but will simply override any existing value.
388+
* Use with caution.
389+
*
390+
* @param string $fromColumn Column name (e.g. A)
391+
* @param string $toColumn Column name (e.g. B)
392+
*
393+
* @return $this
394+
*/
395+
public function shiftColumn($fromColumn, $toColumn)
396+
{
397+
$fromColumn = strtoupper($fromColumn);
398+
$toColumn = strtoupper($toColumn);
399+
400+
if (($fromColumn !== null) && (isset($this->columns[$fromColumn])) && ($toColumn !== null)) {
401+
$this->columns[$fromColumn]->setTable();
402+
$this->columns[$fromColumn]->setColumnIndex($toColumn);
403+
$this->columns[$toColumn] = $this->columns[$fromColumn];
404+
$this->columns[$toColumn]->setTable($this);
405+
unset($this->columns[$fromColumn]);
406+
407+
ksort($this->columns);
408+
}
409+
410+
return $this;
411+
}
412+
383413
/**
384414
* Get table Style.
385415
*

tests/PhpSpreadsheetTests/Worksheet/Table/TableTest.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,64 @@ public function testGetColumnOffset(): void
208208
}
209209
}
210210

211+
public function testRemoveColumns(): void
212+
{
213+
$sheet = $this->getSheet();
214+
$sheet->fromArray(range('H', 'O'), null, 'H2');
215+
$table = new Table(self::INITIAL_RANGE);
216+
$table->getColumn('L')->setShowFilterButton(false);
217+
$sheet->addTable($table);
218+
219+
$sheet->removeColumn('K', 2);
220+
$result = $table->getRange();
221+
self::assertEquals('H2:M256', $result);
222+
223+
// Check that the prop that was set for column L is no longer set
224+
self::assertTrue($table->getColumn('L')->getShowFilterButton());
225+
}
226+
227+
public function testRemoveRows(): void
228+
{
229+
$sheet = $this->getSheet();
230+
$sheet->fromArray(range('H', 'O'), null, 'H2');
231+
$table = new Table(self::INITIAL_RANGE);
232+
$sheet->addTable($table);
233+
234+
$sheet->removeRow(42, 128);
235+
$result = $table->getRange();
236+
self::assertEquals('H2:O128', $result);
237+
}
238+
239+
public function testInsertColumns(): void
240+
{
241+
$sheet = $this->getSheet();
242+
$sheet->fromArray(range('H', 'O'), null, 'H2');
243+
$table = new Table(self::INITIAL_RANGE);
244+
$table->getColumn('N')->setShowFilterButton(false);
245+
$sheet->addTable($table);
246+
247+
$sheet->insertNewColumnBefore('N', 3);
248+
$result = $table->getRange();
249+
self::assertEquals('H2:R256', $result);
250+
251+
// Check that column N no longer has a prop
252+
self::assertTrue($table->getColumn('N')->getShowFilterButton());
253+
// Check that the prop originally set in column N has been moved to column Q
254+
self::assertFalse($table->getColumn('Q')->getShowFilterButton());
255+
}
256+
257+
public function testInsertRows(): void
258+
{
259+
$sheet = $this->getSheet();
260+
$sheet->fromArray(range('H', 'O'), null, 'H2');
261+
$table = new Table(self::INITIAL_RANGE);
262+
$sheet->addTable($table);
263+
264+
$sheet->insertNewRowBefore(3, 4);
265+
$result = $table->getRange();
266+
self::assertEquals('H2:O260', $result);
267+
}
268+
211269
public function testGetInvalidColumnOffset(): void
212270
{
213271
$this->expectException(PhpSpreadsheetException::class);

0 commit comments

Comments
 (0)