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
This commit is contained in:
commit
6f5597d7b1
|
|
@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- 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)
|
||||||
- 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)
|
- 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)
|
||||||
- Named ranges not usable as anchors in OFFSET function [Issue #3013](https://github.com/PHPOffice/PhpSpreadsheet/issues/3013)
|
- Named ranges not usable as anchors in OFFSET function [Issue #3013](https://github.com/PHPOffice/PhpSpreadsheet/issues/3013)
|
||||||
- Fully flatten an array [Issue #2955](https://github.com/PHPOffice/PhpSpreadsheet/issues/2955) [PR #2956](https://github.com/PHPOffice/PhpSpreadsheet/pull/2956)
|
- Fully flatten an array [Issue #2955](https://github.com/PHPOffice/PhpSpreadsheet/issues/2955) [PR #2956](https://github.com/PHPOffice/PhpSpreadsheet/pull/2956)
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ abstract class DefinedName
|
||||||
|
|
||||||
// New title
|
// New title
|
||||||
$newTitle = $this->name;
|
$newTitle = $this->name;
|
||||||
ReferenceHelper::getInstance()->updateNamedFormulas($this->worksheet->getParent(), $oldTitle, $newTitle);
|
ReferenceHelper::getInstance()->updateNamedFormulae($this->worksheet->getParent(), $oldTitle, $newTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -866,13 +862,13 @@ class ReferenceHelper
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update named formulas (i.e. containing worksheet references / named ranges).
|
* Update named formulae (i.e. containing worksheet references / named ranges).
|
||||||
*
|
*
|
||||||
* @param Spreadsheet $spreadsheet Object to update
|
* @param Spreadsheet $spreadsheet Object to update
|
||||||
* @param string $oldName Old name (name to replace)
|
* @param string $oldName Old name (name to replace)
|
||||||
* @param string $newName New name
|
* @param string $newName New name
|
||||||
*/
|
*/
|
||||||
public function updateNamedFormulas(Spreadsheet $spreadsheet, $oldName = '', $newName = ''): void
|
public function updateNamedFormulae(Spreadsheet $spreadsheet, $oldName = '', $newName = ''): void
|
||||||
{
|
{
|
||||||
if ($oldName == '') {
|
if ($oldName == '') {
|
||||||
return;
|
return;
|
||||||
|
|
@ -893,6 +889,41 @@ class ReferenceHelper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function updateDefinedNames(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void
|
||||||
|
{
|
||||||
|
foreach ($worksheet->getParent()->getDefinedNames() as $definedName) {
|
||||||
|
if ($definedName->isFormula() === false) {
|
||||||
|
$this->updateNamedRange($definedName, $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||||
|
} else {
|
||||||
|
$this->updateNamedFormula($definedName, $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateNamedRange(DefinedName $definedName, Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void
|
||||||
|
{
|
||||||
|
$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, '=')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateNamedFormula(DefinedName $definedName, Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void
|
||||||
|
{
|
||||||
|
if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) {
|
||||||
|
$formula = $definedName->getValue();
|
||||||
|
$formula = $this->updateFormulaReferences($formula, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle());
|
||||||
|
$definedName->setValue($formula);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update cell range.
|
* Update cell range.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -923,7 +923,7 @@ class Worksheet implements IComparable
|
||||||
$this->parent->getCalculationEngine()
|
$this->parent->getCalculationEngine()
|
||||||
->renameCalculationCacheForWorksheet($oldTitle, $newTitle);
|
->renameCalculationCacheForWorksheet($oldTitle, $newTitle);
|
||||||
if ($updateFormulaCellReferences) {
|
if ($updateFormulaCellReferences) {
|
||||||
ReferenceHelper::getInstance()->updateNamedFormulas($this->parent, $oldTitle, $newTitle);
|
ReferenceHelper::getInstance()->updateNamedFormulae($this->parent, $oldTitle, $newTitle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue