Skip to content

Commit 2351443

Browse files
authored
Merge pull request #3236 from PHPOffice/Structured-References_On-Changing-a-Column-Heading
Update cells that use structured references in formula, and defined names, when a table column heading is changed
2 parents 73ff527 + d5587e9 commit 2351443

File tree

3 files changed

+135
-2
lines changed

3 files changed

+135
-2
lines changed

src/PhpSpreadsheet/Cell/Cell.php

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
use PhpOffice\PhpSpreadsheet\Exception;
99
use PhpOffice\PhpSpreadsheet\RichText\RichText;
1010
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDate;
11+
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
1112
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\CellStyleAssessor;
1213
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
1314
use PhpOffice\PhpSpreadsheet\Style\Style;
15+
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
1416
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
1517

1618
class Cell
@@ -190,6 +192,29 @@ public function getFormattedValue(): string
190192
);
191193
}
192194

195+
/**
196+
* @param mixed $oldValue
197+
* @param mixed $newValue
198+
*/
199+
protected static function updateIfCellIsTableHeader(Worksheet $workSheet, self $cell, $oldValue, $newValue): void
200+
{
201+
if (StringHelper::strToLower($oldValue ?? '') === StringHelper::strToLower($newValue ?? '')) {
202+
return;
203+
}
204+
205+
foreach ($workSheet->getTableCollection() as $table) {
206+
/** @var Table $table */
207+
if ($cell->isInRange($table->getRange())) {
208+
$rangeRowsColumns = Coordinate::getRangeBoundaries($table->getRange());
209+
if ($cell->getRow() === (int) $rangeRowsColumns[0][1]) {
210+
Table\Column::updateStructuredReferences($workSheet, $oldValue, $newValue);
211+
}
212+
213+
return;
214+
}
215+
}
216+
}
217+
193218
/**
194219
* Set cell value.
195220
*
@@ -221,8 +246,10 @@ public function setValue($value): self
221246
*
222247
* @return Cell
223248
*/
224-
public function setValueExplicit($value, $dataType)
249+
public function setValueExplicit($value, string $dataType = DataType::TYPE_STRING)
225250
{
251+
$oldValue = $this->value;
252+
226253
// set the value according to data type
227254
switch ($dataType) {
228255
case DataType::TYPE_NULL:
@@ -270,7 +297,11 @@ public function setValueExplicit($value, $dataType)
270297
// set the datatype
271298
$this->dataType = $dataType;
272299

273-
return $this->updateInCollection();
300+
$this->updateInCollection();
301+
$cellCoordinate = $this->getCoordinate();
302+
self::updateIfCellIsTableHeader($this->getParent()->getParent(), $this, $oldValue, $value); // @phpstan-ignore-line
303+
304+
return $this->getParent()->get($cellCoordinate); // @phpstan-ignore-line
274305
}
275306

276307
public const CALCULATE_DATE_TIME_ASIS = 0;

src/PhpSpreadsheet/Worksheet/Table/Column.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
namespace PhpOffice\PhpSpreadsheet\Worksheet\Table;
44

5+
use PhpOffice\PhpSpreadsheet\Cell\DataType;
6+
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
7+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
58
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
9+
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
610

711
class Column
812
{
@@ -200,4 +204,51 @@ public function setTable(?Table $table = null): self
200204

201205
return $this;
202206
}
207+
208+
public static function updateStructuredReferences(?Worksheet $workSheet, ?string $oldTitle, string $newTitle): void
209+
{
210+
if ($workSheet === null || $oldTitle === null || $oldTitle === '') {
211+
return;
212+
}
213+
214+
// Remember that table headings are case-insensitive
215+
if (StringHelper::strToLower($oldTitle) !== StringHelper::strToLower($newTitle)) {
216+
// We need to check all formula cells that might contain Structured References that refer
217+
// to this column, and update those formulae to reference the new column text
218+
$spreadsheet = $workSheet->getParent();
219+
foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
220+
self::updateStructuredReferencesInCells($sheet, $oldTitle, $newTitle);
221+
}
222+
self::updateStructuredReferencesInNamedFormulae($spreadsheet, $oldTitle, $newTitle);
223+
}
224+
}
225+
226+
private static function updateStructuredReferencesInCells(Worksheet $worksheet, string $oldTitle, string $newTitle): void
227+
{
228+
$pattern = '/\[(@?)' . preg_quote($oldTitle) . '\]/mui';
229+
230+
foreach ($worksheet->getCoordinates(false) as $coordinate) {
231+
$cell = $worksheet->getCell($coordinate);
232+
if ($cell->getDataType() === DataType::TYPE_FORMULA) {
233+
$formula = $cell->getValue();
234+
if (preg_match($pattern, $formula) === 1) {
235+
$formula = preg_replace($pattern, "[$1{$newTitle}]", $formula);
236+
$cell->setValueExplicit($formula, DataType::TYPE_FORMULA);
237+
}
238+
}
239+
}
240+
}
241+
242+
private static function updateStructuredReferencesInNamedFormulae(Spreadsheet $spreadsheet, string $oldTitle, string $newTitle): void
243+
{
244+
$pattern = '/\[(@?)' . preg_quote($oldTitle) . '\]/mui';
245+
246+
foreach ($spreadsheet->getNamedFormulae() as $namedFormula) {
247+
$formula = $namedFormula->getValue();
248+
if (preg_match($pattern, $formula) === 1) {
249+
$formula = preg_replace($pattern, "[$1{$newTitle}]", $formula);
250+
$namedFormula->setValue($formula); // @phpstan-ignore-line
251+
}
252+
}
253+
}
203254
}

tests/PhpSpreadsheetTests/Worksheet/Table/FormulaTest.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,55 @@ public function testNamedFormulaUpdateOnTableNameChange(): void
5959

6060
$spreadsheet->disconnectWorksheets();
6161
}
62+
63+
public function testCellFormulaUpdateOnHeadingColumnChange(): void
64+
{
65+
$reader = new Xlsx();
66+
$filename = 'tests/data/Worksheet/Table/TableFormulae.xlsx';
67+
$spreadsheet = $reader->load($filename);
68+
$worksheet = $spreadsheet->getActiveSheet();
69+
70+
// Verify original formulae
71+
// Row Formula
72+
self::assertSame("=DeptSales[[#This Row],[Sales\u{a0}Amount]]*DeptSales[[#This Row],[% Commission]]", $worksheet->getCell('E2')->getValue());
73+
// Totals Formula
74+
self::assertSame('=SUBTOTAL(109,DeptSales[Commission Amount])', $worksheet->getCell('E8')->getValue());
75+
76+
$worksheet->getCell('D1')->setValue('Commission %age');
77+
$worksheet->getCell('E1')->setValue('Commission');
78+
79+
// Verify modified formulae
80+
// Row Formula
81+
self::assertSame("=DeptSales[[#This Row],[Sales\u{a0}Amount]]*DeptSales[[#This Row],[Commission %age]]", $worksheet->getCell('E2')->getValue());
82+
// Totals Formula
83+
self::assertSame('=SUBTOTAL(109,DeptSales[Commission])', $worksheet->getCell('E8')->getValue());
84+
85+
$spreadsheet->disconnectWorksheets();
86+
}
87+
88+
public function testNamedFormulaUpdateOnHeadingColumnChange(): void
89+
{
90+
$reader = new Xlsx();
91+
$filename = 'tests/data/Worksheet/Table/TableFormulae.xlsx';
92+
$spreadsheet = $reader->load($filename);
93+
$worksheet = $spreadsheet->getActiveSheet();
94+
95+
$table = $spreadsheet->getActiveSheet()->getTableCollection()[0];
96+
if ($table === null) {
97+
self::markTestSkipped('Unable to read table for testing.');
98+
}
99+
$namedFormula = $spreadsheet->getNamedFormula('CommissionTotal');
100+
if ($namedFormula === null) {
101+
self::markTestSkipped('Unable to read named formula for testing.');
102+
}
103+
104+
// Verify original formula
105+
self::assertSame('SUBTOTAL(109,DeptSales[Commission Amount])', $namedFormula->getFormula());
106+
107+
$worksheet->getCell('E1')->setValue('Commission');
108+
// Verify modified formula
109+
self::assertSame('SUBTOTAL(109,DeptSales[Commission])', $namedFormula->getFormula());
110+
111+
$spreadsheet->disconnectWorksheets();
112+
}
62113
}

0 commit comments

Comments
 (0)