Skip to content

Commit 6f5597d

Browse files
authored
Merge pull request #3077 from PHPOffice/Issue-3076_Insert-Row-with-Defined-Names-Examples
Correct update to named ranges and formulae when inserting/deleting columns/rows
2 parents b9ded91 + d682f2f commit 6f5597d

File tree

5 files changed

+150
-9
lines changed

5 files changed

+150
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
3838

3939
### Fixed
4040

41+
- Fix update to defined names when inserting/deleting rows/columns [Issue #3076](https://github.com/PHPOffice/PhpSpreadsheet/issues/3076) [PR #3077](https://github.com/PHPOffice/PhpSpreadsheet/pull/3077)
4142
- Fix DataValidation sqRef when inserting/deleting rows/columns [Issue #3056](https://github.com/PHPOffice/PhpSpreadsheet/issues/3056) [PR #3074](https://github.com/PHPOffice/PhpSpreadsheet/pull/3074)
4243
- Named ranges not usable as anchors in OFFSET function [Issue #3013](https://github.com/PHPOffice/PhpSpreadsheet/issues/3013)
4344
- Fully flatten an array [Issue #2955](https://github.com/PHPOffice/PhpSpreadsheet/issues/2955) [PR #2956](https://github.com/PHPOffice/PhpSpreadsheet/pull/2956)

src/PhpSpreadsheet/DefinedName.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ public function setName(string $name): self
150150

151151
// New title
152152
$newTitle = $this->name;
153-
ReferenceHelper::getInstance()->updateNamedFormulas($this->worksheet->getParent(), $oldTitle, $newTitle);
153+
ReferenceHelper::getInstance()->updateNamedFormulae($this->worksheet->getParent(), $oldTitle, $newTitle);
154154
}
155155

156156
return $this;

src/PhpSpreadsheet/ReferenceHelper.php

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -538,11 +538,7 @@ function ($coordinate) use ($cellCollection) {
538538

539539
// Update workbook: define names
540540
if (count($worksheet->getParent()->getDefinedNames()) > 0) {
541-
foreach ($worksheet->getParent()->getDefinedNames() as $definedName) {
542-
if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) {
543-
$definedName->setValue($this->updateCellReference($definedName->getValue()));
544-
}
545-
}
541+
$this->updateDefinedNames($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
546542
}
547543

548544
// Garbage collect
@@ -866,13 +862,13 @@ private function updateCellReference($cellReference = 'A1', bool $includeAbsolut
866862
}
867863

868864
/**
869-
* Update named formulas (i.e. containing worksheet references / named ranges).
865+
* Update named formulae (i.e. containing worksheet references / named ranges).
870866
*
871867
* @param Spreadsheet $spreadsheet Object to update
872868
* @param string $oldName Old name (name to replace)
873869
* @param string $newName New name
874870
*/
875-
public function updateNamedFormulas(Spreadsheet $spreadsheet, $oldName = '', $newName = ''): void
871+
public function updateNamedFormulae(Spreadsheet $spreadsheet, $oldName = '', $newName = ''): void
876872
{
877873
if ($oldName == '') {
878874
return;
@@ -893,6 +889,41 @@ public function updateNamedFormulas(Spreadsheet $spreadsheet, $oldName = '', $ne
893889
}
894890
}
895891

892+
private function updateDefinedNames(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void
893+
{
894+
foreach ($worksheet->getParent()->getDefinedNames() as $definedName) {
895+
if ($definedName->isFormula() === false) {
896+
$this->updateNamedRange($definedName, $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
897+
} else {
898+
$this->updateNamedFormula($definedName, $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
899+
}
900+
}
901+
}
902+
903+
private function updateNamedRange(DefinedName $definedName, Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void
904+
{
905+
$cellAddress = $definedName->getValue();
906+
$asFormula = ($cellAddress[0] === '=');
907+
if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) {
908+
if ($asFormula === true) {
909+
$formula = $definedName->getValue();
910+
$formula = $this->updateFormulaReferences($formula, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle());
911+
$definedName->setValue($formula);
912+
} else {
913+
$definedName->setValue($asFormula . $this->updateCellReference(ltrim($cellAddress, '=')));
914+
}
915+
}
916+
}
917+
918+
private function updateNamedFormula(DefinedName $definedName, Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void
919+
{
920+
if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) {
921+
$formula = $definedName->getValue();
922+
$formula = $this->updateFormulaReferences($formula, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle());
923+
$definedName->setValue($formula);
924+
}
925+
}
926+
896927
/**
897928
* Update cell range.
898929
*

src/PhpSpreadsheet/Worksheet/Worksheet.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -923,7 +923,7 @@ public function setTitle($title, $updateFormulaCellReferences = true, $validate
923923
$this->parent->getCalculationEngine()
924924
->renameCalculationCacheForWorksheet($oldTitle, $newTitle);
925925
if ($updateFormulaCellReferences) {
926-
ReferenceHelper::getInstance()->updateNamedFormulas($this->parent, $oldTitle, $newTitle);
926+
ReferenceHelper::getInstance()->updateNamedFormulae($this->parent, $oldTitle, $newTitle);
927927
}
928928
}
929929

tests/PhpSpreadsheetTests/ReferenceHelperTest.php

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
namespace PhpOffice\PhpSpreadsheetTests;
44

5+
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
56
use PhpOffice\PhpSpreadsheet\Cell\DataType;
67
use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
78
use PhpOffice\PhpSpreadsheet\Comment;
9+
use PhpOffice\PhpSpreadsheet\NamedFormula;
10+
use PhpOffice\PhpSpreadsheet\NamedRange;
811
use PhpOffice\PhpSpreadsheet\ReferenceHelper;
912
use PhpOffice\PhpSpreadsheet\Spreadsheet;
1013
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
@@ -538,4 +541,110 @@ public function testDeleteColumnsWithPrintArea(): void
538541
$printArea = $sheet->getPageSetup()->getPrintArea();
539542
self::assertSame('A1:H10', $printArea);
540543
}
544+
545+
public function testInsertRowsWithDefinedNames(): void
546+
{
547+
$spreadsheet = $this->buildDefinedNamesTestWorkbook();
548+
/** @var Worksheet $dataSheet */
549+
$dataSheet = $spreadsheet->getSheetByName('Data');
550+
/** @var Worksheet $totalsSheet */
551+
$totalsSheet = $spreadsheet->getSheetByName('Totals');
552+
553+
$dataSheet->insertNewRowBefore(4, 2);
554+
Calculation::getInstance($spreadsheet)->flushInstance();
555+
556+
/** @var NamedRange $firstColumn */
557+
$firstColumn = $spreadsheet->getNamedRange('FirstColumn');
558+
/** @var NamedRange $secondColumn */
559+
$secondColumn = $spreadsheet->getNamedRange('SecondColumn');
560+
561+
self::assertSame('=Data!$A$2:$A8', $firstColumn->getRange());
562+
self::assertSame('=Data!B$2:B8', $secondColumn->getRange());
563+
self::assertSame(30, $totalsSheet->getCell('A20')->getCalculatedValue());
564+
self::assertSame(25, $totalsSheet->getCell('B20')->getCalculatedValue());
565+
self::assertSame(750, $totalsSheet->getCell('D20')->getCalculatedValue());
566+
}
567+
568+
public function testInsertColumnsWithDefinedNames(): void
569+
{
570+
$spreadsheet = $this->buildDefinedNamesTestWorkbook();
571+
/** @var Worksheet $dataSheet */
572+
$dataSheet = $spreadsheet->getSheetByName('Data');
573+
/** @var Worksheet $totalsSheet */
574+
$totalsSheet = $spreadsheet->getSheetByName('Totals');
575+
576+
$dataSheet->insertNewColumnBefore('B', 2);
577+
Calculation::getInstance($spreadsheet)->flushInstance();
578+
579+
/** @var NamedRange $firstColumn */
580+
$firstColumn = $spreadsheet->getNamedRange('FirstColumn');
581+
/** @var NamedRange $secondColumn */
582+
$secondColumn = $spreadsheet->getNamedRange('SecondColumn');
583+
584+
self::assertSame('=Data!$A$2:$A6', $firstColumn->getRange());
585+
self::assertSame('=Data!D$2:D6', $secondColumn->getRange());
586+
self::assertSame(30, $totalsSheet->getCell('A20')->getCalculatedValue());
587+
self::assertSame(25, $totalsSheet->getCell('B20')->getCalculatedValue());
588+
self::assertSame(750, $totalsSheet->getCell('D20')->getCalculatedValue());
589+
}
590+
591+
public function testDeleteRowsWithDefinedNames(): void
592+
{
593+
$spreadsheet = $this->buildDefinedNamesTestWorkbook();
594+
/** @var Worksheet $dataSheet */
595+
$dataSheet = $spreadsheet->getSheetByName('Data');
596+
/** @var Worksheet $totalsSheet */
597+
$totalsSheet = $spreadsheet->getSheetByName('Totals');
598+
599+
$dataSheet->removeRow(3, 2);
600+
Calculation::getInstance($spreadsheet)->flushInstance();
601+
602+
/** @var NamedRange $firstColumn */
603+
$firstColumn = $spreadsheet->getNamedRange('FirstColumn');
604+
/** @var NamedRange $secondColumn */
605+
$secondColumn = $spreadsheet->getNamedRange('SecondColumn');
606+
607+
self::assertSame('=Data!$A$2:$A4', $firstColumn->getRange());
608+
self::assertSame('=Data!B$2:B4', $secondColumn->getRange());
609+
self::assertSame(20, $totalsSheet->getCell('A20')->getCalculatedValue());
610+
self::assertSame(17, $totalsSheet->getCell('B20')->getCalculatedValue());
611+
self::assertSame(340, $totalsSheet->getCell('D20')->getCalculatedValue());
612+
}
613+
614+
private function buildDefinedNamesTestWorkbook(): Spreadsheet
615+
{
616+
$spreadsheet = new Spreadsheet();
617+
$dataSheet = $spreadsheet->getActiveSheet();
618+
$dataSheet->setTitle('Data');
619+
620+
$totalsSheet = $spreadsheet->addSheet(new Worksheet());
621+
$totalsSheet->setTitle('Totals');
622+
623+
$spreadsheet->setActiveSheetIndexByName('Data');
624+
625+
$dataSheet->fromArray([['Column 1', 'Column 2'], [2, 1], [4, 3], [6, 5], [8, 7], [10, 9]], null, 'A1', true);
626+
627+
$spreadsheet->addNamedRange(
628+
new NamedRange('FirstColumn', $spreadsheet->getActiveSheet(), '=Data!$A$2:$A6')
629+
);
630+
$spreadsheet->addNamedFormula(
631+
new NamedFormula('FirstTotal', $spreadsheet->getActiveSheet(), '=SUM(FirstColumn)')
632+
);
633+
$totalsSheet->setCellValue('A20', '=FirstTotal');
634+
635+
$spreadsheet->addNamedRange(
636+
new NamedRange('SecondColumn', $spreadsheet->getActiveSheet(), '=Data!B$2:B6')
637+
);
638+
$spreadsheet->addNamedFormula(
639+
new NamedFormula('SecondTotal', $spreadsheet->getActiveSheet(), '=SUM(SecondColumn)')
640+
);
641+
$totalsSheet->setCellValue('B20', '=SecondTotal');
642+
643+
$spreadsheet->addNamedFormula(
644+
new NamedFormula('ProductTotal', $spreadsheet->getActiveSheet(), '=FirstTotal*SecondTotal')
645+
);
646+
$totalsSheet->setCellValue('D20', '=ProductTotal');
647+
648+
return $spreadsheet;
649+
}
541650
}

0 commit comments

Comments
 (0)