Correct update to named ranges and formulae when inserting/deleting columns/rows

This commit is contained in:
MarkBaker 2022-09-22 01:10:53 +02:00
parent b9ded919fc
commit abcbac6f9a
2 changed files with 135 additions and 5 deletions

View File

@ -538,11 +538,7 @@ class ReferenceHelper
// Update workbook: define names // Update workbook: define names
if (count($worksheet->getParent()->getDefinedNames()) > 0) { if (count($worksheet->getParent()->getDefinedNames()) > 0) {
foreach ($worksheet->getParent()->getDefinedNames() as $definedName) { $this->updateDefinedNames($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) {
$definedName->setValue($this->updateCellReference($definedName->getValue()));
}
}
} }
// Garbage collect // Garbage collect
@ -1163,4 +1159,29 @@ class ReferenceHelper
{ {
throw new Exception('Cloning a Singleton is not allowed!'); throw new Exception('Cloning a Singleton is not allowed!');
} }
public function updateDefinedNames(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void
{
foreach ($worksheet->getParent()->getDefinedNames() as $definedName) {
if ($definedName->isFormula() === false) {
$cellAddress = $definedName->getValue();
$asFormula = ($cellAddress[0] === '=');
if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) {
if ($asFormula === true) {
$formula = $definedName->getValue();
$formula = $this->updateFormulaReferences($formula, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle());
$definedName->setValue($formula);
} else {
$definedName->setValue($asFormula . $this->updateCellReference(ltrim($cellAddress, '=')));
}
}
} else {
$formula = $definedName->getValue();
if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) {
$formula = $this->updateFormulaReferences($formula, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle());
$definedName->setValue($formula);
}
}
}
}
} }

View File

@ -2,9 +2,12 @@
namespace PhpOffice\PhpSpreadsheetTests; namespace PhpOffice\PhpSpreadsheetTests;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Cell\Hyperlink; use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
use PhpOffice\PhpSpreadsheet\Comment; use PhpOffice\PhpSpreadsheet\Comment;
use PhpOffice\PhpSpreadsheet\NamedFormula;
use PhpOffice\PhpSpreadsheet\NamedRange;
use PhpOffice\PhpSpreadsheet\ReferenceHelper; use PhpOffice\PhpSpreadsheet\ReferenceHelper;
use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
@ -538,4 +541,110 @@ class ReferenceHelperTest extends TestCase
$printArea = $sheet->getPageSetup()->getPrintArea(); $printArea = $sheet->getPageSetup()->getPrintArea();
self::assertSame('A1:H10', $printArea); self::assertSame('A1:H10', $printArea);
} }
public function testInsertRowsWithDefinedNames(): void
{
$spreadsheet = $this->buildDefinedNamesTestWorkbook();
/** @var Worksheet $dataSheet */
$dataSheet = $spreadsheet->getSheetByName('Data');
/** @var Worksheet $totalsSheet */
$totalsSheet = $spreadsheet->getSheetByName('Totals');
$dataSheet->insertNewRowBefore(4, 2);
Calculation::getInstance($spreadsheet)->flushInstance();
/** @var NamedRange $firstColumn */
$firstColumn = $spreadsheet->getNamedRange('FirstColumn');
/** @var NamedRange $secondColumn */
$secondColumn = $spreadsheet->getNamedRange('SecondColumn');
self::assertSame('=Data!$A$2:$A8', $firstColumn->getRange());
self::assertSame('=Data!B$2:B8', $secondColumn->getRange());
self::assertSame(30, $totalsSheet->getCell('A20')->getCalculatedValue());
self::assertSame(25, $totalsSheet->getCell('B20')->getCalculatedValue());
self::assertSame(750, $totalsSheet->getCell('D20')->getCalculatedValue());
}
public function testInsertColumnsWithDefinedNames(): void
{
$spreadsheet = $this->buildDefinedNamesTestWorkbook();
/** @var Worksheet $dataSheet */
$dataSheet = $spreadsheet->getSheetByName('Data');
/** @var Worksheet $totalsSheet */
$totalsSheet = $spreadsheet->getSheetByName('Totals');
$dataSheet->insertNewColumnBefore('B', 2);
Calculation::getInstance($spreadsheet)->flushInstance();
/** @var NamedRange $firstColumn */
$firstColumn = $spreadsheet->getNamedRange('FirstColumn');
/** @var NamedRange $secondColumn */
$secondColumn = $spreadsheet->getNamedRange('SecondColumn');
self::assertSame('=Data!$A$2:$A6', $firstColumn->getRange());
self::assertSame('=Data!D$2:D6', $secondColumn->getRange());
self::assertSame(30, $totalsSheet->getCell('A20')->getCalculatedValue());
self::assertSame(25, $totalsSheet->getCell('B20')->getCalculatedValue());
self::assertSame(750, $totalsSheet->getCell('D20')->getCalculatedValue());
}
public function testDeleteRowsWithDefinedNames(): void
{
$spreadsheet = $this->buildDefinedNamesTestWorkbook();
/** @var Worksheet $dataSheet */
$dataSheet = $spreadsheet->getSheetByName('Data');
/** @var Worksheet $totalsSheet */
$totalsSheet = $spreadsheet->getSheetByName('Totals');
$dataSheet->removeRow(3, 2);
Calculation::getInstance($spreadsheet)->flushInstance();
/** @var NamedRange $firstColumn */
$firstColumn = $spreadsheet->getNamedRange('FirstColumn');
/** @var NamedRange $secondColumn */
$secondColumn = $spreadsheet->getNamedRange('SecondColumn');
self::assertSame('=Data!$A$2:$A4', $firstColumn->getRange());
self::assertSame('=Data!B$2:B4', $secondColumn->getRange());
self::assertSame(20, $totalsSheet->getCell('A20')->getCalculatedValue());
self::assertSame(17, $totalsSheet->getCell('B20')->getCalculatedValue());
self::assertSame(340, $totalsSheet->getCell('D20')->getCalculatedValue());
}
private function buildDefinedNamesTestWorkbook(): Spreadsheet
{
$spreadsheet = new Spreadsheet();
$dataSheet = $spreadsheet->getActiveSheet();
$dataSheet->setTitle('Data');
$totalsSheet = $spreadsheet->addSheet(new Worksheet());
$totalsSheet->setTitle('Totals');
$spreadsheet->setActiveSheetIndexByName('Data');
$dataSheet->fromArray([['Column 1', 'Column 2'], [2, 1], [4, 3], [6, 5], [8, 7], [10, 9]], null, 'A1', true);
$spreadsheet->addNamedRange(
new NamedRange('FirstColumn', $spreadsheet->getActiveSheet(), '=Data!$A$2:$A6')
);
$spreadsheet->addNamedFormula(
new NamedFormula('FirstTotal', $spreadsheet->getActiveSheet(), '=SUM(FirstColumn)')
);
$totalsSheet->setCellValue('A20', '=FirstTotal');
$spreadsheet->addNamedRange(
new NamedRange('SecondColumn', $spreadsheet->getActiveSheet(), '=Data!B$2:B6')
);
$spreadsheet->addNamedFormula(
new NamedFormula('SecondTotal', $spreadsheet->getActiveSheet(), '=SUM(SecondColumn)')
);
$totalsSheet->setCellValue('B20', '=SecondTotal');
$spreadsheet->addNamedFormula(
new NamedFormula('ProductTotal', $spreadsheet->getActiveSheet(), '=FirstTotal*SecondTotal')
);
$totalsSheet->setCellValue('D20', '=ProductTotal');
return $spreadsheet;
}
} }