Skip to content

Commit 10d175e

Browse files
authored
Merge pull request #2671 from aswinkumar863/Table-for-Xlsx
Initial implementation of Excel's tables feature
2 parents ed11a41 + 534cbc0 commit 10d175e

File tree

19 files changed

+2225
-3
lines changed

19 files changed

+2225
-3
lines changed

phpstan-baseline.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5182,7 +5182,7 @@ parameters:
51825182

51835183
-
51845184
message: "#^Parameter \\#2 \\$id of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Rels\\:\\:writeRelationship\\(\\) expects int, string given\\.$#"
5185-
count: 4
5185+
count: 5
51865186
path: src/PhpSpreadsheet/Writer/Xlsx/Rels.php
51875187

51885188
-

samples/Table/01_Table.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
4+
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
5+
use PhpOffice\PhpSpreadsheet\Worksheet\Table\TableStyle;
6+
7+
require __DIR__ . '/../Header.php';
8+
9+
// Create new Spreadsheet object
10+
$helper->log('Create new Spreadsheet object');
11+
$spreadsheet = new Spreadsheet();
12+
13+
// Set document properties
14+
$helper->log('Set document properties');
15+
$spreadsheet->getProperties()->setCreator('aswinkumar863')
16+
->setLastModifiedBy('aswinkumar863')
17+
->setTitle('PhpSpreadsheet Table Test Document')
18+
->setSubject('PhpSpreadsheet Table Test Document')
19+
->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.')
20+
->setKeywords('office PhpSpreadsheet php')
21+
->setCategory('Table');
22+
23+
// Create the worksheet
24+
$helper->log('Add data');
25+
26+
$spreadsheet->setActiveSheetIndex(0);
27+
$spreadsheet->getActiveSheet()->setCellValue('A1', 'Year')
28+
->setCellValue('B1', 'Quarter')
29+
->setCellValue('C1', 'Country')
30+
->setCellValue('D1', 'Sales');
31+
32+
$dataArray = [
33+
['2010', 'Q1', 'United States', 790],
34+
['2010', 'Q2', 'United States', 730],
35+
['2010', 'Q3', 'United States', 860],
36+
['2010', 'Q4', 'United States', 850],
37+
['2011', 'Q1', 'United States', 800],
38+
['2011', 'Q2', 'United States', 700],
39+
['2011', 'Q3', 'United States', 900],
40+
['2011', 'Q4', 'United States', 950],
41+
['2010', 'Q1', 'Belgium', 380],
42+
['2010', 'Q2', 'Belgium', 390],
43+
['2010', 'Q3', 'Belgium', 420],
44+
['2010', 'Q4', 'Belgium', 460],
45+
['2011', 'Q1', 'Belgium', 400],
46+
['2011', 'Q2', 'Belgium', 350],
47+
['2011', 'Q3', 'Belgium', 450],
48+
['2011', 'Q4', 'Belgium', 500],
49+
];
50+
51+
$spreadsheet->getActiveSheet()->fromArray($dataArray, null, 'A2');
52+
53+
// Create Table
54+
$helper->log('Create Table');
55+
$table = new Table('A1:D17', 'Sales_Data');
56+
57+
// Create Columns
58+
$table->getColumn('D')->setShowFilterButton(false);
59+
60+
// Create Table Style
61+
$helper->log('Create Table Style');
62+
$tableStyle = new TableStyle();
63+
$tableStyle->setTheme(TableStyle::TABLE_STYLE_MEDIUM2);
64+
$tableStyle->setShowRowStripes(true);
65+
$tableStyle->setShowColumnStripes(true);
66+
$tableStyle->setShowFirstColumn(true);
67+
$tableStyle->setShowLastColumn(true);
68+
$table->setStyle($tableStyle);
69+
70+
// Add Table to Worksheet
71+
$helper->log('Add Table to Worksheet');
72+
$spreadsheet->getActiveSheet()->addTable($table);
73+
74+
// Save
75+
$helper->write($spreadsheet, __FILE__, ['Xlsx']);

samples/Table/02_Table_Total.php

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
use PhpOffice\PhpSpreadsheet\IOFactory;
4+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
5+
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
6+
7+
require __DIR__ . '/../Header.php';
8+
9+
// Create new Spreadsheet object
10+
$helper->log('Create new Spreadsheet object');
11+
$spreadsheet = new Spreadsheet();
12+
13+
// Set document properties
14+
$helper->log('Set document properties');
15+
$spreadsheet->getProperties()->setCreator('aswinkumar863')
16+
->setLastModifiedBy('aswinkumar863')
17+
->setTitle('PhpSpreadsheet Table Test Document')
18+
->setSubject('PhpSpreadsheet Table Test Document')
19+
->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.')
20+
->setKeywords('office PhpSpreadsheet php')
21+
->setCategory('Table');
22+
23+
// Create the worksheet
24+
$helper->log('Add data');
25+
26+
$spreadsheet->setActiveSheetIndex(0);
27+
$spreadsheet->getActiveSheet()->setCellValue('A1', 'Year')
28+
->setCellValue('B1', 'Quarter')
29+
->setCellValue('C1', 'Country')
30+
->setCellValue('D1', 'Sales');
31+
32+
$dataArray = [
33+
['2010', 'Q1', 'United States', 790],
34+
['2010', 'Q2', 'United States', 730],
35+
['2010', 'Q3', 'United States', 860],
36+
['2010', 'Q4', 'United States', 850],
37+
['2011', 'Q1', 'United States', 800],
38+
['2011', 'Q2', 'United States', 700],
39+
['2011', 'Q3', 'United States', 900],
40+
['2011', 'Q4', 'United States', 950],
41+
['2010', 'Q1', 'Belgium', 380],
42+
['2010', 'Q2', 'Belgium', 390],
43+
['2010', 'Q3', 'Belgium', 420],
44+
['2010', 'Q4', 'Belgium', 460],
45+
['2011', 'Q1', 'Belgium', 400],
46+
['2011', 'Q2', 'Belgium', 350],
47+
['2011', 'Q3', 'Belgium', 450],
48+
['2011', 'Q4', 'Belgium', 500],
49+
];
50+
51+
$spreadsheet->getActiveSheet()->fromArray($dataArray, null, 'A2');
52+
53+
// Table
54+
$helper->log('Create Table');
55+
$table = new Table();
56+
$table->setName('SalesData');
57+
$table->setShowTotalsRow(true);
58+
$table->setRange('A1:D18'); // +1 row for totalsRow
59+
60+
$helper->log('Add Totals Row');
61+
// Table column label not implemented yet,
62+
$table->getColumn('A')->setTotalsRowLabel('Total');
63+
// So set the label directly to the cell
64+
$spreadsheet->getActiveSheet()->getCell('A18')->setValue('Total');
65+
66+
// Table column function not implemented yet,
67+
$table->getColumn('D')->setTotalsRowFunction('sum');
68+
// So set the formula directly to the cell
69+
$spreadsheet->getActiveSheet()->getCell('D18')->setValue('=SUBTOTAL(109,SalesData[Sales])');
70+
71+
// Add Table to Worksheet
72+
$helper->log('Add Table to Worksheet');
73+
$spreadsheet->getActiveSheet()->addTable($table);
74+
75+
// Save
76+
$path = $helper->getFilename(__FILE__);
77+
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
78+
79+
// Disable precalculation to add table's total row
80+
$writer->setPreCalculateFormulas(false);
81+
$callStartTime = microtime(true);
82+
$writer->save($path);
83+
$helper->logWrite($writer, $path, $callStartTime);
84+
$helper->logEndingNotes();

samples/Table/03_Column_Formula.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
use PhpOffice\PhpSpreadsheet\IOFactory;
4+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
5+
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
6+
7+
require __DIR__ . '/../Header.php';
8+
9+
// Create new Spreadsheet object
10+
$helper->log('Create new Spreadsheet object');
11+
$spreadsheet = new Spreadsheet();
12+
13+
// Set document properties
14+
$helper->log('Set document properties');
15+
$spreadsheet->getProperties()->setCreator('aswinkumar863')
16+
->setLastModifiedBy('aswinkumar863')
17+
->setTitle('PhpSpreadsheet Table Test Document')
18+
->setSubject('PhpSpreadsheet Table Test Document')
19+
->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.')
20+
->setKeywords('office PhpSpreadsheet php')
21+
->setCategory('Table');
22+
23+
// Create the worksheet
24+
$helper->log('Add data');
25+
26+
$spreadsheet->setActiveSheetIndex(0);
27+
28+
$columnFormula = '=SUM(Sales_Data[[#This Row],[Q1]:[Q4]])';
29+
30+
$dataArray = [
31+
['Year', 'Country', 'Q1', 'Q2', 'Q3', 'Q4', 'Sales'],
32+
[2010, 'Belgium', 380, 390, 420, 460, $columnFormula],
33+
[2010, 'France', 510, 490, 460, 590, $columnFormula],
34+
[2010, 'Germany', 720, 680, 640, 660, $columnFormula],
35+
[2010, 'Italy', 440, 410, 420, 450, $columnFormula],
36+
[2010, 'Spain', 510, 490, 470, 420, $columnFormula],
37+
[2010, 'UK', 690, 610, 620, 600, $columnFormula],
38+
[2010, 'United States', 790, 730, 860, 850, $columnFormula],
39+
[2011, 'Belgium', 400, 350, 450, 500, $columnFormula],
40+
[2011, 'France', 620, 650, 415, 570, $columnFormula],
41+
[2011, 'Germany', 680, 620, 710, 690, $columnFormula],
42+
[2011, 'Italy', 430, 370, 350, 335, $columnFormula],
43+
[2011, 'Spain', 460, 390, 430, 415, $columnFormula],
44+
[2011, 'UK', 720, 650, 580, 510, $columnFormula],
45+
[2011, 'United States', 800, 700, 900, 950, $columnFormula],
46+
];
47+
48+
$spreadsheet->getActiveSheet()->fromArray($dataArray, null, 'A1');
49+
50+
// Create Table
51+
$helper->log('Create Table');
52+
$table = new Table('A1:G15', 'Sales_Data');
53+
$table->setRange('A1:G15');
54+
55+
// Set Column Formula
56+
$table->getColumn('G')->setColumnFormula($columnFormula);
57+
58+
// Add Table to Worksheet
59+
$helper->log('Add Table to Worksheet');
60+
$spreadsheet->getActiveSheet()->addTable($table);
61+
62+
// Save
63+
$path = $helper->getFilename(__FILE__);
64+
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
65+
66+
// Disable precalculation to add table's total row
67+
$writer->setPreCalculateFormulas(false);
68+
$callStartTime = microtime(true);
69+
$writer->save($path);
70+
$helper->logWrite($writer, $path, $callStartTime);
71+
$helper->logEndingNotes();

src/PhpSpreadsheet/ReferenceHelper.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PhpOffice\PhpSpreadsheet\Cell\DataType;
88
use PhpOffice\PhpSpreadsheet\Style\Conditional;
99
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter;
10+
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
1011
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
1112

1213
class ReferenceHelper
@@ -497,6 +498,9 @@ function ($coordinate) use ($allCoordinates) {
497498
// Update worksheet: autofilter
498499
$this->adjustAutoFilter($worksheet, $beforeCellAddress, $numberOfColumns);
499500

501+
// Update worksheet: table
502+
$this->adjustTable($worksheet, $beforeCellAddress, $numberOfColumns);
503+
500504
// Update worksheet: freeze pane
501505
if ($worksheet->getFreezePane()) {
502506
$splitCell = $worksheet->getFreezePane() ?? '';
@@ -1026,6 +1030,85 @@ private function adjustAutoFilterDelete(int $startCol, int $numberOfColumns, int
10261030
} while ($startColID !== $endColID);
10271031
}
10281032

1033+
private function adjustTable(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns): void
1034+
{
1035+
$tableCollection = $worksheet->getTableCollection();
1036+
1037+
foreach ($tableCollection as $table) {
1038+
$tableRange = $table->getRange();
1039+
if (!empty($tableRange)) {
1040+
if ($numberOfColumns !== 0) {
1041+
$tableColumns = $table->getColumns();
1042+
if (count($tableColumns) > 0) {
1043+
$column = '';
1044+
$row = 0;
1045+
sscanf($beforeCellAddress, '%[A-Z]%d', $column, $row);
1046+
$columnIndex = Coordinate::columnIndexFromString($column);
1047+
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($tableRange);
1048+
if ($columnIndex <= $rangeEnd[0]) {
1049+
if ($numberOfColumns < 0) {
1050+
$this->adjustTableDeleteRules($columnIndex, $numberOfColumns, $tableColumns, $table);
1051+
}
1052+
$startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0];
1053+
1054+
// Shuffle columns in table range
1055+
if ($numberOfColumns > 0) {
1056+
$this->adjustTableInsert($startCol, $numberOfColumns, $rangeEnd[0], $table);
1057+
} else {
1058+
$this->adjustTableDelete($startCol, $numberOfColumns, $rangeEnd[0], $table);
1059+
}
1060+
}
1061+
}
1062+
}
1063+
1064+
$table->setRange($this->updateCellReference($tableRange));
1065+
}
1066+
}
1067+
}
1068+
1069+
private function adjustTableDeleteRules(int $columnIndex, int $numberOfColumns, array $tableColumns, Table $table): void
1070+
{
1071+
// If we're actually deleting any columns that fall within the table range,
1072+
// then we delete any rules for those columns
1073+
$deleteColumn = $columnIndex + $numberOfColumns - 1;
1074+
$deleteCount = abs($numberOfColumns);
1075+
1076+
for ($i = 1; $i <= $deleteCount; ++$i) {
1077+
$columnName = Coordinate::stringFromColumnIndex($deleteColumn + 1);
1078+
if (isset($tableColumns[$columnName])) {
1079+
$table->clearColumn($columnName);
1080+
}
1081+
++$deleteColumn;
1082+
}
1083+
}
1084+
1085+
private function adjustTableInsert(int $startCol, int $numberOfColumns, int $rangeEnd, Table $table): void
1086+
{
1087+
$startColRef = $startCol;
1088+
$endColRef = $rangeEnd;
1089+
$toColRef = $rangeEnd + $numberOfColumns;
1090+
1091+
do {
1092+
$table->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef));
1093+
--$endColRef;
1094+
--$toColRef;
1095+
} while ($startColRef <= $endColRef);
1096+
}
1097+
1098+
private function adjustTableDelete(int $startCol, int $numberOfColumns, int $rangeEnd, Table $table): void
1099+
{
1100+
// For delete, we shuffle from beginning to end to avoid overwriting
1101+
$startColID = Coordinate::stringFromColumnIndex($startCol);
1102+
$toColID = Coordinate::stringFromColumnIndex($startCol + $numberOfColumns);
1103+
$endColID = Coordinate::stringFromColumnIndex($rangeEnd + 1);
1104+
1105+
do {
1106+
$table->shiftColumn($startColID, $toColID);
1107+
++$startColID;
1108+
++$toColID;
1109+
} while ($startColID !== $endColID);
1110+
}
1111+
10291112
private function duplicateStylesByColumn(Worksheet $worksheet, int $beforeColumn, int $beforeRow, int $highestRow, int $numberOfColumns): void
10301113
{
10311114
$beforeColumnName = Coordinate::stringFromColumnIndex($beforeColumn - 1);

0 commit comments

Comments
 (0)