diff --git a/src/PhpSpreadsheet/CellReferenceHelper.php b/src/PhpSpreadsheet/CellReferenceHelper.php index b2f18f6dda..f1241184a3 100644 --- a/src/PhpSpreadsheet/CellReferenceHelper.php +++ b/src/PhpSpreadsheet/CellReferenceHelper.php @@ -11,21 +11,30 @@ class CellReferenceHelper protected int $beforeColumn; + protected bool $beforeColumnAbsolute = false; + + protected string $beforeColumnString; + protected int $beforeRow; + protected bool $beforeRowAbsolute = false; + protected int $numberOfColumns; protected int $numberOfRows; public function __construct(string $beforeCellAddress = 'A1', int $numberOfColumns = 0, int $numberOfRows = 0) { + $this->beforeColumnAbsolute = $beforeCellAddress[0] === '$'; + $this->beforeRowAbsolute = strpos($beforeCellAddress, '$', 1) !== false; $this->beforeCellAddress = str_replace('$', '', $beforeCellAddress); $this->numberOfColumns = $numberOfColumns; $this->numberOfRows = $numberOfRows; // Get coordinate of $beforeCellAddress [$beforeColumn, $beforeRow] = Coordinate::coordinateFromString($beforeCellAddress); - $this->beforeColumn = Coordinate::columnIndexFromString($beforeColumn); + $this->beforeColumnString = $beforeColumn; + $this->beforeColumn = (int) Coordinate::columnIndexFromString($beforeColumn); $this->beforeRow = (int) $beforeRow; } @@ -41,7 +50,7 @@ public function refreshRequired(string $beforeCellAddress, int $numberOfColumns, || $this->numberOfRows !== $numberOfRows; } - public function updateCellReference(string $cellReference = 'A1', bool $includeAbsoluteReferences = false, bool $onlyAbsoluteReferences = false): string + public function updateCellReference(string $cellReference = 'A1', bool $includeAbsoluteReferences = false, bool $onlyAbsoluteReferences = false, ?bool $topLeft = null): string { if (Coordinate::coordinateIsRange($cellReference)) { throw new Exception('Only single cell references may be passed to this method.'); @@ -62,8 +71,45 @@ public function updateCellReference(string $cellReference = 'A1', bool $includeA $updateColumn = (($absoluteColumn !== '$') && $newColumnIndex >= $this->beforeColumn); $updateRow = (($absoluteRow !== '$') && $newRowIndex >= $this->beforeRow); } else { - $updateColumn = ($newColumnIndex >= $this->beforeColumn); - $updateRow = ($newRowIndex >= $this->beforeRow); + // A special case is removing the left/top or bottom/right edge of a range + // $topLeft is null if we aren't adjusting a range at all. + if ( + $topLeft !== null + && $this->numberOfColumns < 0 + && $newColumnIndex >= $this->beforeColumn + $this->numberOfColumns + && $newColumnIndex <= $this->beforeColumn - 1 + ) { + if ($topLeft) { + $newColumnIndex = $this->beforeColumn + $this->numberOfColumns; + } else { + $newColumnIndex = $this->beforeColumn + $this->numberOfColumns - 1; + } + } elseif ($newColumnIndex >= $this->beforeColumn) { + // Create new column reference + $newColumnIndex += $this->numberOfColumns; + } + $newColumn = $absoluteColumn . Coordinate::stringFromColumnIndex($newColumnIndex); + //$updateColumn = ($newColumnIndex >= $this->beforeColumn); + $updateColumn = false; + // A special case is removing the left/top or bottom/right edge of a range + // $topLeft is null if we aren't adjusting a range at all. + if ( + $topLeft !== null + && $this->numberOfRows < 0 + && $newRowIndex >= $this->beforeRow + $this->numberOfRows + && $newRowIndex <= $this->beforeRow - 1 + ) { + if ($topLeft) { + $newRowIndex = $this->beforeRow + $this->numberOfRows; + } else { + $newRowIndex = $this->beforeRow + $this->numberOfRows - 1; + } + } elseif ($newRowIndex >= $this->beforeRow) { + $newRowIndex = $newRowIndex + $this->numberOfRows; + } + $newRow = $absoluteRow . $newRowIndex; + //$updateRow = ($newRowIndex >= $this->beforeRow); + $updateRow = false; } // Create new column reference diff --git a/src/PhpSpreadsheet/ReferenceHelper.php b/src/PhpSpreadsheet/ReferenceHelper.php index 5e4877372b..4e66089832 100644 --- a/src/PhpSpreadsheet/ReferenceHelper.php +++ b/src/PhpSpreadsheet/ReferenceHelper.php @@ -194,8 +194,10 @@ protected function adjustHyperlinks(Worksheet $worksheet, int $numberOfColumns, if ($cellReferenceHelper->cellAddressInDeleteRange($cellAddress) === true) { $worksheet->setHyperlink($cellAddress, null); } elseif ($cellAddress !== $newReference) { - $worksheet->setHyperlink($newReference, $value); $worksheet->setHyperlink($cellAddress, null); + if ($newReference) { + $worksheet->setHyperlink($newReference, $value); + } } } } @@ -292,6 +294,9 @@ protected function adjustDataValidations(Worksheet $worksheet, int $numberOfColu if ($cellAddress !== $newReference) { $worksheet->setDataValidation($newReference, $dataValidation); $worksheet->setDataValidation($cellAddress, null); + if ($newReference) { + $worksheet->setDataValidation($newReference, $dataValidation); + } } } } @@ -307,7 +312,9 @@ protected function adjustMergeCells(Worksheet $worksheet): void $aNewMergeCells = []; // the new array of all merge cells foreach ($aMergeCells as $cellAddress => &$value) { $newReference = $this->updateCellReference($cellAddress); - $aNewMergeCells[$newReference] = $newReference; + if ($newReference) { + $aNewMergeCells[$newReference] = $newReference; + } } $worksheet->setMergeCells($aNewMergeCells); // replace the merge cells array } @@ -328,8 +335,10 @@ protected function adjustProtectedCells(Worksheet $worksheet, int $numberOfColum foreach ($aProtectedCells as $cellAddress => $protectedRange) { $newReference = $this->updateCellReference($cellAddress); if ($cellAddress !== $newReference) { - $worksheet->protectCells($newReference, $protectedRange->getPassword(), true); $worksheet->unprotectCells($cellAddress); + if ($newReference) { + $worksheet->protectCells($newReference, $protectedRange->getPassword(), true); + } } } } @@ -457,7 +466,8 @@ public function insertNewBefore( $cell = $worksheet->getCell($coordinate); $cellIndex = Coordinate::columnIndexFromString($cell->getColumn()); - if ($cellIndex - 1 + $numberOfColumns < 0) { + // Don't update cells that are being removed + if ($numberOfColumns < 0 && $cellIndex >= $beforeColumn + $numberOfColumns && $cellIndex < $beforeColumn) { continue; } @@ -633,8 +643,8 @@ public function updateFormulaReferences( if ($matchCount > 0) { foreach ($matches as $match) { $fromString = self::sheetnameBeforeCells($match[2], $worksheetName, "{$match[3]}:{$match[4]}"); - $modified3 = substr($this->updateCellReference('$A' . $match[3], $includeAbsoluteReferences, $onlyAbsoluteReferences), 2); - $modified4 = substr($this->updateCellReference('$A' . $match[4], $includeAbsoluteReferences, $onlyAbsoluteReferences), 2); + $modified3 = substr($this->updateCellReference('$A' . $match[3], $includeAbsoluteReferences, $onlyAbsoluteReferences, true), 2); + $modified4 = substr($this->updateCellReference('$A' . $match[4], $includeAbsoluteReferences, $onlyAbsoluteReferences, false), 2); if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) { if (self::matchSheetName($match[2], $worksheetName)) { @@ -657,8 +667,8 @@ public function updateFormulaReferences( if ($matchCount > 0) { foreach ($matches as $match) { $fromString = self::sheetnameBeforeCells($match[2], $worksheetName, "{$match[3]}:{$match[4]}"); - $modified3 = substr($this->updateCellReference($match[3] . '$1', $includeAbsoluteReferences, $onlyAbsoluteReferences), 0, -2); - $modified4 = substr($this->updateCellReference($match[4] . '$1', $includeAbsoluteReferences, $onlyAbsoluteReferences), 0, -2); + $modified3 = substr($this->updateCellReference($match[3] . '$1', $includeAbsoluteReferences, $onlyAbsoluteReferences, true), 0, -2); + $modified4 = substr($this->updateCellReference($match[4] . '$1', $includeAbsoluteReferences, $onlyAbsoluteReferences, false), 0, -2); if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) { if (self::matchSheetName($match[2], $worksheetName)) { @@ -681,8 +691,8 @@ public function updateFormulaReferences( if ($matchCount > 0) { foreach ($matches as $match) { $fromString = self::sheetnameBeforeCells($match[2], $worksheetName, "{$match[3]}:{$match[4]}"); - $modified3 = $this->updateCellReference($match[3], $includeAbsoluteReferences, $onlyAbsoluteReferences); - $modified4 = $this->updateCellReference($match[4], $includeAbsoluteReferences, $onlyAbsoluteReferences); + $modified3 = $this->updateCellReference($match[3], $includeAbsoluteReferences, $onlyAbsoluteReferences, true); + $modified4 = $this->updateCellReference($match[4], $includeAbsoluteReferences, $onlyAbsoluteReferences, false); if ($match[3] . $match[4] !== $modified3 . $modified4) { if (self::matchSheetName($match[2], $worksheetName)) { @@ -709,7 +719,7 @@ public function updateFormulaReferences( foreach ($matches as $match) { $fromString = self::sheetnameBeforeCells($match[2], $worksheetName, "{$match[3]}"); - $modified3 = $this->updateCellReference($match[3], $includeAbsoluteReferences, $onlyAbsoluteReferences); + $modified3 = $this->updateCellReference($match[3], $includeAbsoluteReferences, $onlyAbsoluteReferences, null); if ($match[3] !== $modified3) { if (self::matchSheetName($match[2], $worksheetName)) { $toString = self::sheetnameBeforeCells($match[2], $worksheetName, "$modified3"); @@ -890,7 +900,7 @@ private function updateRowRangesAllWorksheets(string $formula, int $numberOfRows * * @return string Updated cell range */ - private function updateCellReference(string $cellReference = 'A1', bool $includeAbsoluteReferences = false, bool $onlyAbsoluteReferences = false): string + private function updateCellReference(string $cellReference = 'A1', bool $includeAbsoluteReferences = false, bool $onlyAbsoluteReferences = false, ?bool $topLeft = null) { // Is it in another worksheet? Will not have to update anything. if (str_contains($cellReference, '!')) { @@ -902,7 +912,7 @@ private function updateCellReference(string $cellReference = 'A1', bool $include /** @var CellReferenceHelper */ $cellReferenceHelper = $this->cellReferenceHelper; - return $cellReferenceHelper->updateCellReference($cellReference, $includeAbsoluteReferences, $onlyAbsoluteReferences); + return $cellReferenceHelper->updateCellReference($cellReference, $includeAbsoluteReferences, $onlyAbsoluteReferences, $topLeft); } // Range @@ -1008,14 +1018,14 @@ private function updateCellRange(string $cellRange = 'A1:A1', bool $includeAbsol $cellReferenceHelper = $this->cellReferenceHelper; if (ctype_alpha($range[$i][$j])) { $range[$i][$j] = Coordinate::coordinateFromString( - $cellReferenceHelper->updateCellReference($range[$i][$j] . '1', $includeAbsoluteReferences, $onlyAbsoluteReferences) + $cellReferenceHelper->updateCellReference($range[$i][$j] . '1', $includeAbsoluteReferences, $onlyAbsoluteReferences, null) )[0]; } elseif (ctype_digit($range[$i][$j])) { $range[$i][$j] = Coordinate::coordinateFromString( - $cellReferenceHelper->updateCellReference('A' . $range[$i][$j], $includeAbsoluteReferences, $onlyAbsoluteReferences) + $cellReferenceHelper->updateCellReference('A' . $range[$i][$j], $includeAbsoluteReferences, $onlyAbsoluteReferences, null) )[1]; } else { - $range[$i][$j] = $cellReferenceHelper->updateCellReference($range[$i][$j], $includeAbsoluteReferences, $onlyAbsoluteReferences); + $range[$i][$j] = $cellReferenceHelper->updateCellReference($range[$i][$j], $includeAbsoluteReferences, $onlyAbsoluteReferences, null); } } } diff --git a/tests/PhpSpreadsheetTests/Issue1449Test.php b/tests/PhpSpreadsheetTests/Issue1449Test.php new file mode 100644 index 0000000000..d00b10f6ed --- /dev/null +++ b/tests/PhpSpreadsheetTests/Issue1449Test.php @@ -0,0 +1,72 @@ +getActiveSheet(); + $sheet2 = $spreadsheet->createSheet(); + $sheet1->setTitle('Sheet1'); + $sheet2->setTitle('Sheet2'); + $sheet1->fromArray( + [ + [3, 1, 2, 33, 1, 10, 20, 30, 40], + [4, 2, 3, 23, 2, 10, 20, 30, 40], + [5, 3, 4, 1, 3, 10, 20, 30, 40], + [6, 4, 6, 4, 3, 10, 20, 30, 40], + [7, 6, 6, 2, 2, 10, 20, 30, 40], + ], + null, + 'C1', + true + ); + $sheet1->getCell('A1')->setValue('=SUM(C4:F7)'); + $sheet2->getCell('A1')->setValue('=SUM(Sheet1!C3:G5)'); + $sheet1->removeColumn('F', 4); + self::assertSame('=SUM(C4:E7)', $sheet1->getCell('A1')->getValue()); + if (!$this->skipTests) { + // References on another sheet not working yet. + self::assertSame('=Sheet1!SUM(C3:E5)', $sheet2->getCell('A1')->getValue()); + } + $spreadsheet->disconnectWorksheets(); + } + + public function testDeleteRows(): void + { + $spreadsheet = new Spreadsheet(); + $sheet1 = $spreadsheet->getActiveSheet(); + $sheet2 = $spreadsheet->createSheet(); + $sheet1->setTitle('Sheet1'); + $sheet2->setTitle('Sheet2'); + $sheet1->fromArray( + [ + [3, 1, 2, 33, 1, 10, 20, 30, 40], + [4, 2, 3, 23, 2, 10, 20, 30, 40], + [5, 3, 4, 1, 3, 10, 20, 30, 40], + [6, 4, 6, 4, 3, 10, 20, 30, 40], + [7, 6, 6, 2, 2, 10, 20, 30, 40], + ], + null, + 'C1', + true + ); + $sheet1->getCell('A1')->setValue('=SUM(C4:F7)'); + $sheet2->getCell('A1')->setValue('=SUM(Sheet1!C3:G5)'); + $sheet1->removeRow(4, 2); + self::assertSame('=SUM(C4:F5)', $sheet1->getCell('A1')->getValue()); + if (!$this->skipTests) { + // References on another sheet not working yet. + self::assertSame('=Sheet1!SUM(C3:G3)', $sheet2->getCell('A1')->getValue()); + } + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Html/HtmlImage2Test.php b/tests/PhpSpreadsheetTests/Reader/Html/HtmlImage2Test.php index 3aaf6a0f02..68d888c70a 100644 --- a/tests/PhpSpreadsheetTests/Reader/Html/HtmlImage2Test.php +++ b/tests/PhpSpreadsheetTests/Reader/Html/HtmlImage2Test.php @@ -16,7 +16,7 @@ public function testCanInsertImageGoodProtocol(): void if (getenv('SKIP_URL_IMAGE_TEST') === '1') { self::markTestSkipped('Skipped due to setting of environment variable'); } - $imagePath = 'https://phpspreadsheet.readthedocs.io/en/latest/topics/images/01-03-filter-icon-1.png'; + $imagePath = 'https://phpspreadsheet.readthedocs.io/en/stable/topics/images/01-03-filter-icon-1.png'; $html = '
test image voilĂ