Skip to content
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

Initial implementation of Excel's tables feature #2671

Merged
merged 19 commits into from
Apr 23, 2022
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
2 changes: 1 addition & 1 deletion phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -5182,7 +5182,7 @@ parameters:

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

-
Expand Down
75 changes: 75 additions & 0 deletions samples/Table/01_Table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
use PhpOffice\PhpSpreadsheet\Worksheet\Table\TableStyle;

require __DIR__ . '/../Header.php';

// Create new Spreadsheet object
$helper->log('Create new Spreadsheet object');
$spreadsheet = new Spreadsheet();

// Set document properties
$helper->log('Set document properties');
$spreadsheet->getProperties()->setCreator('aswinkumar863')
->setLastModifiedBy('aswinkumar863')
->setTitle('PhpSpreadsheet Table Test Document')
->setSubject('PhpSpreadsheet Table Test Document')
->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.')
->setKeywords('office PhpSpreadsheet php')
->setCategory('Table');

// Create the worksheet
$helper->log('Add data');

$spreadsheet->setActiveSheetIndex(0);
$spreadsheet->getActiveSheet()->setCellValue('A1', 'Year')
->setCellValue('B1', 'Quarter')
->setCellValue('C1', 'Country')
->setCellValue('D1', 'Sales');

$dataArray = [
['2010', 'Q1', 'United States', 790],
['2010', 'Q2', 'United States', 730],
['2010', 'Q3', 'United States', 860],
['2010', 'Q4', 'United States', 850],
['2011', 'Q1', 'United States', 800],
['2011', 'Q2', 'United States', 700],
['2011', 'Q3', 'United States', 900],
['2011', 'Q4', 'United States', 950],
['2010', 'Q1', 'Belgium', 380],
['2010', 'Q2', 'Belgium', 390],
['2010', 'Q3', 'Belgium', 420],
['2010', 'Q4', 'Belgium', 460],
['2011', 'Q1', 'Belgium', 400],
['2011', 'Q2', 'Belgium', 350],
['2011', 'Q3', 'Belgium', 450],
['2011', 'Q4', 'Belgium', 500],
];

$spreadsheet->getActiveSheet()->fromArray($dataArray, null, 'A2');

// Create Table
$helper->log('Create Table');
$table = new Table('A1:D17', 'Sales_Data');

// Create Columns
$table->getColumn('D')->setShowFilterButton(false);

// Create Table Style
$helper->log('Create Table Style');
$tableStyle = new TableStyle();
$tableStyle->setTheme(TableStyle::TABLE_STYLE_MEDIUM2);
$tableStyle->setShowRowStripes(true);
$tableStyle->setShowColumnStripes(true);
$tableStyle->setShowFirstColumn(true);
$tableStyle->setShowLastColumn(true);
$table->setStyle($tableStyle);

// Add Table to Worksheet
$helper->log('Add Table to Worksheet');
$spreadsheet->getActiveSheet()->addTable($table);

// Save
$helper->write($spreadsheet, __FILE__, ['Xlsx']);
84 changes: 84 additions & 0 deletions samples/Table/02_Table_Total.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Table;

require __DIR__ . '/../Header.php';

// Create new Spreadsheet object
$helper->log('Create new Spreadsheet object');
$spreadsheet = new Spreadsheet();

// Set document properties
$helper->log('Set document properties');
$spreadsheet->getProperties()->setCreator('aswinkumar863')
->setLastModifiedBy('aswinkumar863')
->setTitle('PhpSpreadsheet Table Test Document')
->setSubject('PhpSpreadsheet Table Test Document')
->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.')
->setKeywords('office PhpSpreadsheet php')
->setCategory('Table');

// Create the worksheet
$helper->log('Add data');

$spreadsheet->setActiveSheetIndex(0);
$spreadsheet->getActiveSheet()->setCellValue('A1', 'Year')
->setCellValue('B1', 'Quarter')
->setCellValue('C1', 'Country')
->setCellValue('D1', 'Sales');

$dataArray = [
['2010', 'Q1', 'United States', 790],
['2010', 'Q2', 'United States', 730],
['2010', 'Q3', 'United States', 860],
['2010', 'Q4', 'United States', 850],
['2011', 'Q1', 'United States', 800],
['2011', 'Q2', 'United States', 700],
['2011', 'Q3', 'United States', 900],
['2011', 'Q4', 'United States', 950],
['2010', 'Q1', 'Belgium', 380],
['2010', 'Q2', 'Belgium', 390],
['2010', 'Q3', 'Belgium', 420],
['2010', 'Q4', 'Belgium', 460],
['2011', 'Q1', 'Belgium', 400],
['2011', 'Q2', 'Belgium', 350],
['2011', 'Q3', 'Belgium', 450],
['2011', 'Q4', 'Belgium', 500],
];

$spreadsheet->getActiveSheet()->fromArray($dataArray, null, 'A2');

// Table
$helper->log('Create Table');
$table = new Table();
$table->setName('SalesData');
$table->setShowTotalsRow(true);
$table->setRange('A1:D18'); // +1 row for totalsRow

$helper->log('Add Totals Row');
// Table column label not implemented yet,
$table->getColumn('A')->setTotalsRowLabel('Total');
// So set the label directly to the cell
$spreadsheet->getActiveSheet()->getCell('A18')->setValue('Total');

// Table column function not implemented yet,
$table->getColumn('D')->setTotalsRowFunction('sum');
// So set the formula directly to the cell
$spreadsheet->getActiveSheet()->getCell('D18')->setValue('=SUBTOTAL(109,SalesData[Sales])');

// Add Table to Worksheet
$helper->log('Add Table to Worksheet');
$spreadsheet->getActiveSheet()->addTable($table);

// Save
$path = $helper->getFilename(__FILE__);
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');

// Disable precalculation to add table's total row
$writer->setPreCalculateFormulas(false);
$callStartTime = microtime(true);
$writer->save($path);
$helper->logWrite($writer, $path, $callStartTime);
$helper->logEndingNotes();
71 changes: 71 additions & 0 deletions samples/Table/03_Column_Formula.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Table;

require __DIR__ . '/../Header.php';

// Create new Spreadsheet object
$helper->log('Create new Spreadsheet object');
$spreadsheet = new Spreadsheet();

// Set document properties
$helper->log('Set document properties');
$spreadsheet->getProperties()->setCreator('aswinkumar863')
->setLastModifiedBy('aswinkumar863')
->setTitle('PhpSpreadsheet Table Test Document')
->setSubject('PhpSpreadsheet Table Test Document')
->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.')
->setKeywords('office PhpSpreadsheet php')
->setCategory('Table');

// Create the worksheet
$helper->log('Add data');

$spreadsheet->setActiveSheetIndex(0);

$columnFormula = '=SUM(Sales_Data[[#This Row],[Q1]:[Q4]])';

$dataArray = [
['Year', 'Country', 'Q1', 'Q2', 'Q3', 'Q4', 'Sales'],
[2010, 'Belgium', 380, 390, 420, 460, $columnFormula],
[2010, 'France', 510, 490, 460, 590, $columnFormula],
[2010, 'Germany', 720, 680, 640, 660, $columnFormula],
[2010, 'Italy', 440, 410, 420, 450, $columnFormula],
[2010, 'Spain', 510, 490, 470, 420, $columnFormula],
[2010, 'UK', 690, 610, 620, 600, $columnFormula],
[2010, 'United States', 790, 730, 860, 850, $columnFormula],
[2011, 'Belgium', 400, 350, 450, 500, $columnFormula],
[2011, 'France', 620, 650, 415, 570, $columnFormula],
[2011, 'Germany', 680, 620, 710, 690, $columnFormula],
[2011, 'Italy', 430, 370, 350, 335, $columnFormula],
[2011, 'Spain', 460, 390, 430, 415, $columnFormula],
[2011, 'UK', 720, 650, 580, 510, $columnFormula],
[2011, 'United States', 800, 700, 900, 950, $columnFormula],
];

$spreadsheet->getActiveSheet()->fromArray($dataArray, null, 'A1');

// Create Table
$helper->log('Create Table');
$table = new Table('A1:G15', 'Sales_Data');
$table->setRange('A1:G15');

// Set Column Formula
$table->getColumn('G')->setColumnFormula($columnFormula);

// Add Table to Worksheet
$helper->log('Add Table to Worksheet');
$spreadsheet->getActiveSheet()->addTable($table);

// Save
$path = $helper->getFilename(__FILE__);
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');

// Disable precalculation to add table's total row
$writer->setPreCalculateFormulas(false);
$callStartTime = microtime(true);
$writer->save($path);
$helper->logWrite($writer, $path, $callStartTime);
$helper->logEndingNotes();
83 changes: 83 additions & 0 deletions src/PhpSpreadsheet/ReferenceHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter;
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;

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

// Update worksheet: table
$this->adjustTable($worksheet, $beforeCellAddress, $numberOfColumns);

// Update worksheet: freeze pane
if ($worksheet->getFreezePane()) {
$splitCell = $worksheet->getFreezePane() ?? '';
Expand Down Expand Up @@ -1026,6 +1030,85 @@ private function adjustAutoFilterDelete(int $startCol, int $numberOfColumns, int
} while ($startColID !== $endColID);
}

private function adjustTable(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns): void
{
$tableCollection = $worksheet->getTableCollection();

foreach ($tableCollection as $table) {
$tableRange = $table->getRange();
if (!empty($tableRange)) {
if ($numberOfColumns !== 0) {
$tableColumns = $table->getColumns();
if (count($tableColumns) > 0) {
$column = '';
$row = 0;
sscanf($beforeCellAddress, '%[A-Z]%d', $column, $row);
$columnIndex = Coordinate::columnIndexFromString($column);
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($tableRange);
if ($columnIndex <= $rangeEnd[0]) {
if ($numberOfColumns < 0) {
$this->adjustTableDeleteRules($columnIndex, $numberOfColumns, $tableColumns, $table);
}
$startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0];

// Shuffle columns in table range
if ($numberOfColumns > 0) {
$this->adjustTableInsert($startCol, $numberOfColumns, $rangeEnd[0], $table);
} else {
$this->adjustTableDelete($startCol, $numberOfColumns, $rangeEnd[0], $table);
}
}
}
}

$table->setRange($this->updateCellReference($tableRange));
}
}
}

private function adjustTableDeleteRules(int $columnIndex, int $numberOfColumns, array $tableColumns, Table $table): void
{
// If we're actually deleting any columns that fall within the table range,
// then we delete any rules for those columns
$deleteColumn = $columnIndex + $numberOfColumns - 1;
$deleteCount = abs($numberOfColumns);

for ($i = 1; $i <= $deleteCount; ++$i) {
$columnName = Coordinate::stringFromColumnIndex($deleteColumn + 1);
if (isset($tableColumns[$columnName])) {
$table->clearColumn($columnName);
}
++$deleteColumn;
}
}

private function adjustTableInsert(int $startCol, int $numberOfColumns, int $rangeEnd, Table $table): void
{
$startColRef = $startCol;
$endColRef = $rangeEnd;
$toColRef = $rangeEnd + $numberOfColumns;

do {
$table->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef));
--$endColRef;
--$toColRef;
} while ($startColRef <= $endColRef);
}

private function adjustTableDelete(int $startCol, int $numberOfColumns, int $rangeEnd, Table $table): void
{
// For delete, we shuffle from beginning to end to avoid overwriting
$startColID = Coordinate::stringFromColumnIndex($startCol);
$toColID = Coordinate::stringFromColumnIndex($startCol + $numberOfColumns);
$endColID = Coordinate::stringFromColumnIndex($rangeEnd + 1);

do {
$table->shiftColumn($startColID, $toColID);
++$startColID;
++$toColID;
} while ($startColID !== $endColID);
}

private function duplicateStylesByColumn(Worksheet $worksheet, int $beforeColumn, int $beforeRow, int $highestRow, int $numberOfColumns): void
{
$beforeColumnName = Coordinate::stringFromColumnIndex($beforeColumn - 1);
Expand Down
Loading