Auto adjust table range using ReferenceHelper
Automatically adjusts table range on insertion and deletion of rows and columns within table range
This commit is contained in:
parent
50b91e8ede
commit
bc6ec1932a
|
|
@ -7,6 +7,7 @@ use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||||
use PhpOffice\PhpSpreadsheet\Style\Conditional;
|
use PhpOffice\PhpSpreadsheet\Style\Conditional;
|
||||||
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter;
|
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
|
||||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
|
|
||||||
class ReferenceHelper
|
class ReferenceHelper
|
||||||
|
|
@ -497,6 +498,9 @@ class ReferenceHelper
|
||||||
// Update worksheet: autofilter
|
// Update worksheet: autofilter
|
||||||
$this->adjustAutoFilter($worksheet, $beforeCellAddress, $numberOfColumns);
|
$this->adjustAutoFilter($worksheet, $beforeCellAddress, $numberOfColumns);
|
||||||
|
|
||||||
|
// Update worksheet: table
|
||||||
|
$this->adjustTable($worksheet, $beforeCellAddress, $numberOfColumns);
|
||||||
|
|
||||||
// Update worksheet: freeze pane
|
// Update worksheet: freeze pane
|
||||||
if ($worksheet->getFreezePane()) {
|
if ($worksheet->getFreezePane()) {
|
||||||
$splitCell = $worksheet->getFreezePane() ?? '';
|
$splitCell = $worksheet->getFreezePane() ?? '';
|
||||||
|
|
@ -1026,6 +1030,85 @@ class ReferenceHelper
|
||||||
} while ($startColID !== $endColID);
|
} while ($startColID !== $endColID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function adjustTable(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns): void
|
||||||
|
{
|
||||||
|
$tableCollection = $worksheet->getTableCollection();
|
||||||
|
|
||||||
|
foreach ($tableCollection as $table) {
|
||||||
|
$tableRange = $table->getRange();
|
||||||
|
if (!empty($tableRange)) {
|
||||||
|
if ($numberOfColumns !== 0) {
|
||||||
|
$tableColumns = $table->getColumns();
|
||||||
|
if (count($tableColumns) > 0) {
|
||||||
|
$column = '';
|
||||||
|
$row = 0;
|
||||||
|
sscanf($beforeCellAddress, '%[A-Z]%d', $column, $row);
|
||||||
|
$columnIndex = Coordinate::columnIndexFromString($column);
|
||||||
|
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($tableRange);
|
||||||
|
if ($columnIndex <= $rangeEnd[0]) {
|
||||||
|
if ($numberOfColumns < 0) {
|
||||||
|
$this->adjustTableDeleteRules($columnIndex, $numberOfColumns, $tableColumns, $table);
|
||||||
|
}
|
||||||
|
$startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0];
|
||||||
|
|
||||||
|
// Shuffle columns in table range
|
||||||
|
if ($numberOfColumns > 0) {
|
||||||
|
$this->adjustTableInsert($startCol, $numberOfColumns, $rangeEnd[0], $table);
|
||||||
|
} else {
|
||||||
|
$this->adjustTableDelete($startCol, $numberOfColumns, $rangeEnd[0], $table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$table->setRange($this->updateCellReference($tableRange));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function adjustTableDeleteRules(int $columnIndex, int $numberOfColumns, array $tableColumns, Table $table): void
|
||||||
|
{
|
||||||
|
// If we're actually deleting any columns that fall within the table range,
|
||||||
|
// then we delete any rules for those columns
|
||||||
|
$deleteColumn = $columnIndex + $numberOfColumns - 1;
|
||||||
|
$deleteCount = abs($numberOfColumns);
|
||||||
|
|
||||||
|
for ($i = 1; $i <= $deleteCount; ++$i) {
|
||||||
|
$columnName = Coordinate::stringFromColumnIndex($deleteColumn + 1);
|
||||||
|
if (isset($tableColumns[$columnName])) {
|
||||||
|
$table->clearColumn($columnName);
|
||||||
|
}
|
||||||
|
++$deleteColumn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function adjustTableInsert(int $startCol, int $numberOfColumns, int $rangeEnd, Table $table): void
|
||||||
|
{
|
||||||
|
$startColRef = $startCol;
|
||||||
|
$endColRef = $rangeEnd;
|
||||||
|
$toColRef = $rangeEnd + $numberOfColumns;
|
||||||
|
|
||||||
|
do {
|
||||||
|
$table->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef));
|
||||||
|
--$endColRef;
|
||||||
|
--$toColRef;
|
||||||
|
} while ($startColRef <= $endColRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function adjustTableDelete(int $startCol, int $numberOfColumns, int $rangeEnd, Table $table): void
|
||||||
|
{
|
||||||
|
// For delete, we shuffle from beginning to end to avoid overwriting
|
||||||
|
$startColID = Coordinate::stringFromColumnIndex($startCol);
|
||||||
|
$toColID = Coordinate::stringFromColumnIndex($startCol + $numberOfColumns);
|
||||||
|
$endColID = Coordinate::stringFromColumnIndex($rangeEnd + 1);
|
||||||
|
|
||||||
|
do {
|
||||||
|
$table->shiftColumn($startColID, $toColID);
|
||||||
|
++$startColID;
|
||||||
|
++$toColID;
|
||||||
|
} while ($startColID !== $endColID);
|
||||||
|
}
|
||||||
|
|
||||||
private function duplicateStylesByColumn(Worksheet $worksheet, int $beforeColumn, int $beforeRow, int $highestRow, int $numberOfColumns): void
|
private function duplicateStylesByColumn(Worksheet $worksheet, int $beforeColumn, int $beforeRow, int $highestRow, int $numberOfColumns): void
|
||||||
{
|
{
|
||||||
$beforeColumnName = Coordinate::stringFromColumnIndex($beforeColumn - 1);
|
$beforeColumnName = Coordinate::stringFromColumnIndex($beforeColumn - 1);
|
||||||
|
|
|
||||||
|
|
@ -380,6 +380,36 @@ class Table
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shift an Table Column Rule to a different column.
|
||||||
|
*
|
||||||
|
* Note: This method bypasses validation of the destination column to ensure it is within this Table range.
|
||||||
|
* Nor does it verify whether any column rule already exists at $toColumn, but will simply override any existing value.
|
||||||
|
* Use with caution.
|
||||||
|
*
|
||||||
|
* @param string $fromColumn Column name (e.g. A)
|
||||||
|
* @param string $toColumn Column name (e.g. B)
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function shiftColumn($fromColumn, $toColumn)
|
||||||
|
{
|
||||||
|
$fromColumn = strtoupper($fromColumn);
|
||||||
|
$toColumn = strtoupper($toColumn);
|
||||||
|
|
||||||
|
if (($fromColumn !== null) && (isset($this->columns[$fromColumn])) && ($toColumn !== null)) {
|
||||||
|
$this->columns[$fromColumn]->setTable();
|
||||||
|
$this->columns[$fromColumn]->setColumnIndex($toColumn);
|
||||||
|
$this->columns[$toColumn] = $this->columns[$fromColumn];
|
||||||
|
$this->columns[$toColumn]->setTable($this);
|
||||||
|
unset($this->columns[$fromColumn]);
|
||||||
|
|
||||||
|
ksort($this->columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get table Style.
|
* Get table Style.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -208,6 +208,64 @@ class TableTest extends SetupTeardown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testRemoveColumns(): void
|
||||||
|
{
|
||||||
|
$sheet = $this->getSheet();
|
||||||
|
$sheet->fromArray(range('H', 'O'), null, 'H2');
|
||||||
|
$table = new Table(self::INITIAL_RANGE);
|
||||||
|
$table->getColumn('L')->setShowFilterButton(false);
|
||||||
|
$sheet->addTable($table);
|
||||||
|
|
||||||
|
$sheet->removeColumn('K', 2);
|
||||||
|
$result = $table->getRange();
|
||||||
|
self::assertEquals('H2:M256', $result);
|
||||||
|
|
||||||
|
// Check that the prop that was set for column L is no longer set
|
||||||
|
self::assertTrue($table->getColumn('L')->getShowFilterButton());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRemoveRows(): void
|
||||||
|
{
|
||||||
|
$sheet = $this->getSheet();
|
||||||
|
$sheet->fromArray(range('H', 'O'), null, 'H2');
|
||||||
|
$table = new Table(self::INITIAL_RANGE);
|
||||||
|
$sheet->addTable($table);
|
||||||
|
|
||||||
|
$sheet->removeRow(42, 128);
|
||||||
|
$result = $table->getRange();
|
||||||
|
self::assertEquals('H2:O128', $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInsertColumns(): void
|
||||||
|
{
|
||||||
|
$sheet = $this->getSheet();
|
||||||
|
$sheet->fromArray(range('H', 'O'), null, 'H2');
|
||||||
|
$table = new Table(self::INITIAL_RANGE);
|
||||||
|
$table->getColumn('N')->setShowFilterButton(false);
|
||||||
|
$sheet->addTable($table);
|
||||||
|
|
||||||
|
$sheet->insertNewColumnBefore('N', 3);
|
||||||
|
$result = $table->getRange();
|
||||||
|
self::assertEquals('H2:R256', $result);
|
||||||
|
|
||||||
|
// Check that column N no longer has a prop
|
||||||
|
self::assertTrue($table->getColumn('N')->getShowFilterButton());
|
||||||
|
// Check that the prop originally set in column N has been moved to column Q
|
||||||
|
self::assertFalse($table->getColumn('Q')->getShowFilterButton());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInsertRows(): void
|
||||||
|
{
|
||||||
|
$sheet = $this->getSheet();
|
||||||
|
$sheet->fromArray(range('H', 'O'), null, 'H2');
|
||||||
|
$table = new Table(self::INITIAL_RANGE);
|
||||||
|
$sheet->addTable($table);
|
||||||
|
|
||||||
|
$sheet->insertNewRowBefore(3, 4);
|
||||||
|
$result = $table->getRange();
|
||||||
|
self::assertEquals('H2:O260', $result);
|
||||||
|
}
|
||||||
|
|
||||||
public function testGetInvalidColumnOffset(): void
|
public function testGetInvalidColumnOffset(): void
|
||||||
{
|
{
|
||||||
$this->expectException(PhpSpreadsheetException::class);
|
$this->expectException(PhpSpreadsheetException::class);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue