Skip to content

Absolute References in Cell Formulae should always be updated when inserting/deleting rows/columns #3402

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/PhpSpreadsheet/CellReferenceHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,14 @@ protected function updateColumnReference(int $newColumnIndex, string $absoluteCo
{
$newColumn = Coordinate::stringFromColumnIndex(min($newColumnIndex + $this->numberOfColumns, AddressRange::MAX_COLUMN_INT));

return $absoluteColumn . $newColumn;
return "{$absoluteColumn}{$newColumn}";
}

protected function updateRowReference(int $newRowIndex, string $absoluteRow): string
{
$newRow = $newRowIndex + $this->numberOfRows;
$newRow = ($newRow > AddressRange::MAX_ROW) ? AddressRange::MAX_ROW : $newRow;

return $absoluteRow . (string) $newRow;
return "{$absoluteRow}{$newRow}";
}
}
36 changes: 25 additions & 11 deletions src/PhpSpreadsheet/ReferenceHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ public function insertNewBefore(
$cellCollection = $worksheet->getCellCollection();
$missingCoordinates = array_filter(
array_map(function ($row) use ($highestColumn) {
return $highestColumn . $row;
return "{$highestColumn}{$row}";
}, range(1, $highestRow)),
function ($coordinate) use ($cellCollection) {
return $cellCollection->has($coordinate) === false;
Expand Down Expand Up @@ -453,20 +453,20 @@ function ($coordinate) use ($cellCollection) {
if ($cell->getDataType() === DataType::TYPE_FORMULA) {
// Formula should be adjusted
$worksheet->getCell($newCoordinate)
->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle()));
->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true));
} else {
// Formula should not be adjusted
// Cell value should not be adjusted
$worksheet->getCell($newCoordinate)->setValueExplicit($cell->getValue(), $cell->getDataType());
}

// Clear the original cell
$worksheet->getCellCollection()->delete($coordinate);
} else {
/* 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) {
// 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(), true));
}
}
}
Expand Down Expand Up @@ -609,7 +609,7 @@ public function updateFormulaReferences(
// Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
$column = 100000;
$row = 10000000 + (int) trim($match[3], '$');
$cellIndex = $column . $row;
$cellIndex = "{$column}{$row}";

$newCellTokens[$cellIndex] = preg_quote($toString, '/');
$cellTokens[$cellIndex] = '/(?<!\d\$\!)' . preg_quote($fromString, '/') . '(?!\d)/i';
Expand All @@ -634,7 +634,7 @@ public function updateFormulaReferences(
// Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
$column = Coordinate::columnIndexFromString(trim($match[3], '$')) + 100000;
$row = 10000000;
$cellIndex = $column . $row;
$cellIndex = "{$column}{$row}";

$newCellTokens[$cellIndex] = preg_quote($toString, '/');
$cellTokens[$cellIndex] = '/(?<![A-Z\$\!])' . preg_quote($fromString, '/') . '(?![A-Z])/i';
Expand All @@ -660,7 +660,7 @@ public function updateFormulaReferences(
// Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
$column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000;
$row = (int) trim($row, '$') + 10000000;
$cellIndex = $column . $row;
$cellIndex = "{$column}{$row}";

$newCellTokens[$cellIndex] = preg_quote($toString, '/');
$cellTokens[$cellIndex] = '/(?<![A-Z]\$\!)' . preg_quote($fromString, '/') . '(?!\d)/i';
Expand Down Expand Up @@ -917,20 +917,34 @@ private function updateNamedRange(DefinedName $definedName, Worksheet $worksheet
$cellAddress = $definedName->getValue();
$asFormula = ($cellAddress[0] === '=');
if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) {
/**
* If we delete the entire range that is referenced by a Named Range, MS Excel sets the value to #REF!
* PhpSpreadsheet still only does a basic adjustment, so the Named Range will still reference Cells.
* Note that this applies only when deleting columns/rows; subsequent insertion won't fix the #REF!
* TODO Can we work out a method to identify Named Ranges that cease to be valid, so that we can replace
* them with a #REF!
*/
if ($asFormula === true) {
$formula = $this->updateFormulaReferences($cellAddress, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle());
$formula = $this->updateFormulaReferences($cellAddress, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true);
$definedName->setValue($formula);
} else {
$definedName->setValue($this->updateCellReference(ltrim($cellAddress, '=')));
$definedName->setValue($this->updateCellReference(ltrim($cellAddress, '='), true));
}
}
}

private function updateNamedFormula(DefinedName $definedName, Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void
{
if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) {
/**
* If we delete the entire range that is referenced by a Named Formula, MS Excel sets the value to #REF!
* PhpSpreadsheet still only does a basic adjustment, so the Named Formula will still reference Cells.
* Note that this applies only when deleting columns/rows; subsequent insertion won't fix the #REF!
* TODO Can we work out a method to identify Named Ranges that cease to be valid, so that we can replace
* them with a #REF!
*/
$formula = $definedName->getValue();
$formula = $this->updateFormulaReferences($formula, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle());
$formula = $this->updateFormulaReferences($formula, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true);
$definedName->setValue($formula);
}
}
Expand Down