diff --git a/src/PhpSpreadsheet/Cell/Cell.php b/src/PhpSpreadsheet/Cell/Cell.php index 47959f68b7..58a39af21a 100644 --- a/src/PhpSpreadsheet/Cell/Cell.php +++ b/src/PhpSpreadsheet/Cell/Cell.php @@ -8,9 +8,11 @@ use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDate; +use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\CellStyleAssessor; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Style\Style; +use PhpOffice\PhpSpreadsheet\Worksheet\Table; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; class Cell @@ -190,6 +192,29 @@ public function getFormattedValue(): string ); } + /** + * @param mixed $oldValue + * @param mixed $newValue + */ + protected static function updateIfCellIsTableHeader(Worksheet $workSheet, self $cell, $oldValue, $newValue): void + { + if (StringHelper::strToLower($oldValue ?? '') === StringHelper::strToLower($newValue ?? '')) { + return; + } + + foreach ($workSheet->getTableCollection() as $table) { + /** @var Table $table */ + if ($cell->isInRange($table->getRange())) { + $rangeRowsColumns = Coordinate::getRangeBoundaries($table->getRange()); + if ($cell->getRow() === (int) $rangeRowsColumns[0][1]) { + Table\Column::updateStructuredReferences($workSheet, $oldValue, $newValue); + } + + return; + } + } + } + /** * Set cell value. * @@ -221,8 +246,10 @@ public function setValue($value): self * * @return Cell */ - public function setValueExplicit($value, $dataType) + public function setValueExplicit($value, string $dataType = DataType::TYPE_STRING) { + $oldValue = $this->value; + // set the value according to data type switch ($dataType) { case DataType::TYPE_NULL: @@ -270,7 +297,11 @@ public function setValueExplicit($value, $dataType) // set the datatype $this->dataType = $dataType; - return $this->updateInCollection(); + $this->updateInCollection(); + $cellCoordinate = $this->getCoordinate(); + self::updateIfCellIsTableHeader($this->getParent()->getParent(), $this, $oldValue, $value); // @phpstan-ignore-line + + return $this->getParent()->get($cellCoordinate); // @phpstan-ignore-line } public const CALCULATE_DATE_TIME_ASIS = 0; diff --git a/src/PhpSpreadsheet/Worksheet/Table/Column.php b/src/PhpSpreadsheet/Worksheet/Table/Column.php index a7c445f530..9fe19ecbb6 100644 --- a/src/PhpSpreadsheet/Worksheet/Table/Column.php +++ b/src/PhpSpreadsheet/Worksheet/Table/Column.php @@ -2,7 +2,11 @@ namespace PhpOffice\PhpSpreadsheet\Worksheet\Table; +use PhpOffice\PhpSpreadsheet\Cell\DataType; +use PhpOffice\PhpSpreadsheet\Shared\StringHelper; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\Table; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; class Column { @@ -200,4 +204,51 @@ public function setTable(?Table $table = null): self return $this; } + + public static function updateStructuredReferences(?Worksheet $workSheet, ?string $oldTitle, string $newTitle): void + { + if ($workSheet === null || $oldTitle === null || $oldTitle === '') { + return; + } + + // Remember that table headings are case-insensitive + if (StringHelper::strToLower($oldTitle) !== StringHelper::strToLower($newTitle)) { + // We need to check all formula cells that might contain Structured References that refer + // to this column, and update those formulae to reference the new column text + $spreadsheet = $workSheet->getParent(); + foreach ($spreadsheet->getWorksheetIterator() as $sheet) { + self::updateStructuredReferencesInCells($sheet, $oldTitle, $newTitle); + } + self::updateStructuredReferencesInNamedFormulae($spreadsheet, $oldTitle, $newTitle); + } + } + + private static function updateStructuredReferencesInCells(Worksheet $worksheet, string $oldTitle, string $newTitle): void + { + $pattern = '/\[(@?)' . preg_quote($oldTitle) . '\]/mui'; + + foreach ($worksheet->getCoordinates(false) as $coordinate) { + $cell = $worksheet->getCell($coordinate); + if ($cell->getDataType() === DataType::TYPE_FORMULA) { + $formula = $cell->getValue(); + if (preg_match($pattern, $formula) === 1) { + $formula = preg_replace($pattern, "[$1{$newTitle}]", $formula); + $cell->setValueExplicit($formula, DataType::TYPE_FORMULA); + } + } + } + } + + private static function updateStructuredReferencesInNamedFormulae(Spreadsheet $spreadsheet, string $oldTitle, string $newTitle): void + { + $pattern = '/\[(@?)' . preg_quote($oldTitle) . '\]/mui'; + + foreach ($spreadsheet->getNamedFormulae() as $namedFormula) { + $formula = $namedFormula->getValue(); + if (preg_match($pattern, $formula) === 1) { + $formula = preg_replace($pattern, "[$1{$newTitle}]", $formula); + $namedFormula->setValue($formula); // @phpstan-ignore-line + } + } + } } diff --git a/tests/PhpSpreadsheetTests/Worksheet/Table/FormulaTest.php b/tests/PhpSpreadsheetTests/Worksheet/Table/FormulaTest.php index c958045f35..69e54446ef 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/Table/FormulaTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/Table/FormulaTest.php @@ -59,4 +59,55 @@ public function testNamedFormulaUpdateOnTableNameChange(): void $spreadsheet->disconnectWorksheets(); } + + public function testCellFormulaUpdateOnHeadingColumnChange(): void + { + $reader = new Xlsx(); + $filename = 'tests/data/Worksheet/Table/TableFormulae.xlsx'; + $spreadsheet = $reader->load($filename); + $worksheet = $spreadsheet->getActiveSheet(); + + // Verify original formulae + // Row Formula + self::assertSame("=DeptSales[[#This Row],[Sales\u{a0}Amount]]*DeptSales[[#This Row],[% Commission]]", $worksheet->getCell('E2')->getValue()); + // Totals Formula + self::assertSame('=SUBTOTAL(109,DeptSales[Commission Amount])', $worksheet->getCell('E8')->getValue()); + + $worksheet->getCell('D1')->setValue('Commission %age'); + $worksheet->getCell('E1')->setValue('Commission'); + + // Verify modified formulae + // Row Formula + self::assertSame("=DeptSales[[#This Row],[Sales\u{a0}Amount]]*DeptSales[[#This Row],[Commission %age]]", $worksheet->getCell('E2')->getValue()); + // Totals Formula + self::assertSame('=SUBTOTAL(109,DeptSales[Commission])', $worksheet->getCell('E8')->getValue()); + + $spreadsheet->disconnectWorksheets(); + } + + public function testNamedFormulaUpdateOnHeadingColumnChange(): void + { + $reader = new Xlsx(); + $filename = 'tests/data/Worksheet/Table/TableFormulae.xlsx'; + $spreadsheet = $reader->load($filename); + $worksheet = $spreadsheet->getActiveSheet(); + + $table = $spreadsheet->getActiveSheet()->getTableCollection()[0]; + if ($table === null) { + self::markTestSkipped('Unable to read table for testing.'); + } + $namedFormula = $spreadsheet->getNamedFormula('CommissionTotal'); + if ($namedFormula === null) { + self::markTestSkipped('Unable to read named formula for testing.'); + } + + // Verify original formula + self::assertSame('SUBTOTAL(109,DeptSales[Commission Amount])', $namedFormula->getFormula()); + + $worksheet->getCell('E1')->setValue('Commission'); + // Verify modified formula + self::assertSame('SUBTOTAL(109,DeptSales[Commission])', $namedFormula->getFormula()); + + $spreadsheet->disconnectWorksheets(); + } }