Merge pull request #2682 from PHPOffice/Refactoring-Work-on-ReferenceHelper

Some work on refactoring the ReferenceHelper to extract the logic for updating cell references. This is a preliminary step toward allowing updates to absolute cell references, required to update Conditional Formatting rules.

And a bugfix when deleting cells that contain hyperlinks (the hperlinks weren't being deleted, so were being "inherited" by whatever cell moved to that address).

Plus some additional unit tests for the ReferenceHelper.
This commit is contained in:
Mark Baker 2022-03-16 16:54:00 +01:00 committed by GitHub
commit 251605f9b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 363 additions and 202 deletions

View File

@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
### Fixed ### Fixed
- Fix bug when deleting cells with hyperlinks, where the hyperlink was then being "inherited" by whatever cell moved to that cell address.
- Fix bug in Conditional Formatting in the Xls Writer that resulted in a broken file when there were multiple conditional ranges in a worksheet. - Fix bug in Conditional Formatting in the Xls Writer that resulted in a broken file when there were multiple conditional ranges in a worksheet.
- Fix Conditional Formatting in the Xls Writer to work with rules that contain string literals, cell references and formulae. - Fix Conditional Formatting in the Xls Writer to work with rules that contain string literals, cell references and formulae.
- Fix for setting Active Sheet to the first loaded worksheet when bookViews element isn't defined [Issue #2666](https://github.com/PHPOffice/PhpSpreadsheet/issues/2666) [PR #2669](https://github.com/PHPOffice/PhpSpreadsheet/pull/2669) - Fix for setting Active Sheet to the first loaded worksheet when bookViews element isn't defined [Issue #2666](https://github.com/PHPOffice/PhpSpreadsheet/issues/2666) [PR #2669](https://github.com/PHPOffice/PhpSpreadsheet/pull/2669)

View File

@ -3165,31 +3165,11 @@ parameters:
count: 1 count: 1
path: src/PhpSpreadsheet/ReferenceHelper.php path: src/PhpSpreadsheet/ReferenceHelper.php
-
message: "#^Parameter \\#1 \\$index of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\:\\:setRowIndex\\(\\) expects int, string given\\.$#"
count: 1
path: src/PhpSpreadsheet/ReferenceHelper.php
-
message: "#^Parameter \\#2 \\$callback of function uksort expects callable\\(\\(int\\|string\\), \\(int\\|string\\)\\)\\: int, array\\{'self', 'cellReverseSort'\\} given\\.$#"
count: 4
path: src/PhpSpreadsheet/ReferenceHelper.php
-
message: "#^Parameter \\#2 \\$callback of function uksort expects callable\\(\\(int\\|string\\), \\(int\\|string\\)\\)\\: int, array\\{'self', 'cellSort'\\} given\\.$#"
count: 4
path: src/PhpSpreadsheet/ReferenceHelper.php
- -
message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|null given\\.$#" message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|null given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/ReferenceHelper.php path: src/PhpSpreadsheet/ReferenceHelper.php
-
message: "#^Static property PhpOffice\\\\PhpSpreadsheet\\\\ReferenceHelper\\:\\:\\$instance \\(PhpOffice\\\\PhpSpreadsheet\\\\ReferenceHelper\\) in isset\\(\\) is not nullable\\.$#"
count: 1
path: src/PhpSpreadsheet/ReferenceHelper.php
- -
message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\Run\\:\\:\\$font \\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\Run\\:\\:\\$font \\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#"
count: 1 count: 1

View File

@ -0,0 +1,103 @@
<?php
namespace PhpOffice\PhpSpreadsheet;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
class CellReferenceHelper
{
/**
* @var string
*/
protected $beforeCellAddress;
/**
* @var int
*/
protected $beforeColumn;
/**
* @var int
*/
protected $beforeRow;
/**
* @var int
*/
protected $numberOfColumns;
/**
* @var int
*/
protected $numberOfRows;
public function __construct(string $beforeCellAddress = 'A1', int $numberOfColumns = 0, int $numberOfRows = 0)
{
$this->beforeCellAddress = str_replace('$', '', $beforeCellAddress);
$this->numberOfColumns = $numberOfColumns;
$this->numberOfRows = $numberOfRows;
// Get coordinate of $beforeCellAddress
[$beforeColumn, $beforeRow] = Coordinate::coordinateFromString($beforeCellAddress);
$this->beforeColumn = (int) Coordinate::columnIndexFromString($beforeColumn);
$this->beforeRow = (int) $beforeRow;
}
public function refreshRequired(string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): bool
{
return $this->beforeCellAddress !== $beforeCellAddress ||
$this->numberOfColumns !== $numberOfColumns ||
$this->numberOfRows !== $numberOfRows;
}
public function updateCellReference(string $cellReference = 'A1'): string
{
if (Coordinate::coordinateIsRange($cellReference)) {
throw new Exception('Only single cell references may be passed to this method.');
}
// Get coordinate of $cellReference
[$newColumn, $newRow] = Coordinate::coordinateFromString($cellReference);
$newColumnIndex = (int) Coordinate::columnIndexFromString(str_replace('$', '', $newColumn));
$newRowIndex = (int) str_replace('$', '', $newRow);
// Verify which parts should be updated
$updateColumn = (($newColumn[0] !== '$') && $newColumnIndex >= $this->beforeColumn);
$updateRow = (($newRow[0] !== '$') && $newRow >= $this->beforeRow);
// Create new column reference
if ($updateColumn) {
$newColumn = Coordinate::stringFromColumnIndex($newColumnIndex + $this->numberOfColumns);
}
// Create new row reference
if ($updateRow) {
$newRow = $newRowIndex + $this->numberOfRows;
}
// Return new reference
return "{$newColumn}{$newRow}";
}
public function cellAddressInDeleteRange(string $cellAddress): bool
{
[$cellColumn, $cellRow] = Coordinate::coordinateFromString($cellAddress);
$cellColumnIndex = Coordinate::columnIndexFromString($cellColumn);
// Is cell within the range of rows/columns if we're deleting
if (
$this->numberOfRows < 0 &&
($cellRow >= ($this->beforeRow + $this->numberOfRows)) &&
($cellRow < $this->beforeRow)
) {
return true;
} elseif (
$this->numberOfColumns < 0 &&
($cellColumnIndex >= ($this->beforeColumn + $this->numberOfColumns)) &&
($cellColumnIndex < $this->beforeColumn)
) {
return true;
}
return false;
}
}

View File

@ -20,10 +20,15 @@ class ReferenceHelper
/** /**
* Instance of this class. * Instance of this class.
* *
* @var ReferenceHelper * @var ?ReferenceHelper
*/ */
private static $instance; private static $instance;
/**
* @var CellReferenceHelper
*/
private $cellReferenceHelper;
/** /**
* Get an instance of this class. * Get an instance of this class.
* *
@ -31,7 +36,7 @@ class ReferenceHelper
*/ */
public static function getInstance() public static function getInstance()
{ {
if (!isset(self::$instance) || (self::$instance === null)) { if (self::$instance === null) {
self::$instance = new self(); self::$instance = new self();
} }
@ -115,67 +120,32 @@ class ReferenceHelper
return ($ar < $br) ? 1 : -1; return ($ar < $br) ? 1 : -1;
} }
/**
* Test whether a cell address falls within a defined range of cells.
*
* @param string $cellAddress Address of the cell we're testing
* @param int $beforeRow Number of the row we're inserting/deleting before
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
* @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
* @param int $numberOfCols Number of columns to insert/delete (negative values indicate deletion)
*
* @return bool
*/
private static function cellAddressInDeleteRange($cellAddress, $beforeRow, $numberOfRows, $beforeColumnIndex, $numberOfCols)
{
[$cellColumn, $cellRow] = Coordinate::coordinateFromString($cellAddress);
$cellColumnIndex = Coordinate::columnIndexFromString($cellColumn);
// Is cell within the range of rows/columns if we're deleting
if (
$numberOfRows < 0 &&
($cellRow >= ($beforeRow + $numberOfRows)) &&
($cellRow < $beforeRow)
) {
return true;
} elseif (
$numberOfCols < 0 &&
($cellColumnIndex >= ($beforeColumnIndex + $numberOfCols)) &&
($cellColumnIndex < $beforeColumnIndex)
) {
return true;
}
return false;
}
/** /**
* Update page breaks when inserting/deleting rows/columns. * Update page breaks when inserting/deleting rows/columns.
* *
* @param Worksheet $worksheet The worksheet that we're editing * @param Worksheet $worksheet The worksheet that we're editing
* @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1')
* @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
* @param int $beforeRow Number of the row we're inserting/deleting before
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
*/ */
protected function adjustPageBreaks(Worksheet $worksheet, $beforeCellAddress, $beforeColumnIndex, $numberOfColumns, $beforeRow, $numberOfRows): void protected function adjustPageBreaks(Worksheet $worksheet, $numberOfColumns, $numberOfRows): void
{ {
$aBreaks = $worksheet->getBreaks(); $aBreaks = $worksheet->getBreaks();
($numberOfColumns > 0 || $numberOfRows > 0) ? ($numberOfColumns > 0 || $numberOfRows > 0)
uksort($aBreaks, ['self', 'cellReverseSort']) : uksort($aBreaks, ['self', 'cellSort']); ? uksort($aBreaks, [self::class, 'cellReverseSort'])
: uksort($aBreaks, [self::class, 'cellSort']);
foreach ($aBreaks as $key => $value) { foreach ($aBreaks as $cellAddress => $value) {
if (self::cellAddressInDeleteRange($key, $beforeRow, $numberOfRows, $beforeColumnIndex, $numberOfColumns)) { if ($this->cellReferenceHelper->cellAddressInDeleteRange($cellAddress) === true) {
// If we're deleting, then clear any defined breaks that are within the range // If we're deleting, then clear any defined breaks that are within the range
// of rows/columns that we're deleting // of rows/columns that we're deleting
$worksheet->setBreak($key, Worksheet::BREAK_NONE); $worksheet->setBreak($cellAddress, Worksheet::BREAK_NONE);
} else { } else {
// Otherwise update any affected breaks by inserting a new break at the appropriate point // Otherwise update any affected breaks by inserting a new break at the appropriate point
// and removing the old affected break // and removing the old affected break
$newReference = $this->updateCellReference($key, $beforeCellAddress, $numberOfColumns, $numberOfRows); $newReference = $this->updateCellReference($cellAddress);
if ($key != $newReference) { if ($cellAddress !== $newReference) {
$worksheet->setBreak($newReference, $value) $worksheet->setBreak($newReference, $value)
->setBreak($key, Worksheet::BREAK_NONE); ->setBreak($cellAddress, Worksheet::BREAK_NONE);
} }
} }
} }
@ -185,22 +155,17 @@ class ReferenceHelper
* Update cell comments when inserting/deleting rows/columns. * Update cell comments when inserting/deleting rows/columns.
* *
* @param Worksheet $worksheet The worksheet that we're editing * @param Worksheet $worksheet The worksheet that we're editing
* @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1')
* @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
* @param int $beforeRow Number of the row we're inserting/deleting before
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
*/ */
protected function adjustComments($worksheet, $beforeCellAddress, $beforeColumnIndex, $numberOfColumns, $beforeRow, $numberOfRows): void protected function adjustComments($worksheet): void
{ {
$aComments = $worksheet->getComments(); $aComments = $worksheet->getComments();
$aNewComments = []; // the new array of all comments $aNewComments = []; // the new array of all comments
foreach ($aComments as $key => &$value) { foreach ($aComments as $cellAddress => &$value) {
// Any comments inside a deleted range will be ignored // Any comments inside a deleted range will be ignored
if (!self::cellAddressInDeleteRange($key, $beforeRow, $numberOfRows, $beforeColumnIndex, $numberOfColumns)) { if ($this->cellReferenceHelper->cellAddressInDeleteRange($cellAddress) === false) {
// Otherwise build a new array of comments indexed by the adjusted cell reference // Otherwise build a new array of comments indexed by the adjusted cell reference
$newReference = $this->updateCellReference($key, $beforeCellAddress, $numberOfColumns, $numberOfRows); $newReference = $this->updateCellReference($cellAddress);
$aNewComments[$newReference] = $value; $aNewComments[$newReference] = $value;
} }
} }
@ -212,21 +177,23 @@ class ReferenceHelper
* Update hyperlinks when inserting/deleting rows/columns. * Update hyperlinks when inserting/deleting rows/columns.
* *
* @param Worksheet $worksheet The worksheet that we're editing * @param Worksheet $worksheet The worksheet that we're editing
* @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1')
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
*/ */
protected function adjustHyperlinks($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows): void protected function adjustHyperlinks($worksheet, $numberOfColumns, $numberOfRows): void
{ {
$aHyperlinkCollection = $worksheet->getHyperlinkCollection(); $aHyperlinkCollection = $worksheet->getHyperlinkCollection();
($numberOfColumns > 0 || $numberOfRows > 0) ? ($numberOfColumns > 0 || $numberOfRows > 0)
uksort($aHyperlinkCollection, ['self', 'cellReverseSort']) : uksort($aHyperlinkCollection, ['self', 'cellSort']); ? uksort($aHyperlinkCollection, [self::class, 'cellReverseSort'])
: uksort($aHyperlinkCollection, [self::class, 'cellSort']);
foreach ($aHyperlinkCollection as $key => $value) { foreach ($aHyperlinkCollection as $cellAddress => $value) {
$newReference = $this->updateCellReference($key, $beforeCellAddress, $numberOfColumns, $numberOfRows); $newReference = $this->updateCellReference($cellAddress);
if ($key != $newReference) { if ($this->cellReferenceHelper->cellAddressInDeleteRange($cellAddress) === true) {
$worksheet->setHyperlink($cellAddress, null);
} elseif ($cellAddress !== $newReference) {
$worksheet->setHyperlink($newReference, $value); $worksheet->setHyperlink($newReference, $value);
$worksheet->setHyperlink($key, null); $worksheet->setHyperlink($cellAddress, null);
} }
} }
} }
@ -235,21 +202,21 @@ class ReferenceHelper
* Update data validations when inserting/deleting rows/columns. * Update data validations when inserting/deleting rows/columns.
* *
* @param Worksheet $worksheet The worksheet that we're editing * @param Worksheet $worksheet The worksheet that we're editing
* @param string $before Insert/Delete before this cell address (e.g. 'A1')
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
*/ */
protected function adjustDataValidations(Worksheet $worksheet, $before, $numberOfColumns, $numberOfRows): void protected function adjustDataValidations(Worksheet $worksheet, $numberOfColumns, $numberOfRows): void
{ {
$aDataValidationCollection = $worksheet->getDataValidationCollection(); $aDataValidationCollection = $worksheet->getDataValidationCollection();
($numberOfColumns > 0 || $numberOfRows > 0) ? ($numberOfColumns > 0 || $numberOfRows > 0)
uksort($aDataValidationCollection, ['self', 'cellReverseSort']) : uksort($aDataValidationCollection, ['self', 'cellSort']); ? uksort($aDataValidationCollection, [self::class, 'cellReverseSort'])
: uksort($aDataValidationCollection, [self::class, 'cellSort']);
foreach ($aDataValidationCollection as $key => $value) { foreach ($aDataValidationCollection as $cellAddress => $value) {
$newReference = $this->updateCellReference($key, $before, $numberOfColumns, $numberOfRows); $newReference = $this->updateCellReference($cellAddress);
if ($key != $newReference) { if ($cellAddress !== $newReference) {
$worksheet->setDataValidation($newReference, $value); $worksheet->setDataValidation($newReference, $value);
$worksheet->setDataValidation($key, null); $worksheet->setDataValidation($cellAddress, null);
} }
} }
} }
@ -258,16 +225,13 @@ class ReferenceHelper
* Update merged cells when inserting/deleting rows/columns. * Update merged cells when inserting/deleting rows/columns.
* *
* @param Worksheet $worksheet The worksheet that we're editing * @param Worksheet $worksheet The worksheet that we're editing
* @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1')
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
*/ */
protected function adjustMergeCells(Worksheet $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows): void protected function adjustMergeCells(Worksheet $worksheet): void
{ {
$aMergeCells = $worksheet->getMergeCells(); $aMergeCells = $worksheet->getMergeCells();
$aNewMergeCells = []; // the new array of all merge cells $aNewMergeCells = []; // the new array of all merge cells
foreach ($aMergeCells as $key => &$value) { foreach ($aMergeCells as $cellAddress => &$value) {
$newReference = $this->updateCellReference($key, $beforeCellAddress, $numberOfColumns, $numberOfRows); $newReference = $this->updateCellReference($cellAddress);
$aNewMergeCells[$newReference] = $newReference; $aNewMergeCells[$newReference] = $newReference;
} }
$worksheet->setMergeCells($aNewMergeCells); // replace the merge cells array $worksheet->setMergeCells($aNewMergeCells); // replace the merge cells array
@ -277,20 +241,20 @@ class ReferenceHelper
* Update protected cells when inserting/deleting rows/columns. * Update protected cells when inserting/deleting rows/columns.
* *
* @param Worksheet $worksheet The worksheet that we're editing * @param Worksheet $worksheet The worksheet that we're editing
* @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1')
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
*/ */
protected function adjustProtectedCells(Worksheet $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows): void protected function adjustProtectedCells(Worksheet $worksheet, $numberOfColumns, $numberOfRows): void
{ {
$aProtectedCells = $worksheet->getProtectedCells(); $aProtectedCells = $worksheet->getProtectedCells();
($numberOfColumns > 0 || $numberOfRows > 0) ? ($numberOfColumns > 0 || $numberOfRows > 0)
uksort($aProtectedCells, ['self', 'cellReverseSort']) : uksort($aProtectedCells, ['self', 'cellSort']); ? uksort($aProtectedCells, [self::class, 'cellReverseSort'])
foreach ($aProtectedCells as $key => $value) { : uksort($aProtectedCells, [self::class, 'cellSort']);
$newReference = $this->updateCellReference($key, $beforeCellAddress, $numberOfColumns, $numberOfRows); foreach ($aProtectedCells as $cellAddress => $value) {
if ($key != $newReference) { $newReference = $this->updateCellReference($cellAddress);
if ($cellAddress !== $newReference) {
$worksheet->protectCells($newReference, $value, true); $worksheet->protectCells($newReference, $value, true);
$worksheet->unprotectCells($key); $worksheet->unprotectCells($cellAddress);
} }
} }
} }
@ -299,18 +263,15 @@ class ReferenceHelper
* Update column dimensions when inserting/deleting rows/columns. * Update column dimensions when inserting/deleting rows/columns.
* *
* @param Worksheet $worksheet The worksheet that we're editing * @param Worksheet $worksheet The worksheet that we're editing
* @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1')
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
*/ */
protected function adjustColumnDimensions(Worksheet $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows): void protected function adjustColumnDimensions(Worksheet $worksheet): void
{ {
$aColumnDimensions = array_reverse($worksheet->getColumnDimensions(), true); $aColumnDimensions = array_reverse($worksheet->getColumnDimensions(), true);
if (!empty($aColumnDimensions)) { if (!empty($aColumnDimensions)) {
foreach ($aColumnDimensions as $objColumnDimension) { foreach ($aColumnDimensions as $objColumnDimension) {
$newReference = $this->updateCellReference($objColumnDimension->getColumnIndex() . '1', $beforeCellAddress, $numberOfColumns, $numberOfRows); $newReference = $this->updateCellReference($objColumnDimension->getColumnIndex() . '1');
[$newReference] = Coordinate::coordinateFromString($newReference); [$newReference] = Coordinate::coordinateFromString($newReference);
if ($objColumnDimension->getColumnIndex() != $newReference) { if ($objColumnDimension->getColumnIndex() !== $newReference) {
$objColumnDimension->setColumnIndex($newReference); $objColumnDimension->setColumnIndex($newReference);
} }
} }
@ -322,20 +283,19 @@ class ReferenceHelper
* Update row dimensions when inserting/deleting rows/columns. * Update row dimensions when inserting/deleting rows/columns.
* *
* @param Worksheet $worksheet The worksheet that we're editing * @param Worksheet $worksheet The worksheet that we're editing
* @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1')
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
* @param int $beforeRow Number of the row we're inserting/deleting before * @param int $beforeRow Number of the row we're inserting/deleting before
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
*/ */
protected function adjustRowDimensions(Worksheet $worksheet, $beforeCellAddress, $numberOfColumns, $beforeRow, $numberOfRows): void protected function adjustRowDimensions(Worksheet $worksheet, $beforeRow, $numberOfRows): void
{ {
$aRowDimensions = array_reverse($worksheet->getRowDimensions(), true); $aRowDimensions = array_reverse($worksheet->getRowDimensions(), true);
if (!empty($aRowDimensions)) { if (!empty($aRowDimensions)) {
foreach ($aRowDimensions as $objRowDimension) { foreach ($aRowDimensions as $objRowDimension) {
$newReference = $this->updateCellReference('A' . $objRowDimension->getRowIndex(), $beforeCellAddress, $numberOfColumns, $numberOfRows); $newReference = $this->updateCellReference('A' . $objRowDimension->getRowIndex());
[, $newReference] = Coordinate::coordinateFromString($newReference); [, $newReference] = Coordinate::coordinateFromString($newReference);
if ($objRowDimension->getRowIndex() != $newReference) { $newRoweference = (int) $newReference;
$objRowDimension->setRowIndex($newReference); if ($objRowDimension->getRowIndex() !== $newRoweference) {
$objRowDimension->setRowIndex($newRoweference);
} }
} }
$worksheet->refreshRowDimensions(); $worksheet->refreshRowDimensions();
@ -368,6 +328,13 @@ class ReferenceHelper
$remove = ($numberOfColumns < 0 || $numberOfRows < 0); $remove = ($numberOfColumns < 0 || $numberOfRows < 0);
$allCoordinates = $worksheet->getCoordinates(); $allCoordinates = $worksheet->getCoordinates();
if (
$this->cellReferenceHelper === null ||
$this->cellReferenceHelper->refreshRequired($beforeCellAddress, $numberOfColumns, $numberOfRows)
) {
$this->cellReferenceHelper = new CellReferenceHelper($beforeCellAddress, $numberOfColumns, $numberOfRows);
}
// Get coordinate of $beforeCellAddress // Get coordinate of $beforeCellAddress
[$beforeColumn, $beforeRow] = Coordinate::indexesFromString($beforeCellAddress); [$beforeColumn, $beforeRow] = Coordinate::indexesFromString($beforeCellAddress);
@ -427,7 +394,7 @@ class ReferenceHelper
$worksheet->getCell($newCoordinate)->setXfIndex($cell->getXfIndex()); $worksheet->getCell($newCoordinate)->setXfIndex($cell->getXfIndex());
// Insert this cell at its new location // Insert this cell at its new location
if ($cell->getDataType() == DataType::TYPE_FORMULA) { if ($cell->getDataType() === DataType::TYPE_FORMULA) {
// Formula should be adjusted // Formula should be adjusted
$worksheet->getCell($newCoordinate) $worksheet->getCell($newCoordinate)
->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle())); ->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle()));
@ -441,7 +408,7 @@ class ReferenceHelper
} else { } else {
/* We don't need to update styles for rows/columns before our insertion position, /* We don't need to update styles for rows/columns before our insertion position,
but we do still need to adjust any formulae in those cells */ but we do still need to adjust any formulae in those cells */
if ($cell->getDataType() == DataType::TYPE_FORMULA) { if ($cell->getDataType() === DataType::TYPE_FORMULA) {
// Formula should be adjusted // Formula should be adjusted
$cell->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle())); $cell->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle()));
} }
@ -461,39 +428,39 @@ class ReferenceHelper
} }
// Update worksheet: column dimensions // Update worksheet: column dimensions
$this->adjustColumnDimensions($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); $this->adjustColumnDimensions($worksheet);
// Update worksheet: row dimensions // Update worksheet: row dimensions
$this->adjustRowDimensions($worksheet, $beforeCellAddress, $numberOfColumns, $beforeRow, $numberOfRows); $this->adjustRowDimensions($worksheet, $beforeRow, $numberOfRows);
// Update worksheet: page breaks // Update worksheet: page breaks
$this->adjustPageBreaks($worksheet, $beforeCellAddress, $beforeColumn, $numberOfColumns, $beforeRow, $numberOfRows); $this->adjustPageBreaks($worksheet, $numberOfColumns, $numberOfRows);
// Update worksheet: comments // Update worksheet: comments
$this->adjustComments($worksheet, $beforeCellAddress, $beforeColumn, $numberOfColumns, $beforeRow, $numberOfRows); $this->adjustComments($worksheet);
// Update worksheet: hyperlinks // Update worksheet: hyperlinks
$this->adjustHyperlinks($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); $this->adjustHyperlinks($worksheet, $numberOfColumns, $numberOfRows);
// Update worksheet: data validations // Update worksheet: data validations
$this->adjustDataValidations($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); $this->adjustDataValidations($worksheet, $numberOfColumns, $numberOfRows);
// Update worksheet: merge cells // Update worksheet: merge cells
$this->adjustMergeCells($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); $this->adjustMergeCells($worksheet);
// Update worksheet: protected cells // Update worksheet: protected cells
$this->adjustProtectedCells($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); $this->adjustProtectedCells($worksheet, $numberOfColumns, $numberOfRows);
// Update worksheet: autofilter // Update worksheet: autofilter
$this->adjustAutoFilter($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); $this->adjustAutoFilter($worksheet, $beforeCellAddress, $numberOfColumns);
// Update worksheet: freeze pane // Update worksheet: freeze pane
if ($worksheet->getFreezePane()) { if ($worksheet->getFreezePane()) {
$splitCell = $worksheet->getFreezePane() ?? ''; $splitCell = $worksheet->getFreezePane() ?? '';
$topLeftCell = $worksheet->getTopLeftCell() ?? ''; $topLeftCell = $worksheet->getTopLeftCell() ?? '';
$splitCell = $this->updateCellReference($splitCell, $beforeCellAddress, $numberOfColumns, $numberOfRows); $splitCell = $this->updateCellReference($splitCell);
$topLeftCell = $this->updateCellReference($topLeftCell, $beforeCellAddress, $numberOfColumns, $numberOfRows); $topLeftCell = $this->updateCellReference($topLeftCell);
$worksheet->freezePane($splitCell, $topLeftCell); $worksheet->freezePane($splitCell, $topLeftCell);
} }
@ -501,14 +468,14 @@ class ReferenceHelper
// Page setup // Page setup
if ($worksheet->getPageSetup()->isPrintAreaSet()) { if ($worksheet->getPageSetup()->isPrintAreaSet()) {
$worksheet->getPageSetup()->setPrintArea( $worksheet->getPageSetup()->setPrintArea(
$this->updateCellReference($worksheet->getPageSetup()->getPrintArea(), $beforeCellAddress, $numberOfColumns, $numberOfRows) $this->updateCellReference($worksheet->getPageSetup()->getPrintArea())
); );
} }
// Update worksheet: drawings // Update worksheet: drawings
$aDrawings = $worksheet->getDrawingCollection(); $aDrawings = $worksheet->getDrawingCollection();
foreach ($aDrawings as $objDrawing) { foreach ($aDrawings as $objDrawing) {
$newReference = $this->updateCellReference($objDrawing->getCoordinates(), $beforeCellAddress, $numberOfColumns, $numberOfRows); $newReference = $this->updateCellReference($objDrawing->getCoordinates());
if ($objDrawing->getCoordinates() != $newReference) { if ($objDrawing->getCoordinates() != $newReference) {
$objDrawing->setCoordinates($newReference); $objDrawing->setCoordinates($newReference);
} }
@ -518,7 +485,7 @@ class ReferenceHelper
if (count($worksheet->getParent()->getDefinedNames()) > 0) { if (count($worksheet->getParent()->getDefinedNames()) > 0) {
foreach ($worksheet->getParent()->getDefinedNames() as $definedName) { foreach ($worksheet->getParent()->getDefinedNames() as $definedName) {
if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) { if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) {
$definedName->setValue($this->updateCellReference($definedName->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows)); $definedName->setValue($this->updateCellReference($definedName->getValue()));
} }
} }
} }
@ -540,6 +507,13 @@ class ReferenceHelper
*/ */
public function updateFormulaReferences($formula = '', $beforeCellAddress = 'A1', $numberOfColumns = 0, $numberOfRows = 0, $worksheetName = '') public function updateFormulaReferences($formula = '', $beforeCellAddress = 'A1', $numberOfColumns = 0, $numberOfRows = 0, $worksheetName = '')
{ {
if (
$this->cellReferenceHelper === null ||
$this->cellReferenceHelper->refreshRequired($beforeCellAddress, $numberOfColumns, $numberOfRows)
) {
$this->cellReferenceHelper = new CellReferenceHelper($beforeCellAddress, $numberOfColumns, $numberOfRows);
}
// Update cell references in the formula // Update cell references in the formula
$formulaBlocks = explode('"', $formula); $formulaBlocks = explode('"', $formula);
$i = false; $i = false;
@ -549,13 +523,13 @@ class ReferenceHelper
$adjustCount = 0; $adjustCount = 0;
$newCellTokens = $cellTokens = []; $newCellTokens = $cellTokens = [];
// Search for row ranges (e.g. 'Sheet1'!3:5 or 3:5) with or without $ absolutes (e.g. $3:5) // Search for row ranges (e.g. 'Sheet1'!3:5 or 3:5) with or without $ absolutes (e.g. $3:5)
$matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_ROWRANGE . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER); $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_ROWRANGE . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
if ($matchCount > 0) { if ($matchCount > 0) {
foreach ($matches as $match) { foreach ($matches as $match) {
$fromString = ($match[2] > '') ? $match[2] . '!' : ''; $fromString = ($match[2] > '') ? $match[2] . '!' : '';
$fromString .= $match[3] . ':' . $match[4]; $fromString .= $match[3] . ':' . $match[4];
$modified3 = substr($this->updateCellReference('$A' . $match[3], $beforeCellAddress, $numberOfColumns, $numberOfRows), 2); $modified3 = substr($this->updateCellReference('$A' . $match[3]), 2);
$modified4 = substr($this->updateCellReference('$A' . $match[4], $beforeCellAddress, $numberOfColumns, $numberOfRows), 2); $modified4 = substr($this->updateCellReference('$A' . $match[4]), 2);
if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) { if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) {
if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) {
@ -574,13 +548,13 @@ class ReferenceHelper
} }
} }
// Search for column ranges (e.g. 'Sheet1'!C:E or C:E) with or without $ absolutes (e.g. $C:E) // Search for column ranges (e.g. 'Sheet1'!C:E or C:E) with or without $ absolutes (e.g. $C:E)
$matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_COLRANGE . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER); $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_COLRANGE . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
if ($matchCount > 0) { if ($matchCount > 0) {
foreach ($matches as $match) { foreach ($matches as $match) {
$fromString = ($match[2] > '') ? $match[2] . '!' : ''; $fromString = ($match[2] > '') ? $match[2] . '!' : '';
$fromString .= $match[3] . ':' . $match[4]; $fromString .= $match[3] . ':' . $match[4];
$modified3 = substr($this->updateCellReference($match[3] . '$1', $beforeCellAddress, $numberOfColumns, $numberOfRows), 0, -2); $modified3 = substr($this->updateCellReference($match[3] . '$1'), 0, -2);
$modified4 = substr($this->updateCellReference($match[4] . '$1', $beforeCellAddress, $numberOfColumns, $numberOfRows), 0, -2); $modified4 = substr($this->updateCellReference($match[4] . '$1'), 0, -2);
if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) { if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) {
if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) {
@ -599,13 +573,13 @@ class ReferenceHelper
} }
} }
// Search for cell ranges (e.g. 'Sheet1'!A3:C5 or A3:C5) with or without $ absolutes (e.g. $A1:C$5) // Search for cell ranges (e.g. 'Sheet1'!A3:C5 or A3:C5) with or without $ absolutes (e.g. $A1:C$5)
$matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLRANGE . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER); $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLRANGE . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
if ($matchCount > 0) { if ($matchCount > 0) {
foreach ($matches as $match) { foreach ($matches as $match) {
$fromString = ($match[2] > '') ? $match[2] . '!' : ''; $fromString = ($match[2] > '') ? $match[2] . '!' : '';
$fromString .= $match[3] . ':' . $match[4]; $fromString .= $match[3] . ':' . $match[4];
$modified3 = $this->updateCellReference($match[3], $beforeCellAddress, $numberOfColumns, $numberOfRows); $modified3 = $this->updateCellReference($match[3]);
$modified4 = $this->updateCellReference($match[4], $beforeCellAddress, $numberOfColumns, $numberOfRows); $modified4 = $this->updateCellReference($match[4]);
if ($match[3] . $match[4] !== $modified3 . $modified4) { if ($match[3] . $match[4] !== $modified3 . $modified4) {
if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) {
@ -625,14 +599,14 @@ class ReferenceHelper
} }
} }
// Search for cell references (e.g. 'Sheet1'!A3 or C5) with or without $ absolutes (e.g. $A1 or C$5) // Search for cell references (e.g. 'Sheet1'!A3 or C5) with or without $ absolutes (e.g. $A1 or C$5)
$matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLREF . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER); $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLREF . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
if ($matchCount > 0) { if ($matchCount > 0) {
foreach ($matches as $match) { foreach ($matches as $match) {
$fromString = ($match[2] > '') ? $match[2] . '!' : ''; $fromString = ($match[2] > '') ? $match[2] . '!' : '';
$fromString .= $match[3]; $fromString .= $match[3];
$modified3 = $this->updateCellReference($match[3], $beforeCellAddress, $numberOfColumns, $numberOfRows); $modified3 = $this->updateCellReference($match[3]);
if ($match[3] !== $modified3) { if ($match[3] !== $modified3) {
if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) {
$toString = ($match[2] > '') ? $match[2] . '!' : ''; $toString = ($match[2] > '') ? $match[2] . '!' : '';
@ -809,13 +783,10 @@ class ReferenceHelper
* Update cell reference. * Update cell reference.
* *
* @param string $cellReference Cell address or range of addresses * @param string $cellReference Cell address or range of addresses
* @param string $beforeCellAddress Insert before this one
* @param int $numberOfColumns Number of columns to increment
* @param int $numberOfRows Number of rows to increment
* *
* @return string Updated cell range * @return string Updated cell range
*/ */
public function updateCellReference($cellReference = 'A1', $beforeCellAddress = 'A1', $numberOfColumns = 0, $numberOfRows = 0) private function updateCellReference($cellReference = 'A1')
{ {
// Is it in another worksheet? Will not have to update anything. // Is it in another worksheet? Will not have to update anything.
if (strpos($cellReference, '!') !== false) { if (strpos($cellReference, '!') !== false) {
@ -823,10 +794,10 @@ class ReferenceHelper
// Is it a range or a single cell? // Is it a range or a single cell?
} elseif (!Coordinate::coordinateIsRange($cellReference)) { } elseif (!Coordinate::coordinateIsRange($cellReference)) {
// Single cell // Single cell
return $this->updateSingleCellReference($cellReference, $beforeCellAddress, $numberOfColumns, $numberOfRows); return $this->cellReferenceHelper->updateCellReference($cellReference);
} elseif (Coordinate::coordinateIsRange($cellReference)) { } elseif (Coordinate::coordinateIsRange($cellReference)) {
// Range // Range
return $this->updateCellRange($cellReference, $beforeCellAddress, $numberOfColumns, $numberOfRows); return $this->updateCellRange($cellReference);
} }
// Return original // Return original
@ -865,13 +836,10 @@ class ReferenceHelper
* Update cell range. * Update cell range.
* *
* @param string $cellRange Cell range (e.g. 'B2:D4', 'B:C' or '2:3') * @param string $cellRange Cell range (e.g. 'B2:D4', 'B:C' or '2:3')
* @param string $beforeCellAddress Insert before this one
* @param int $numberOfColumns Number of columns to increment
* @param int $numberOfRows Number of rows to increment
* *
* @return string Updated cell range * @return string Updated cell range
*/ */
private function updateCellRange($cellRange = 'A1:A1', $beforeCellAddress = 'A1', $numberOfColumns = 0, $numberOfRows = 0) private function updateCellRange(string $cellRange = 'A1:A1'): string
{ {
if (!Coordinate::coordinateIsRange($cellRange)) { if (!Coordinate::coordinateIsRange($cellRange)) {
throw new Exception('Only cell ranges may be passed to this method.'); throw new Exception('Only cell ranges may be passed to this method.');
@ -884,13 +852,15 @@ class ReferenceHelper
$jc = count($range[$i]); $jc = count($range[$i]);
for ($j = 0; $j < $jc; ++$j) { for ($j = 0; $j < $jc; ++$j) {
if (ctype_alpha($range[$i][$j])) { if (ctype_alpha($range[$i][$j])) {
$r = Coordinate::coordinateFromString($this->updateSingleCellReference($range[$i][$j] . '1', $beforeCellAddress, $numberOfColumns, $numberOfRows)); $range[$i][$j] = Coordinate::coordinateFromString(
$range[$i][$j] = $r[0]; $this->cellReferenceHelper->updateCellReference($range[$i][$j] . '1')
)[0];
} elseif (ctype_digit($range[$i][$j])) { } elseif (ctype_digit($range[$i][$j])) {
$r = Coordinate::coordinateFromString($this->updateSingleCellReference('A' . $range[$i][$j], $beforeCellAddress, $numberOfColumns, $numberOfRows)); $range[$i][$j] = Coordinate::coordinateFromString(
$range[$i][$j] = $r[1]; $this->cellReferenceHelper->updateCellReference('A' . $range[$i][$j])
)[1];
} else { } else {
$range[$i][$j] = $this->updateSingleCellReference($range[$i][$j], $beforeCellAddress, $numberOfColumns, $numberOfRows); $range[$i][$j] = $this->cellReferenceHelper->updateCellReference($range[$i][$j]);
} }
} }
} }
@ -899,46 +869,6 @@ class ReferenceHelper
return Coordinate::buildRange($range); return Coordinate::buildRange($range);
} }
/**
* Update single cell reference.
*
* @param string $cellReference Single cell reference
* @param string $beforeCellAddress Insert before this one
* @param int $numberOfColumns Number of columns to increment
* @param int $numberOfRows Number of rows to increment
*
* @return string Updated cell reference
*/
private function updateSingleCellReference($cellReference = 'A1', $beforeCellAddress = 'A1', $numberOfColumns = 0, $numberOfRows = 0)
{
if (Coordinate::coordinateIsRange($cellReference)) {
throw new Exception('Only single cell references may be passed to this method.');
}
// Get coordinate of $beforeCellAddress
[$beforeColumn, $beforeRow] = Coordinate::coordinateFromString($beforeCellAddress);
// Get coordinate of $cellReference
[$newColumn, $newRow] = Coordinate::coordinateFromString($cellReference);
// Verify which parts should be updated
$updateColumn = (($newColumn[0] != '$') && ($beforeColumn[0] != '$') && (Coordinate::columnIndexFromString($newColumn) >= Coordinate::columnIndexFromString($beforeColumn)));
$updateRow = (($newRow[0] != '$') && ($beforeRow[0] != '$') && $newRow >= $beforeRow);
// Create new column reference
if ($updateColumn) {
$newColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($newColumn) + $numberOfColumns);
}
// Create new row reference
if ($updateRow) {
$newRow = (int) $newRow + $numberOfRows;
}
// Return new reference
return $newColumn . $newRow;
}
private function clearColumnStrips(int $highestRow, int $beforeColumn, int $numberOfColumns, Worksheet $worksheet): void private function clearColumnStrips(int $highestRow, int $beforeColumn, int $numberOfColumns, Worksheet $worksheet): void
{ {
for ($i = 1; $i <= $highestRow - 1; ++$i) { for ($i = 1; $i <= $highestRow - 1; ++$i) {
@ -969,7 +899,7 @@ class ReferenceHelper
} }
} }
private function adjustAutoFilter(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void private function adjustAutoFilter(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns): void
{ {
$autoFilter = $worksheet->getAutoFilter(); $autoFilter = $worksheet->getAutoFilter();
$autoFilterRange = $autoFilter->getRange(); $autoFilterRange = $autoFilter->getRange();
@ -999,7 +929,7 @@ class ReferenceHelper
} }
$worksheet->setAutoFilter( $worksheet->setAutoFilter(
$this->updateCellReference($autoFilterRange, $beforeCellAddress, $numberOfColumns, $numberOfRows) $this->updateCellReference($autoFilterRange)
); );
} }
} }

View File

@ -3,8 +3,11 @@
namespace PhpOffice\PhpSpreadsheetTests; namespace PhpOffice\PhpSpreadsheetTests;
use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
use PhpOffice\PhpSpreadsheet\Comment;
use PhpOffice\PhpSpreadsheet\ReferenceHelper; use PhpOffice\PhpSpreadsheet\ReferenceHelper;
use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class ReferenceHelperTest extends TestCase class ReferenceHelperTest extends TestCase
@ -177,4 +180,120 @@ class ReferenceHelperTest extends TestCase
self::assertNull($cells[1][1]); self::assertNull($cells[1][1]);
self::assertArrayNotHasKey(2, $cells[1]); self::assertArrayNotHasKey(2, $cells[1]);
} }
public function testInsertRowsWithPageBreaks(): void
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->fromArray([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]], null, 'A1', true);
$sheet->setBreak('A2', Worksheet::BREAK_ROW);
$sheet->setBreak('A5', Worksheet::BREAK_ROW);
$sheet->insertNewRowBefore(2, 2);
$breaks = $sheet->getBreaks();
ksort($breaks);
self::assertSame(['A4' => Worksheet::BREAK_ROW, 'A7' => Worksheet::BREAK_ROW], $breaks);
}
public function testDeleteRowsWithPageBreaks(): void
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->fromArray([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]], null, 'A1', true);
$sheet->setBreak('A2', Worksheet::BREAK_ROW);
$sheet->setBreak('A5', Worksheet::BREAK_ROW);
$sheet->removeRow(2, 2);
$breaks = $sheet->getBreaks();
self::assertSame(['A3' => Worksheet::BREAK_ROW], $breaks);
}
public function testInsertRowsWithComments(): void
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->fromArray([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]], null, 'A1', true);
$sheet->getComment('A2')->getText()->createText('First Comment');
$sheet->getComment('A5')->getText()->createText('Second Comment');
$sheet->insertNewRowBefore(2, 2);
$comments = array_map(
function (Comment $value) {
return $value->getText()->getPlainText();
},
$sheet->getComments()
);
self::assertSame(['A4' => 'First Comment', 'A7' => 'Second Comment'], $comments);
}
public function testDeleteRowsWithComments(): void
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->fromArray([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]], null, 'A1', true);
$sheet->getComment('A2')->getText()->createText('First Comment');
$sheet->getComment('A5')->getText()->createText('Second Comment');
$sheet->removeRow(2, 2);
$comments = array_map(
function (Comment $value) {
return $value->getText()->getPlainText();
},
$sheet->getComments()
);
self::assertSame(['A3' => 'Second Comment'], $comments);
}
public function testInsertRowsWithHyperlinks(): void
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->fromArray([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]], null, 'A1', true);
$sheet->getCell('A2')->getHyperlink()->setUrl('https://github.com/PHPOffice/PhpSpreadsheet');
$sheet->getCell('A5')->getHyperlink()->setUrl('https://phpspreadsheet.readthedocs.io/en/latest/');
$sheet->insertNewRowBefore(2, 2);
$hyperlinks = array_map(
function (Hyperlink $value) {
return $value->getUrl();
},
$sheet->getHyperlinkCollection()
);
ksort($hyperlinks);
self::assertSame(
[
'A4' => 'https://github.com/PHPOffice/PhpSpreadsheet',
'A7' => 'https://phpspreadsheet.readthedocs.io/en/latest/',
],
$hyperlinks
);
}
public function testDeleteRowsWithHyperlinks(): void
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->fromArray([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]], null, 'A1', true);
$sheet->getCell('A2')->getHyperlink()->setUrl('https://github.com/PHPOffice/PhpSpreadsheet');
$sheet->getCell('A5')->getHyperlink()->setUrl('https://phpspreadsheet.readthedocs.io/en/latest/');
$sheet->removeRow(2, 2);
$hyperlinks = array_map(
function (Hyperlink $value) {
return $value->getUrl();
},
$sheet->getHyperlinkCollection()
);
self::assertSame(['A3' => 'https://phpspreadsheet.readthedocs.io/en/latest/'], $hyperlinks);
}
} }

View File

@ -22,6 +22,34 @@ return [
'2020', '2020',
'=SUM(A1:C3)', '=SUM(A1:C3)',
], ],
'column range' => [
'=SUM(B:C)',
2,
0,
'2020',
'=SUM(D:E)',
],
'column range with absolute' => [
'=SUM($B:C)',
2,
0,
'2020',
'=SUM($B:E)',
],
'row range' => [
'=SUM(2:3)',
0,
2,
'2020',
'=SUM(4:5)',
],
'row range with absolute' => [
'=SUM($2:3)',
0,
2,
'2020',
'=SUM($2:5)',
],
[ [
'=SUM(2020!C3:E5,2019!C3:E5)', '=SUM(2020!C3:E5,2019!C3:E5)',
-2, -2,