diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 3491b02c74..5047f49e97 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -5745,7 +5745,8 @@ private function evaluateDefinedName(Cell $cell, DefinedName $namedRange, Worksh $this->debugLog->writeDebugLog('Defined Name is a %s with a value of %s', $definedNameType, $definedNameValue); - $recursiveCalculationCell = ($definedNameWorksheet !== null && $definedNameWorksheet !== $cellWorksheet) + $originalCoordinate = $cell->getCoordinate(); + $recursiveCalculationCell = ($definedNameType !== 'Formula' && $definedNameWorksheet !== null && $definedNameWorksheet !== $cellWorksheet) ? $definedNameWorksheet->getCell('A1') : $cell; $recursiveCalculationCellAddress = $recursiveCalculationCell->getCoordinate(); @@ -5763,6 +5764,7 @@ private function evaluateDefinedName(Cell $cell, DefinedName $namedRange, Worksh $recursiveCalculator->getDebugLog()->setWriteDebugLog($this->getDebugLog()->getWriteDebugLog()); $recursiveCalculator->getDebugLog()->setEchoDebugLog($this->getDebugLog()->getEchoDebugLog()); $result = $recursiveCalculator->_calculateFormulaValue($definedNameValue, $recursiveCalculationCellAddress, $recursiveCalculationCell, true); + $cellWorksheet->getCell($originalCoordinate); if ($this->getDebugLog()->getWriteDebugLog()) { $this->debugLog->mergeDebugLog(array_slice($recursiveCalculator->getDebugLog()->getLog(), 3)); diff --git a/src/PhpSpreadsheet/CellReferenceHelper.php b/src/PhpSpreadsheet/CellReferenceHelper.php index 0e164543b4..6e036354f0 100644 --- a/src/PhpSpreadsheet/CellReferenceHelper.php +++ b/src/PhpSpreadsheet/CellReferenceHelper.php @@ -56,7 +56,7 @@ public function refreshRequired(string $beforeCellAddress, int $numberOfColumns, $this->numberOfRows !== $numberOfRows; } - public function updateCellReference(string $cellReference = 'A1', bool $includeAbsoluteReferences = false): string + public function updateCellReference(string $cellReference = 'A1', bool $includeAbsoluteReferences = false, bool $onlyAbsoluteReferences = false): string { if (Coordinate::coordinateIsRange($cellReference)) { throw new Exception('Only single cell references may be passed to this method.'); @@ -70,7 +70,10 @@ public function updateCellReference(string $cellReference = 'A1', bool $includeA $absoluteColumn = $newColumn[0] === '$' ? '$' : ''; $absoluteRow = $newRow[0] === '$' ? '$' : ''; // Verify which parts should be updated - if ($includeAbsoluteReferences === false) { + if ($onlyAbsoluteReferences === true) { + $updateColumn = (($absoluteColumn === '$') && $newColumnIndex >= $this->beforeColumn); + $updateRow = (($absoluteRow === '$') && $newRowIndex >= $this->beforeRow); + } elseif ($includeAbsoluteReferences === false) { $updateColumn = (($absoluteColumn !== '$') && $newColumnIndex >= $this->beforeColumn); $updateRow = (($absoluteRow !== '$') && $newRowIndex >= $this->beforeRow); } else { diff --git a/src/PhpSpreadsheet/ReferenceHelper.php b/src/PhpSpreadsheet/ReferenceHelper.php index 90eee534db..4c7de199ee 100644 --- a/src/PhpSpreadsheet/ReferenceHelper.php +++ b/src/PhpSpreadsheet/ReferenceHelper.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheet; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; +use PhpOffice\PhpSpreadsheet\Cell\AddressRange; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Style\Conditional; @@ -572,7 +573,8 @@ public function updateFormulaReferences( $numberOfColumns = 0, $numberOfRows = 0, $worksheetName = '', - bool $includeAbsoluteReferences = false + bool $includeAbsoluteReferences = false, + bool $onlyAbsoluteReferences = false ) { if ( $this->cellReferenceHelper === null || @@ -596,8 +598,8 @@ public function updateFormulaReferences( foreach ($matches as $match) { $fromString = ($match[2] > '') ? $match[2] . '!' : ''; $fromString .= $match[3] . ':' . $match[4]; - $modified3 = substr($this->updateCellReference('$A' . $match[3], $includeAbsoluteReferences), 2); - $modified4 = substr($this->updateCellReference('$A' . $match[4], $includeAbsoluteReferences), 2); + $modified3 = substr($this->updateCellReference('$A' . $match[3], $includeAbsoluteReferences, $onlyAbsoluteReferences), 2); + $modified4 = substr($this->updateCellReference('$A' . $match[4], $includeAbsoluteReferences, $onlyAbsoluteReferences), 2); if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) { if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { @@ -621,8 +623,8 @@ public function updateFormulaReferences( foreach ($matches as $match) { $fromString = ($match[2] > '') ? $match[2] . '!' : ''; $fromString .= $match[3] . ':' . $match[4]; - $modified3 = substr($this->updateCellReference($match[3] . '$1', $includeAbsoluteReferences), 0, -2); - $modified4 = substr($this->updateCellReference($match[4] . '$1', $includeAbsoluteReferences), 0, -2); + $modified3 = substr($this->updateCellReference($match[3] . '$1', $includeAbsoluteReferences, $onlyAbsoluteReferences), 0, -2); + $modified4 = substr($this->updateCellReference($match[4] . '$1', $includeAbsoluteReferences, $onlyAbsoluteReferences), 0, -2); if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) { if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { @@ -646,8 +648,8 @@ public function updateFormulaReferences( foreach ($matches as $match) { $fromString = ($match[2] > '') ? $match[2] . '!' : ''; $fromString .= $match[3] . ':' . $match[4]; - $modified3 = $this->updateCellReference($match[3], $includeAbsoluteReferences); - $modified4 = $this->updateCellReference($match[4], $includeAbsoluteReferences); + $modified3 = $this->updateCellReference($match[3], $includeAbsoluteReferences, $onlyAbsoluteReferences); + $modified4 = $this->updateCellReference($match[4], $includeAbsoluteReferences, $onlyAbsoluteReferences); if ($match[3] . $match[4] !== $modified3 . $modified4) { if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { @@ -674,7 +676,7 @@ public function updateFormulaReferences( $fromString = ($match[2] > '') ? $match[2] . '!' : ''; $fromString .= $match[3]; - $modified3 = $this->updateCellReference($match[3], $includeAbsoluteReferences); + $modified3 = $this->updateCellReference($match[3], $includeAbsoluteReferences, $onlyAbsoluteReferences); if ($match[3] !== $modified3) { if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { $toString = ($match[2] > '') ? $match[2] . '!' : ''; @@ -757,11 +759,13 @@ private function updateCellReferencesAllWorksheets(string $formula, int $numberO $row = $rows[$splitCount][0]; if (!empty($column) && $column[0] !== '$') { - $column = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($column) + $numberOfColumns); + $column = ((Coordinate::columnIndexFromString($column) + $numberOfColumns) % AddressRange::MAX_COLUMN_INT) ?: AddressRange::MAX_COLUMN_INT; + $column = Coordinate::stringFromColumnIndex($column); + $rowOffset -= ($columnLength - strlen($column)); $formula = substr($formula, 0, $columnOffset) . $column . substr($formula, $columnOffset + $columnLength); } if (!empty($row) && $row[0] !== '$') { - $row = (int) $row + $numberOfRows; + $row = (((int) $row + $numberOfRows) % AddressRange::MAX_ROW) ?: AddressRange::MAX_ROW; $formula = substr($formula, 0, $rowOffset) . $row . substr($formula, $rowOffset + $rowLength); } } @@ -854,7 +858,7 @@ private function updateRowRangesAllWorksheets(string $formula, int $numberOfRows * * @return string Updated cell range */ - private function updateCellReference($cellReference = 'A1', bool $includeAbsoluteReferences = false) + private function updateCellReference($cellReference = 'A1', bool $includeAbsoluteReferences = false, bool $onlyAbsoluteReferences = false) { // Is it in another worksheet? Will not have to update anything. if (strpos($cellReference, '!') !== false) { @@ -863,11 +867,11 @@ private function updateCellReference($cellReference = 'A1', bool $includeAbsolut // Is it a range or a single cell? if (!Coordinate::coordinateIsRange($cellReference)) { // Single cell - return $this->cellReferenceHelper->updateCellReference($cellReference, $includeAbsoluteReferences); + return $this->cellReferenceHelper->updateCellReference($cellReference, $includeAbsoluteReferences, $onlyAbsoluteReferences); } // Range - return $this->updateCellRange($cellReference, $includeAbsoluteReferences); + return $this->updateCellRange($cellReference, $includeAbsoluteReferences, $onlyAbsoluteReferences); } /** @@ -922,7 +926,7 @@ private function updateNamedRange(DefinedName $definedName, Worksheet $worksheet * them with a #REF! */ if ($asFormula === true) { - $formula = $this->updateFormulaReferences($cellAddress, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true); + $formula = $this->updateFormulaReferences($cellAddress, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true, true); $definedName->setValue($formula); } else { $definedName->setValue($this->updateCellReference(ltrim($cellAddress, '='), true)); @@ -953,7 +957,7 @@ private function updateNamedFormula(DefinedName $definedName, Worksheet $workshe * * @return string Updated cell range */ - private function updateCellRange(string $cellRange = 'A1:A1', bool $includeAbsoluteReferences = false): string + private function updateCellRange(string $cellRange = 'A1:A1', bool $includeAbsoluteReferences = false, bool $onlyAbsoluteReferences = false): string { if (!Coordinate::coordinateIsRange($cellRange)) { throw new Exception('Only cell ranges may be passed to this method.'); @@ -967,14 +971,14 @@ private function updateCellRange(string $cellRange = 'A1:A1', bool $includeAbsol for ($j = 0; $j < $jc; ++$j) { if (ctype_alpha($range[$i][$j])) { $range[$i][$j] = Coordinate::coordinateFromString( - $this->cellReferenceHelper->updateCellReference($range[$i][$j] . '1', $includeAbsoluteReferences) + $this->cellReferenceHelper->updateCellReference($range[$i][$j] . '1', $includeAbsoluteReferences, $onlyAbsoluteReferences) )[0]; } elseif (ctype_digit($range[$i][$j])) { $range[$i][$j] = Coordinate::coordinateFromString( - $this->cellReferenceHelper->updateCellReference('A' . $range[$i][$j], $includeAbsoluteReferences) + $this->cellReferenceHelper->updateCellReference('A' . $range[$i][$j], $includeAbsoluteReferences, $onlyAbsoluteReferences) )[1]; } else { - $range[$i][$j] = $this->cellReferenceHelper->updateCellReference($range[$i][$j], $includeAbsoluteReferences); + $range[$i][$j] = $this->cellReferenceHelper->updateCellReference($range[$i][$j], $includeAbsoluteReferences, $onlyAbsoluteReferences); } } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndirectTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndirectTest.php index e10836ce6a..8f0727e0c2 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndirectTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndirectTest.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef; +use PhpOffice\PhpSpreadsheet\NamedFormula; use PhpOffice\PhpSpreadsheet\NamedRange; use PhpOffice\PhpSpreadsheet\Reader\Xlsx as ReaderXlsx; @@ -176,4 +177,33 @@ public static function providerRelative(): array 'uninitialized cell' => [null, 'RC[+2]'], // Excel result is 0 ]; } + + /** @var bool */ + private static $definedFormulaWorking = false; + + public function testAboveCell(): void + { + $spreadsheet = $this->getSpreadsheet(); + $spreadsheet->addNamedFormula( + new NamedFormula('SumAbove', $spreadsheet->getActiveSheet(), '=SUM(INDIRECT(ADDRESS(1,COLUMN())):INDIRECT(ADDRESS(ROW()-1,COLUMN())))') + ); + $sheet = $this->getSheet(); + $sheet->getCell('A1')->setValue(100); + $sheet->getCell('A2')->setValue(200); + $sheet->getCell('A3')->setValue(300); + $sheet->getCell('A4')->setValue(400); + $sheet->getCell('A5')->setValue(500); + $sheet->getCell('A6')->setValue('=SUM(A$1:INDIRECT(ADDRESS(ROW()-1,COLUMN())))'); + self::AssertSame(1500, $sheet->getCell('A6')->getCalculatedValue()); + if (self::$definedFormulaWorking) { + $sheet->getCell('B1')->setValue(10); + $sheet->getCell('B2')->setValue(20); + $sheet->getCell('B3')->setValue(30); + $sheet->getCell('B4')->setValue(40); + $sheet->getCell('B5')->setValue('=SumAbove'); + self::AssertSame(100, $sheet->getCell('B5')->getCalculatedValue()); + } else { + self::markTestIncomplete('PhpSpreadsheet does not handle this correctly'); + } + } } diff --git a/tests/PhpSpreadsheetTests/ReferenceHelper3Test.php b/tests/PhpSpreadsheetTests/ReferenceHelper3Test.php new file mode 100644 index 0000000000..b1ee653fbe --- /dev/null +++ b/tests/PhpSpreadsheetTests/ReferenceHelper3Test.php @@ -0,0 +1,113 @@ +getActiveSheet(); + $sheet->setTitle('Data'); + + $spreadsheet->addNamedRange(new NamedRange('FIRST', $sheet, '=$A1')); + $spreadsheet->addNamedRange(new NamedRange('SECOND', $sheet, '=$B1')); + $spreadsheet->addNamedRange(new NamedRange('THIRD', $sheet, '=$C1')); + + $sheet->fromArray([ + [1, 2, 3, '=FIRST', '=SECOND', '=THIRD', '=10*$A1'], + [4, 5, 6, '=FIRST', '=SECOND', '=THIRD'], + [7, 8, 9, '=FIRST', '=SECOND', '=THIRD'], + ]); + + $sheet->insertNewRowBefore(1, 4); + $sheet->insertNewColumnBefore('A', 1); + self::assertSame(1, $sheet->getCell('E5')->getCalculatedValue()); + self::assertSame(5, $sheet->getCell('F6')->getCalculatedValue()); + self::assertSame(9, $sheet->getCell('G7')->getCalculatedValue()); + self::assertSame('=10*$B5', $sheet->getCell('H5')->getValue()); + self::assertSame(10, $sheet->getCell('H5')->getCalculatedValue()); + $firstColumn = $spreadsheet->getNamedRange('FIRST'); + /** @var NamedRange $firstColumn */ + self::assertSame('=$B1', $firstColumn->getRange()); + $spreadsheet->disconnectWorksheets(); + } + + public function testCompletelyRelative(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setTitle('Data'); + + $spreadsheet->addNamedRange(new NamedRange('CellAbove', $sheet, '=A1048576')); + $spreadsheet->addNamedRange(new NamedRange('CellBelow', $sheet, '=A2')); + $spreadsheet->addNamedRange(new NamedRange('CellToLeft', $sheet, '=XFD1')); + $spreadsheet->addNamedRange(new NamedRange('CellToRight', $sheet, '=B1')); + + $sheet->fromArray([ + [null, 'Above', null, null, 'Above', null, null, 'Above', null, null, 'Above', null], + ['Left', '=CellAbove', 'Right', 'Left', '=CellBelow', 'Right', 'Left', '=CellToLeft', 'Right', 'Left', '=CellToRight', 'Right'], + [null, 'Below', null, null, 'Below', null, null, 'Below', null, null, 'Below', null], + ], null, 'A1', true); + self::assertSame('Above', $sheet->getCell('B2')->getCalculatedValue()); + self::assertSame('Below', $sheet->getCell('E2')->getCalculatedValue()); + self::assertSame('Left', $sheet->getCell('H2')->getCalculatedValue()); + self::assertSame('Right', $sheet->getCell('K2')->getCalculatedValue()); + + Calculation::getInstance($spreadsheet)->flushInstance(); + self::assertNull($sheet->getCell('L7')->getCalculatedValue(), 'value in L7 after flush is null'); + // Reset it once more + Calculation::getInstance($spreadsheet)->flushInstance(); + // shift 5 rows down and 1 column to the right + $sheet->insertNewRowBefore(1, 5); + $sheet->insertNewColumnBefore('A', 1); + + self::assertSame('Above', $sheet->getCell('C7')->getCalculatedValue()); // Above + self::assertSame('Below', $sheet->getCell('F7')->getCalculatedValue()); + self::assertSame('Left', $sheet->getCell('I7')->getCalculatedValue()); + self::assertSame('Right', $sheet->getCell('L7')->getCalculatedValue()); + + $sheet2 = $spreadsheet->createSheet(); + $sheet2->setCellValue('L6', 'NotThisCell'); + $sheet2->setCellValue('L7', '=CellAbove'); + self::assertSame('Above', $sheet2->getCell('L7')->getCalculatedValue(), 'relative value uses cell on worksheet where name is defined'); + $spreadsheet->disconnectWorksheets(); + } + + /** @var bool */ + private static $sumFormulaWorking = false; + + public function testSumAboveCell(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $spreadsheet->addNamedRange(new NamedRange('AboveCell', $sheet, 'A1048576')); + $sheet->setCellValue('C2', 123); + $sheet->setCellValue('C3', '=AboveCell'); + $sheet->fromArray([ + ['Column 1', 'Column 2'], + [2, 1], + [4, 3], + [6, 5], + [8, 7], + [10, 9], + [12, 11], + [14, 13], + [16, 15], + ['=SUM(A2:AboveCell)', '=SUM(B2:AboveCell)'], + ], null, 'A1', true); + self::assertSame(123, $sheet->getCell('C3')->getCalculatedValue()); + if (self::$sumFormulaWorking) { + self::assertSame(72, $sheet->getCell('A10')->getCalculatedValue()); + } else { + $spreadsheet->disconnectWorksheets(); + self::markTestIncomplete('PhpSpreadsheet does not handle this correctly'); + } + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/ReferenceHelperTest.php b/tests/PhpSpreadsheetTests/ReferenceHelperTest.php index 4357bfe3bc..05b3376f8e 100644 --- a/tests/PhpSpreadsheetTests/ReferenceHelperTest.php +++ b/tests/PhpSpreadsheetTests/ReferenceHelperTest.php @@ -2,7 +2,6 @@ namespace PhpOffice\PhpSpreadsheetTests; -use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Cell\Hyperlink; use PhpOffice\PhpSpreadsheet\Comment; @@ -151,6 +150,7 @@ public function testInsertNewBeforeRetainDataType(): void self::assertSame($oldValue, $newValue); self::assertSame($oldDataType, $newDataType); + $spreadsheet->disconnectWorksheets(); } public function testRemoveColumnShiftsCorrectColumnValueIntoRemovedColumnCoordinates(): void @@ -179,6 +179,7 @@ public function testRemoveColumnShiftsCorrectColumnValueIntoRemovedColumnCoordin self::assertSame('a2', $cells[1][0]); self::assertNull($cells[1][1]); self::assertArrayNotHasKey(2, $cells[1]); + $spreadsheet->disconnectWorksheets(); } public function testInsertRowsWithPageBreaks(): void @@ -194,6 +195,7 @@ public function testInsertRowsWithPageBreaks(): void $breaks = $sheet->getBreaks(); ksort($breaks); self::assertSame(['A4' => Worksheet::BREAK_ROW, 'A7' => Worksheet::BREAK_ROW], $breaks); + $spreadsheet->disconnectWorksheets(); } public function testDeleteRowsWithPageBreaks(): void @@ -208,6 +210,7 @@ public function testDeleteRowsWithPageBreaks(): void $breaks = $sheet->getBreaks(); self::assertSame(['A3' => Worksheet::BREAK_ROW], $breaks); + $spreadsheet->disconnectWorksheets(); } public function testInsertRowsWithComments(): void @@ -228,6 +231,7 @@ function (Comment $value) { ); self::assertSame(['A4' => 'First Comment', 'A7' => 'Second Comment'], $comments); + $spreadsheet->disconnectWorksheets(); } public function testDeleteRowsWithComments(): void @@ -248,6 +252,7 @@ function (Comment $value) { ); self::assertSame(['A3' => 'Second Comment'], $comments); + $spreadsheet->disconnectWorksheets(); } public function testInsertRowsWithHyperlinks(): void @@ -275,6 +280,7 @@ function (Hyperlink $value) { ], $hyperlinks ); + $spreadsheet->disconnectWorksheets(); } public function testDeleteRowsWithHyperlinks(): void @@ -295,6 +301,7 @@ function (Hyperlink $value) { ); self::assertSame(['A3' => 'https://phpspreadsheet.readthedocs.io/en/latest/'], $hyperlinks); + $spreadsheet->disconnectWorksheets(); } public function testInsertRowsWithDataValidation(): void @@ -311,6 +318,7 @@ public function testInsertRowsWithDataValidation(): void self::assertFalse($sheet->getCell($cellAddress)->hasDataValidation()); self::assertTrue($sheet->getCell('E7')->hasDataValidation()); self::assertSame('E7', $sheet->getDataValidation('E7')->getSqref()); + $spreadsheet->disconnectWorksheets(); } public function testDeleteRowsWithDataValidation(): void @@ -327,6 +335,7 @@ public function testDeleteRowsWithDataValidation(): void self::assertFalse($sheet->getCell($cellAddress)->hasDataValidation()); self::assertTrue($sheet->getCell('E3')->hasDataValidation()); self::assertSame('E3', $sheet->getDataValidation('E3')->getSqref()); + $spreadsheet->disconnectWorksheets(); } public function testDeleteColumnsWithDataValidation(): void @@ -343,6 +352,7 @@ public function testDeleteColumnsWithDataValidation(): void self::assertFalse($sheet->getCell($cellAddress)->hasDataValidation()); self::assertTrue($sheet->getCell('C5')->hasDataValidation()); self::assertSame('C5', $sheet->getDataValidation('C5')->getSqref()); + $spreadsheet->disconnectWorksheets(); } public function testInsertColumnsWithDataValidation(): void @@ -359,6 +369,7 @@ public function testInsertColumnsWithDataValidation(): void self::assertFalse($sheet->getCell($cellAddress)->hasDataValidation()); self::assertTrue($sheet->getCell('G5')->hasDataValidation()); self::assertSame('G5', $sheet->getDataValidation('G5')->getSqref()); + $spreadsheet->disconnectWorksheets(); } private function setDataValidation(Worksheet $sheet, string $cellAddress): void @@ -399,6 +410,7 @@ public function testInsertRowsWithConditionalFormatting(): void self::assertSame('$H$7', $conditions->getConditions()[0]); } } + $spreadsheet->disconnectWorksheets(); } public function testInsertColumnssWithConditionalFormatting(): void @@ -422,6 +434,7 @@ public function testInsertColumnssWithConditionalFormatting(): void self::assertSame('$J$5', $conditions->getConditions()[0]); } } + $spreadsheet->disconnectWorksheets(); } public function testDeleteRowsWithConditionalFormatting(): void @@ -445,6 +458,7 @@ public function testDeleteRowsWithConditionalFormatting(): void self::assertSame('$H$5', $conditions->getConditions()[0]); } } + $spreadsheet->disconnectWorksheets(); } public function testDeleteColumnsWithConditionalFormatting(): void @@ -468,6 +482,7 @@ public function testDeleteColumnsWithConditionalFormatting(): void self::assertSame('$F$5', $conditions->getConditions()[0]); } } + $spreadsheet->disconnectWorksheets(); } private function setConditionalFormatting(Worksheet $sheet, string $cellRange): void @@ -500,6 +515,7 @@ public function testInsertRowsWithPrintArea(): void $printArea = $sheet->getPageSetup()->getPrintArea(); self::assertSame('A1:J12', $printArea); + $spreadsheet->disconnectWorksheets(); } public function testInsertColumnsWithPrintArea(): void @@ -512,6 +528,7 @@ public function testInsertColumnsWithPrintArea(): void $printArea = $sheet->getPageSetup()->getPrintArea(); self::assertSame('A1:L10', $printArea); + $spreadsheet->disconnectWorksheets(); } public function testDeleteRowsWithPrintArea(): void @@ -524,6 +541,7 @@ public function testDeleteRowsWithPrintArea(): void $printArea = $sheet->getPageSetup()->getPrintArea(); self::assertSame('A1:J8', $printArea); + $spreadsheet->disconnectWorksheets(); } public function testDeleteColumnsWithPrintArea(): void @@ -536,9 +554,10 @@ public function testDeleteColumnsWithPrintArea(): void $printArea = $sheet->getPageSetup()->getPrintArea(); self::assertSame('A1:H10', $printArea); + $spreadsheet->disconnectWorksheets(); } - public function testInsertRowsWithDefinedNames(): void + public function testInsertDeleteRowsWithDefinedNames(): void { $spreadsheet = $this->buildDefinedNamesTestWorkbook(); /** @var Worksheet $dataSheet */ @@ -546,45 +565,48 @@ public function testInsertRowsWithDefinedNames(): void /** @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()); - } + $dataSheet->setCellValue('D2', '=FirstTotal'); + $dataSheet->setCellValue('D3', '=FirstTotal'); + $dataSheet->setCellValue('B2', '=SecondTotal'); + $dataSheet->setCellValue('B3', '=SecondTotal'); + $dataSheet->setCellValue('B4', '=ProductTotal'); - public function testInsertColumnsWithDefinedNames(): void - { - $spreadsheet = $this->buildDefinedNamesTestWorkbook(); - /** @var Worksheet $dataSheet */ - $dataSheet = $spreadsheet->getSheetByName('Data'); - /** @var Worksheet $totalsSheet */ - $totalsSheet = $spreadsheet->getSheetByName('Totals'); + $dataSheet->insertNewRowBefore(2, 5); // 5 rows before row 2 + self::assertSame('=Data!$A$7:$A6', $firstColumn->getRange()); + self::assertSame('=Data!B$7:B6', $secondColumn->getRange()); + $dataSheet->removeRow(2, 1); // remove one of inserted rows + self::assertSame('=Data!$A$6:$A6', $firstColumn->getRange()); + self::assertSame('=Data!B$6:B6', $secondColumn->getRange()); - $dataSheet->insertNewColumnBefore('B', 2); - Calculation::getInstance($spreadsheet)->flushInstance(); + self::assertSame('=Data!$A$6:$A6', $firstColumn->getRange()); + self::assertSame('=Data!B$6:B6', $secondColumn->getRange()); - /** @var NamedRange $firstColumn */ - $firstColumn = $spreadsheet->getNamedRange('FirstColumn'); - /** @var NamedRange $secondColumn */ - $secondColumn = $spreadsheet->getNamedRange('SecondColumn'); + self::assertSame(42, $dataSheet->getCell('D6')->getCalculatedValue()); + self::assertSame(56, $dataSheet->getCell('D7')->getCalculatedValue()); + self::assertSame(36, $dataSheet->getCell('B6')->getCalculatedValue()); + self::assertSame(49, $dataSheet->getCell('B7')->getCalculatedValue()); - 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()); + $totalsSheet->setCellValue('D6', '=FirstTotal'); + $totalsSheet->setCellValue('D7', '=FirstTotal'); + $totalsSheet->setCellValue('B6', '=SecondTotal'); + $totalsSheet->setCellValue('B7', '=SecondTotal'); + $totalsSheet->setCellValue('B8', '=ProductTotal'); + self::assertSame($dataSheet->getCell('D6')->getCalculatedValue(), $totalsSheet->getCell('D6')->getCalculatedValue()); + self::assertSame($dataSheet->getCell('D7')->getCalculatedValue(), $totalsSheet->getCell('D7')->getCalculatedValue()); + self::assertSame($dataSheet->getCell('B6')->getCalculatedValue(), $totalsSheet->getCell('B6')->getCalculatedValue()); + self::assertSame($dataSheet->getCell('B7')->getCalculatedValue(), $totalsSheet->getCell('B7')->getCalculatedValue()); + self::assertSame(4608, $dataSheet->getCell('B8')->getCalculatedValue()); + self::assertSame($dataSheet->getCell('B8')->getCalculatedValue(), $totalsSheet->getCell('B8')->getCalculatedValue()); + + $spreadsheet->disconnectWorksheets(); } - public function testDeleteRowsWithDefinedNames(): void + public function testInsertDeleteColumnsWithDefinedNames(): void { $spreadsheet = $this->buildDefinedNamesTestWorkbook(); /** @var Worksheet $dataSheet */ @@ -592,19 +614,37 @@ public function testDeleteRowsWithDefinedNames(): void /** @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()); + $dataSheet->setCellValue('D2', '=FirstTotal'); + $dataSheet->setCellValue('D3', '=FirstTotal'); + $dataSheet->setCellValue('B2', '=SecondTotal'); + $dataSheet->setCellValue('B3', '=SecondTotal'); + $dataSheet->setCellValue('B4', '=ProductTotal'); + + $dataSheet->insertNewColumnBefore('A', 3); + self::assertSame('=Data!$D$2:$D6', $firstColumn->getRange()); + self::assertSame('=Data!B$2:B6', $secondColumn->getRange()); + $dataSheet->removeColumn('A'); + self::assertSame('=Data!$C$2:$C6', $firstColumn->getRange()); + self::assertSame('=Data!B$2:B6', $secondColumn->getRange()); + + self::assertSame(42, $dataSheet->getCell('F2')->getCalculatedValue()); + self::assertSame(56, $dataSheet->getCell('F3')->getCalculatedValue()); + self::assertSame(36, $dataSheet->getCell('D2')->getCalculatedValue()); + self::assertSame(49, $dataSheet->getCell('D3')->getCalculatedValue()); + + $totalsSheet->setCellValue('B2', '=SecondTotal'); + $totalsSheet->setCellValue('B3', '=SecondTotal'); + self::assertSame(42, $totalsSheet->getCell('B2')->getCalculatedValue()); + self::assertSame(56, $totalsSheet->getCell('B3')->getCalculatedValue()); + + self::assertSame(4608, $dataSheet->getCell('D4')->getCalculatedValue()); + + $spreadsheet->disconnectWorksheets(); } private function buildDefinedNamesTestWorkbook(): Spreadsheet @@ -618,7 +658,8 @@ private function buildDefinedNamesTestWorkbook(): Spreadsheet $spreadsheet->setActiveSheetIndexByName('Data'); - $dataSheet->fromArray([['Column 1', 'Column 2'], [2, 1], [4, 3], [6, 5], [8, 7], [10, 9]], null, 'A1', true); + $dataSheet->fromArray([['Column 1', 'Column 2'], [2, 1], [4, 3], [6, 5], [8, 7], [10, 9], [12, 11], [14, 13], [16, 15]], null, 'A1', true); + $dataSheet->insertNewColumnBefore('B', 1); $spreadsheet->addNamedRange( new NamedRange('FirstColumn', $spreadsheet->getActiveSheet(), '=Data!$A$2:$A6') @@ -626,7 +667,6 @@ private function buildDefinedNamesTestWorkbook(): Spreadsheet $spreadsheet->addNamedFormula( new NamedFormula('FirstTotal', $spreadsheet->getActiveSheet(), '=SUM(FirstColumn)') ); - $totalsSheet->setCellValue('A20', '=FirstTotal'); $spreadsheet->addNamedRange( new NamedRange('SecondColumn', $spreadsheet->getActiveSheet(), '=Data!B$2:B6') @@ -634,6 +674,41 @@ private function buildDefinedNamesTestWorkbook(): Spreadsheet $spreadsheet->addNamedFormula( new NamedFormula('SecondTotal', $spreadsheet->getActiveSheet(), '=SUM(SecondColumn)') ); + + $spreadsheet->addNamedFormula( + new NamedFormula('ProductTotal', $spreadsheet->getActiveSheet(), '=FirstTotal*SecondTotal') + ); + + return $spreadsheet; + } + + private function buildDefinedNamesAbsoluteWorkbook(): 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], [12, 11], [14, 13], [16, 15]], null, 'A1', true); + + $spreadsheet->addNamedRange( + new NamedRange('FirstColumn', $spreadsheet->getActiveSheet(), '=Data!$A$2:$A$6') + ); + $spreadsheet->addNamedFormula( + new NamedFormula('FirstTotal', $spreadsheet->getActiveSheet(), '=SUM(FirstColumn)') + ); + $totalsSheet->setCellValue('A20', '=FirstTotal'); + + $spreadsheet->addNamedRange( + new NamedRange('SecondColumn', $spreadsheet->getActiveSheet(), '=Data!$B$2:$B$6') + ); + $spreadsheet->addNamedFormula( + new NamedFormula('SecondTotal', $spreadsheet->getActiveSheet(), '=SUM(SecondColumn)') + ); $totalsSheet->setCellValue('B20', '=SecondTotal'); $spreadsheet->addNamedFormula( @@ -643,4 +718,38 @@ private function buildDefinedNamesTestWorkbook(): Spreadsheet return $spreadsheet; } + + public function testInsertBothWithDefinedNamesAbsolute(): void + { + $spreadsheet = $this->buildDefinedNamesAbsoluteWorkbook(); + /** @var Worksheet $dataSheet */ + $dataSheet = $spreadsheet->getSheetByName('Data'); + /** @var Worksheet $totalsSheet */ + $totalsSheet = $spreadsheet->getSheetByName('Totals'); + + $dataSheet->setCellValue('C2', '=FirstTotal'); + $dataSheet->setCellValue('C3', '=FirstTotal'); + $dataSheet->setCellValue('C4', '=SecondTotal'); + + $dataSheet->insertNewColumnBefore('A', 2); + $dataSheet->insertNewRowBefore(2, 4); // 4 rows before row 2 + + /** @var NamedRange $firstColumn */ + $firstColumn = $spreadsheet->getNamedRange('FirstColumn'); + /** @var NamedRange $secondColumn */ + $secondColumn = $spreadsheet->getNamedRange('SecondColumn'); + + self::assertSame('=Data!$C$6:$C$10', $firstColumn->getRange()); + self::assertSame('=Data!$D$6:$D$10', $secondColumn->getRange()); + + self::assertSame(30, $totalsSheet->getCell('A20')->getCalculatedValue()); + self::assertSame(25, $totalsSheet->getCell('B20')->getCalculatedValue()); + self::assertSame(750, $totalsSheet->getCell('D20')->getCalculatedValue()); + + self::assertSame(30, $dataSheet->getCell('E6')->getCalculatedValue()); + self::assertSame(30, $dataSheet->getCell('E7')->getCalculatedValue()); + self::assertSame(25, $dataSheet->getCell('E8')->getCalculatedValue()); + + $spreadsheet->disconnectWorksheets(); + } }