Skip to content

Commit dcc2563

Browse files
committed
Retitling Cloned Worksheets
Fix #641 (marked stale in 2018, but now reopened). When a sheet's title is changed, PhpSpreadsheet updates references to the old sheet name found in formulas. Which is a good idea when the sheet is attached to the spreadsheet, but a bad idea when it isn't (often because it has been cloned without re-attaching to the spreadsheet). This PR continues to change formulas in the former case, but will no longer do so for the latter.
1 parent fb757cf commit dcc2563

File tree

3 files changed

+95
-3
lines changed

3 files changed

+95
-3
lines changed

src/PhpSpreadsheet/Spreadsheet.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,16 @@ public function getActiveSheet(): Worksheet
514514
public function createSheet(?int $sheetIndex = null): Worksheet
515515
{
516516
$newSheet = new Worksheet($this);
517+
$title = $newSheet->getTitle();
518+
if ($this->sheetNameExists($title)) {
519+
$i = 1;
520+
$newTitle = "$title $i";
521+
while ($this->sheetNameExists($newTitle)) {
522+
++$i;
523+
$newTitle = "$title $i";
524+
}
525+
$newSheet->setTitle($newTitle);
526+
}
517527
$this->addSheet($newSheet, $sheetIndex);
518528

519529
return $newSheet;

src/PhpSpreadsheet/Worksheet/Worksheet.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ public function __construct(?Spreadsheet $parent = null, string $title = 'Worksh
321321
{
322322
// Set parent and title
323323
$this->parent = $parent;
324+
$this->hash = spl_object_id($this);
324325
$this->setTitle($title, false);
325326
// setTitle can change $pTitle
326327
$this->setCodeName($this->getTitle());
@@ -349,7 +350,6 @@ public function __construct(?Spreadsheet $parent = null, string $title = 'Worksh
349350
$this->autoFilter = new AutoFilter('', $this);
350351
// Table collection
351352
$this->tableCollection = new ArrayObject();
352-
$this->hash = spl_object_id($this);
353353
}
354354

355355
/**
@@ -869,7 +869,7 @@ public function setTitle(string $title, bool $updateFormulaCellReferences = true
869869
// Syntax check
870870
self::checkSheetTitle($title);
871871

872-
if ($this->parent) {
872+
if ($this->parent && $this->parent->getIndex($this, true) >= 0) {
873873
// Is there already such sheet name?
874874
if ($this->parent->sheetNameExists($title)) {
875875
// Use name, but append with lowest possible integer
@@ -899,7 +899,7 @@ public function setTitle(string $title, bool $updateFormulaCellReferences = true
899899
// Set title
900900
$this->title = $title;
901901

902-
if ($this->parent && $this->parent->getCalculationEngine()) {
902+
if ($this->parent && $this->parent->getIndex($this, true) >= 0 && $this->parent->getCalculationEngine()) {
903903
// New title
904904
$newTitle = $this->getTitle();
905905
$this->parent->getCalculationEngine()
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Worksheet;
6+
7+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
8+
use PHPUnit\Framework\TestCase;
9+
10+
class Issue641Test extends TestCase
11+
{
12+
/**
13+
* Problem cloning sheet referred to in formulas.
14+
*/
15+
public function testIssue641(): void
16+
{
17+
$xlsx = new Spreadsheet();
18+
$xlsx->removeSheetByIndex(0);
19+
$availableWs = [];
20+
21+
$worksheet = $xlsx->createSheet();
22+
$worksheet->setTitle('Condensed A');
23+
$worksheet->getCell('A1')->setValue("=SUM('Detailed A'!A1:A10)");
24+
$worksheet->getCell('A2')->setValue(mt_rand(1, 30));
25+
$availableWs[] = 'Condensed A';
26+
27+
$worksheet = $xlsx->createSheet();
28+
$worksheet->setTitle('Condensed B');
29+
$worksheet->getCell('A1')->setValue("=SUM('Detailed B'!A1:A10)");
30+
$worksheet->getCell('A2')->setValue(mt_rand(1, 30));
31+
$availableWs[] = 'Condensed B';
32+
33+
// at this point the value in worksheet 'Condensed B' cell A1 is
34+
// =SUM('Detailed B'!A1:A10)
35+
36+
// worksheet in question is cloned and totals are attached
37+
$totalWs1 = clone $xlsx->getSheet($xlsx->getSheetCount() - 1);
38+
$totalWs1->setTitle('Condensed Total');
39+
$xlsx->addSheet($totalWs1);
40+
$formula = '=';
41+
foreach ($availableWs as $ws) {
42+
$formula .= sprintf("+'%s'!A2", $ws);
43+
}
44+
$totalWs1->getCell('A1')->setValue("=SUM('Detailed Total'!A1:A10)");
45+
$totalWs1->getCell('A2')->setValue($formula);
46+
47+
$availableWs = [];
48+
49+
$worksheet = $xlsx->createSheet();
50+
$worksheet->setTitle('Detailed A');
51+
for ($step = 1; $step <= 10; ++$step) {
52+
$worksheet->getCell("A{$step}")->setValue(mt_rand(1, 30));
53+
}
54+
$availableWs[] = 'Detailed A';
55+
56+
$worksheet = $xlsx->createSheet();
57+
$worksheet->setTitle('Detailed B');
58+
for ($step = 1; $step <= 10; ++$step) {
59+
$worksheet->getCell("A{$step}")->setValue(mt_rand(1, 30));
60+
}
61+
$availableWs[] = 'Detailed B';
62+
63+
$totalWs2 = clone $xlsx->getSheet($xlsx->getSheetCount() - 1);
64+
$totalWs2->setTitle('Detailed Total');
65+
$xlsx->addSheet($totalWs2);
66+
67+
for ($step = 1; $step <= 10; ++$step) {
68+
$formula = '=';
69+
foreach ($availableWs as $ws) {
70+
$formula .= sprintf("+'%s'!A%s", $ws, $step);
71+
}
72+
$totalWs2->getCell("A{$step}")->setValue($formula);
73+
}
74+
75+
self::assertSame("=SUM('Detailed A'!A1:A10)", $xlsx->getSheetByName('Condensed A')?->getCell('A1')?->getValue());
76+
self::assertSame("=SUM('Detailed B'!A1:A10)", $xlsx->getSheetByName('Condensed B')?->getCell('A1')?->getValue());
77+
self::assertSame("=SUM('Detailed Total'!A1:A10)", $xlsx->getSheetByName('Condensed Total')?->getCell('A1')?->getValue());
78+
self::assertSame("=+'Detailed A'!A1+'Detailed B'!A1", $xlsx->getSheetByName('Detailed Total')?->getCell('A1')?->getValue());
79+
80+
$xlsx->disconnectWorksheets();
81+
}
82+
}

0 commit comments

Comments
 (0)