Skip to content

Commit 4971801

Browse files
kevin-verschaeveKevin Verschaeveoleibman
authored
fix: Allow to read data from a table located in a different sheet (#3659)
* fix: Allow to read data from a table located in a different sheet * Add Test Case, Correct Calculation Calculate correct result, at least for calculations with a single answer. For an array of answers, result will be similar to other functions which return an array (dynamic arrays not yet fully supported). * Rename test class * Allow Appropriate Use of Table as DefinedName in Calculations See Issue3569Test, which behaves like Excel. * Remove Dead Code Scrutinizer is correct here. * Memory Leak Worksheet points to Table, Table points back to Worksheet. Circular reference prevents garbage collection. Remove Table collection in Worksheet at destruct time to avoid this problem. * PhpUnit10 Failure Avoiding memory leak triggered segfault in Phpunit 10. Research and fix later. * Add toString to StructuredReference Absence of such a method seems to cause problems for untaken IF branches in Calculation. * Remove Dead Assignments Correctly detected by Scrutinizer. --------- Co-authored-by: Kevin Verschaeve <[email protected]> Co-authored-by: oleibman <[email protected]>
1 parent 37a8a56 commit 4971801

File tree

6 files changed

+174
-3
lines changed

6 files changed

+174
-3
lines changed

src/PhpSpreadsheet/Calculation/Calculation.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
1313
use PhpOffice\PhpSpreadsheet\Cell\DataType;
1414
use PhpOffice\PhpSpreadsheet\DefinedName;
15+
use PhpOffice\PhpSpreadsheet\NamedRange;
1516
use PhpOffice\PhpSpreadsheet\ReferenceHelper;
1617
use PhpOffice\PhpSpreadsheet\Shared;
1718
use PhpOffice\PhpSpreadsheet\Spreadsheet;
@@ -5176,6 +5177,26 @@ private function processTokenStack($tokens, $cellID = null, ?Cell $cell = null)
51765177

51775178
$this->debugLog->writeDebugLog('Evaluating Defined Name %s', $definedName);
51785179
$namedRange = DefinedName::resolveName($definedName, $pCellWorksheet);
5180+
// If not Defined Name, try as Table.
5181+
if ($namedRange === null && $this->spreadsheet !== null) {
5182+
$table = $this->spreadsheet->getTableByName($definedName);
5183+
if ($table !== null) {
5184+
$tableRange = Coordinate::getRangeBoundaries($table->getRange());
5185+
if ($table->getShowHeaderRow()) {
5186+
++$tableRange[0][1];
5187+
}
5188+
if ($table->getShowTotalsRow()) {
5189+
--$tableRange[1][1];
5190+
}
5191+
$tableRangeString =
5192+
'$' . $tableRange[0][0]
5193+
. '$' . $tableRange[0][1]
5194+
. ':'
5195+
. '$' . $tableRange[1][0]
5196+
. '$' . $tableRange[1][1];
5197+
$namedRange = new NamedRange($definedName, $table->getWorksheet(), $tableRangeString);
5198+
}
5199+
}
51795200
if ($namedRange === null) {
51805201
return $this->raiseFormulaError("undefined name '$definedName'");
51815202
}

src/PhpSpreadsheet/Calculation/Engine/Operands/StructuredReference.php

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,13 @@ public function parse(Cell $cell): string
8585
{
8686
$this->getTableStructure($cell);
8787
$cellRange = ($this->isRowReference()) ? $this->getRowReference($cell) : $this->getColumnReference();
88+
$sheetName = '';
89+
$worksheet = $this->table->getWorksheet();
90+
if ($worksheet !== null && $worksheet !== $cell->getWorksheet()) {
91+
$sheetName = "'" . $worksheet->getTitle() . "'!";
92+
}
8893

89-
return $cellRange;
94+
return $sheetName . $cellRange;
9095
}
9196

9297
private function isRowReference(): bool
@@ -115,7 +120,12 @@ private function getTableStructure(Cell $cell): void
115120
$this->totalsRow = ($this->table->getShowTotalsRow()) ? (int) $tableRange[1][1] : null;
116121
$this->lastDataRow = ($this->table->getShowTotalsRow()) ? (int) $tableRange[1][1] - 1 : $tableRange[1][1];
117122

118-
$this->columns = $this->getColumns($cell, $tableRange);
123+
$cellParam = $cell;
124+
$worksheet = $this->table->getWorksheet();
125+
if ($worksheet !== null && $worksheet !== $cell->getWorksheet()) {
126+
$cellParam = $worksheet->getCell('A1');
127+
}
128+
$this->columns = $this->getColumns($cellParam, $tableRange);
119129
}
120130

121131
/**
@@ -146,6 +156,13 @@ private function getTableByName(Cell $cell): Table
146156
{
147157
$table = $cell->getWorksheet()->getTableByName($this->tableName);
148158

159+
if ($table === null) {
160+
$spreadsheet = $cell->getWorksheet()->getParent();
161+
if ($spreadsheet !== null) {
162+
$table = $spreadsheet->getTableByName($this->tableName);
163+
}
164+
}
165+
149166
if ($table === null) {
150167
throw new Exception("Table {$this->tableName} for Structured Reference cannot be located");
151168
}
@@ -324,7 +341,7 @@ private function getColumnsForColumnReference(string $reference, int $startRow,
324341
{
325342
$columnsSelected = false;
326343
foreach ($this->columns as $columnId => $columnName) {
327-
$columnName = str_replace("\u{a0}", ' ', $columnName);
344+
$columnName = str_replace("\u{a0}", ' ', $columnName ?? '');
328345
$cellFrom = "{$columnId}{$startRow}";
329346
$cellTo = "{$columnId}{$endRow}";
330347
$cellReference = ($cellFrom === $cellTo) ? $cellFrom : "{$cellFrom}:{$cellTo}";
@@ -341,4 +358,9 @@ private function getColumnsForColumnReference(string $reference, int $startRow,
341358

342359
return $reference;
343360
}
361+
362+
public function __toString(): string
363+
{
364+
return $this->value;
365+
}
344366
}

src/PhpSpreadsheet/Spreadsheet.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
1010
use PhpOffice\PhpSpreadsheet\Style\Style;
1111
use PhpOffice\PhpSpreadsheet\Worksheet\Iterator;
12+
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
1213
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
1314
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
1415

@@ -1685,4 +1686,17 @@ public function resetThemeFonts(): void
16851686
}
16861687
}
16871688
}
1689+
1690+
public function getTableByName(string $tableName): ?Table
1691+
{
1692+
$table = null;
1693+
foreach ($this->workSheetCollection as $sheet) {
1694+
$table = $sheet->getTableByName($tableName);
1695+
if ($table !== null) {
1696+
break;
1697+
}
1698+
}
1699+
1700+
return $table;
1701+
}
16881702
}

src/PhpSpreadsheet/Worksheet/Worksheet.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,7 @@ public function __destruct()
444444

445445
$this->disconnectCells();
446446
$this->rowDimensions = [];
447+
//$this->removeTableCollection(); // problem with phpunit10
447448
}
448449

449450
/**
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheetTests\Worksheet\Table;
4+
5+
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
6+
7+
class Issue3635Test extends SetupTeardown
8+
{
9+
public function testNewExample(): void
10+
{
11+
$sheet = $this->getSheet();
12+
$sheet->setTitle('Feuil1');
13+
$sheet->fromArray(
14+
[
15+
['Numbers'],
16+
[1],
17+
[2],
18+
[3],
19+
[4],
20+
[5],
21+
],
22+
null,
23+
'B2',
24+
true
25+
);
26+
$table = new Table('B2:B7', 'Tableau2');
27+
$sheet->addTable($table);
28+
$sheet->getCell('E2')->setValue('=IF(E3>0,SUM(SUM(Tableau2),SUM(Tableau2)),"NOPE")');
29+
$sheet->getCell('G2')->setValue('=IF(G3>0,SUM(SUM(Tableau2),SUM(Tableau2)),"NOPE")');
30+
$sheet->getCell('G3')->setValue(1);
31+
self::assertSame('NOPE', $sheet->getCell('E2')->getCalculatedValue());
32+
self::assertSame(30, $sheet->getCell('G2')->getCalculatedValue());
33+
}
34+
35+
public function testOriginalExample(): void
36+
{
37+
$sheet = $this->getSheet();
38+
$formula = '=IF([@BAUTEIL]="EK","Entrauchungsklappe",(IF([@BAUTEIL]="VE","Entrauchungsventilator",(IF([@BAUTEIL]="JK","Jalousieklappe",(IF([@BAUTEIL]="LK","Lichtkuppel","ok")))))))';
39+
$sheet->fromArray(
40+
[
41+
['BAUTEIL', 'Result'],
42+
['EK', $formula],
43+
['VE', $formula],
44+
['EK', $formula],
45+
['JK', $formula],
46+
['LK', $formula],
47+
['None', $formula],
48+
[null, $formula],
49+
[null, $formula],
50+
[null, $formula],
51+
],
52+
null,
53+
'A1',
54+
true
55+
);
56+
$table = new Table('A1:B10', 'Tableau3');
57+
$sheet->addTable($table);
58+
self::assertSame('Entrauchungsklappe', $sheet->getCell('B2')->getCalculatedValue());
59+
self::assertSame('Entrauchungsventilator', $sheet->getCell('B3')->getCalculatedValue());
60+
self::assertSame('Entrauchungsklappe', $sheet->getCell('B4')->getCalculatedValue());
61+
self::assertSame('Jalousieklappe', $sheet->getCell('B5')->getCalculatedValue());
62+
self::assertSame('Lichtkuppel', $sheet->getCell('B6')->getCalculatedValue());
63+
self::assertSame('ok', $sheet->getCell('B7')->getCalculatedValue());
64+
self::assertSame('ok', $sheet->getCell('B8')->getCalculatedValue());
65+
self::assertSame('ok', $sheet->getCell('B9')->getCalculatedValue());
66+
self::assertSame('ok', $sheet->getCell('B10')->getCalculatedValue());
67+
}
68+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheetTests\Worksheet\Table;
4+
5+
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
6+
7+
class Issue3659Test extends SetupTeardown
8+
{
9+
public function testTableOnOtherSheet(): void
10+
{
11+
$spreadsheet = $this->getSpreadsheet();
12+
$sheet = $this->getSheet();
13+
$sheet->setTitle('Feuil1');
14+
$tableSheet = $spreadsheet->createSheet();
15+
$tableSheet->setTitle('sheet_with_table');
16+
$tableSheet->fromArray(
17+
[
18+
['MyCol', 'Colonne2', 'Colonne3'],
19+
[10, 20],
20+
[2],
21+
[3],
22+
[4],
23+
],
24+
null,
25+
'B1',
26+
true
27+
);
28+
$table = new Table('B1:D5', 'Tableau1');
29+
$tableSheet->addTable($table);
30+
$sheet->setSelectedCells('F7');
31+
$tableSheet->setSelectedCells('F8');
32+
self::assertSame($sheet, $spreadsheet->getActiveSheet());
33+
$sheet->getCell('A1')->setValue('=SUM(Tableau1[MyCol])');
34+
$sheet->getCell('A2')->setValue('=SUM(Tableau1[])');
35+
$sheet->getCell('A3')->setValue('=SUM(Tableau1)');
36+
$sheet->getCell('A4')->setValue('=CONCAT(Tableau1)');
37+
self::assertSame(19, $sheet->getCell('A1')->getCalculatedValue());
38+
self::assertSame(39, $sheet->getCell('A2')->getCalculatedValue());
39+
self::assertSame(39, $sheet->getCell('A3')->getCalculatedValue());
40+
self::assertSame('1020234', $sheet->getCell('A4')->getCalculatedValue(), 'Header row not included');
41+
self::assertSame('F7', $sheet->getSelectedCells());
42+
self::assertSame('F8', $tableSheet->getSelectedCells());
43+
self::assertSame($sheet, $spreadsheet->getActiveSheet());
44+
}
45+
}

0 commit comments

Comments
 (0)