Merge pull request #2671 from aswinkumar863/Table-for-Xlsx
Initial implementation of Excel's tables feature
This commit is contained in:
commit
10d175e686
|
|
@ -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
|
||||
|
||||
-
|
||||
|
|
|
|||
|
|
@ -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']);
|
||||
|
|
@ -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();
|
||||
|
|
@ -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();
|
||||
|
|
@ -7,6 +7,7 @@ use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
|||
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
|
||||
|
|
@ -497,6 +498,9 @@ class ReferenceHelper
|
|||
// 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() ?? '';
|
||||
|
|
@ -1026,6 +1030,85 @@ class ReferenceHelper
|
|||
} 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);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,454 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Worksheet;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Table\TableStyle;
|
||||
|
||||
class Table
|
||||
{
|
||||
/**
|
||||
* Table Name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $name = '';
|
||||
|
||||
/**
|
||||
* Show Header Row.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $showHeaderRow = true;
|
||||
|
||||
/**
|
||||
* Show Totals Row.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $showTotalsRow = false;
|
||||
|
||||
/**
|
||||
* Table Range.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $range = '';
|
||||
|
||||
/**
|
||||
* Table Worksheet.
|
||||
*
|
||||
* @var null|Worksheet
|
||||
*/
|
||||
private $workSheet;
|
||||
|
||||
/**
|
||||
* Table Column.
|
||||
*
|
||||
* @var Table\Column[]
|
||||
*/
|
||||
private $columns = [];
|
||||
|
||||
/**
|
||||
* Table Style.
|
||||
*
|
||||
* @var TableStyle
|
||||
*/
|
||||
private $style;
|
||||
|
||||
/**
|
||||
* Create a new Table.
|
||||
*
|
||||
* @param AddressRange|array<int>|string $range
|
||||
* A simple string containing a Cell range like 'A1:E10' is permitted
|
||||
* or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
|
||||
* or an AddressRange object.
|
||||
* @param string $name (e.g. Table1)
|
||||
*/
|
||||
public function __construct($range = '', string $name = '')
|
||||
{
|
||||
$this->setRange($range);
|
||||
$this->setName($name);
|
||||
$this->style = new TableStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Table name.
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Table name.
|
||||
*/
|
||||
public function setName(string $name): self
|
||||
{
|
||||
$name = trim($name);
|
||||
|
||||
if (!empty($name)) {
|
||||
if (strlen($name) === 1 && in_array($name, ['C', 'c', 'R', 'r'])) {
|
||||
throw new PhpSpreadsheetException('The table name is invalid');
|
||||
}
|
||||
if (strlen($name) > 255) {
|
||||
throw new PhpSpreadsheetException('The table name cannot be longer than 255 characters');
|
||||
}
|
||||
// Check for A1 or R1C1 cell reference notation
|
||||
if (
|
||||
preg_match(Coordinate::A1_COORDINATE_REGEX, $name) ||
|
||||
preg_match('/^R\[?\-?[0-9]*\]?C\[?\-?[0-9]*\]?$/i', $name)
|
||||
) {
|
||||
throw new PhpSpreadsheetException('The table name can\'t be the same as a cell reference');
|
||||
}
|
||||
if (!preg_match('/^[\p{L}_\\\\]/iu', $name)) {
|
||||
throw new PhpSpreadsheetException('The table name must begin a name with a letter, an underscore character (_), or a backslash (\)');
|
||||
}
|
||||
if (!preg_match('/^[\p{L}_\\\\][\p{L}\p{M}0-9\._]+$/iu', $name)) {
|
||||
throw new PhpSpreadsheetException('The table name contains invalid characters');
|
||||
}
|
||||
}
|
||||
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get show Header Row.
|
||||
*/
|
||||
public function getShowHeaderRow(): bool
|
||||
{
|
||||
return $this->showHeaderRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set show Header Row.
|
||||
*/
|
||||
public function setShowHeaderRow(bool $showHeaderRow): self
|
||||
{
|
||||
$this->showHeaderRow = $showHeaderRow;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get show Totals Row.
|
||||
*/
|
||||
public function getShowTotalsRow(): bool
|
||||
{
|
||||
return $this->showTotalsRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set show Totals Row.
|
||||
*/
|
||||
public function setShowTotalsRow(bool $showTotalsRow): self
|
||||
{
|
||||
$this->showTotalsRow = $showTotalsRow;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Table Range.
|
||||
*/
|
||||
public function getRange(): string
|
||||
{
|
||||
return $this->range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Table Cell Range.
|
||||
*
|
||||
* @param AddressRange|array<int>|string $range
|
||||
* A simple string containing a Cell range like 'A1:E10' is permitted
|
||||
* or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
|
||||
* or an AddressRange object.
|
||||
*/
|
||||
public function setRange($range = ''): self
|
||||
{
|
||||
// extract coordinate
|
||||
if ($range !== '') {
|
||||
[, $range] = Worksheet::extractSheetTitle(Validations::validateCellRange($range), true);
|
||||
}
|
||||
if (empty($range)) {
|
||||
// Discard all column rules
|
||||
$this->columns = [];
|
||||
$this->range = '';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (strpos($range, ':') === false) {
|
||||
throw new PhpSpreadsheetException('Table must be set on a range of cells.');
|
||||
}
|
||||
|
||||
[$width, $height] = Coordinate::rangeDimension($range);
|
||||
if ($width < 1 || $height < 2) {
|
||||
throw new PhpSpreadsheetException('The table range must be at least 1 column and 2 rows');
|
||||
}
|
||||
|
||||
$this->range = $range;
|
||||
// Discard any column ruless that are no longer valid within this range
|
||||
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
|
||||
foreach ($this->columns as $key => $value) {
|
||||
$colIndex = Coordinate::columnIndexFromString($key);
|
||||
if (($rangeStart[0] > $colIndex) || ($rangeEnd[0] < $colIndex)) {
|
||||
unset($this->columns[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Table Cell Range to max row.
|
||||
*/
|
||||
public function setRangeToMaxRow(): self
|
||||
{
|
||||
if ($this->workSheet !== null) {
|
||||
$thisrange = $this->range;
|
||||
$range = preg_replace('/\\d+$/', (string) $this->workSheet->getHighestRow(), $thisrange) ?? '';
|
||||
if ($range !== $thisrange) {
|
||||
$this->setRange($range);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Table's Worksheet.
|
||||
*/
|
||||
public function getWorksheet(): ?Worksheet
|
||||
{
|
||||
return $this->workSheet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Table's Worksheet.
|
||||
*/
|
||||
public function setWorksheet(?Worksheet $worksheet = null): self
|
||||
{
|
||||
if ($this->name !== '' && $worksheet !== null) {
|
||||
$spreadsheet = $worksheet->getParent();
|
||||
$tableName = StringHelper::strToUpper($this->name);
|
||||
|
||||
foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
|
||||
foreach ($sheet->getTableCollection() as $table) {
|
||||
if (StringHelper::strToUpper($table->getName()) === $tableName) {
|
||||
throw new PhpSpreadsheetException("Workbook already contains a table named '{$this->name}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->workSheet = $worksheet;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all Table Columns.
|
||||
*
|
||||
* @return Table\Column[]
|
||||
*/
|
||||
public function getColumns(): array
|
||||
{
|
||||
return $this->columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the specified column is in the Table range.
|
||||
*
|
||||
* @param string $column Column name (e.g. A)
|
||||
*
|
||||
* @return int The column offset within the table range
|
||||
*/
|
||||
public function isColumnInRange(string $column): int
|
||||
{
|
||||
if (empty($this->range)) {
|
||||
throw new PhpSpreadsheetException('No table range is defined.');
|
||||
}
|
||||
|
||||
$columnIndex = Coordinate::columnIndexFromString($column);
|
||||
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
|
||||
if (($rangeStart[0] > $columnIndex) || ($rangeEnd[0] < $columnIndex)) {
|
||||
throw new PhpSpreadsheetException('Column is outside of current table range.');
|
||||
}
|
||||
|
||||
return $columnIndex - $rangeStart[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specified Table Column Offset within the defined Table range.
|
||||
*
|
||||
* @param string $column Column name (e.g. A)
|
||||
*
|
||||
* @return int The offset of the specified column within the table range
|
||||
*/
|
||||
public function getColumnOffset($column): int
|
||||
{
|
||||
return $this->isColumnInRange($column);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specified Table Column.
|
||||
*
|
||||
* @param string $column Column name (e.g. A)
|
||||
*/
|
||||
public function getColumn($column): Table\Column
|
||||
{
|
||||
$this->isColumnInRange($column);
|
||||
|
||||
if (!isset($this->columns[$column])) {
|
||||
$this->columns[$column] = new Table\Column($column, $this);
|
||||
}
|
||||
|
||||
return $this->columns[$column];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specified Table Column by it's offset.
|
||||
*
|
||||
* @param int $columnOffset Column offset within range (starting from 0)
|
||||
*/
|
||||
public function getColumnByOffset($columnOffset): Table\Column
|
||||
{
|
||||
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
|
||||
$pColumn = Coordinate::stringFromColumnIndex($rangeStart[0] + $columnOffset);
|
||||
|
||||
return $this->getColumn($pColumn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Table.
|
||||
*
|
||||
* @param string|Table\Column $columnObjectOrString
|
||||
* A simple string containing a Column ID like 'A' is permitted
|
||||
*/
|
||||
public function setColumn($columnObjectOrString): self
|
||||
{
|
||||
if ((is_string($columnObjectOrString)) && (!empty($columnObjectOrString))) {
|
||||
$column = $columnObjectOrString;
|
||||
} elseif (is_object($columnObjectOrString) && ($columnObjectOrString instanceof Table\Column)) {
|
||||
$column = $columnObjectOrString->getColumnIndex();
|
||||
} else {
|
||||
throw new PhpSpreadsheetException('Column is not within the table range.');
|
||||
}
|
||||
$this->isColumnInRange($column);
|
||||
|
||||
if (is_string($columnObjectOrString)) {
|
||||
$this->columns[$columnObjectOrString] = new Table\Column($columnObjectOrString, $this);
|
||||
} else {
|
||||
$columnObjectOrString->setTable($this);
|
||||
$this->columns[$column] = $columnObjectOrString;
|
||||
}
|
||||
ksort($this->columns);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear a specified Table Column.
|
||||
*
|
||||
* @param string $column Column name (e.g. A)
|
||||
*/
|
||||
public function clearColumn($column): self
|
||||
{
|
||||
$this->isColumnInRange($column);
|
||||
|
||||
if (isset($this->columns[$column])) {
|
||||
unset($this->columns[$column]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift an Table Column Rule to a different column.
|
||||
*
|
||||
* Note: This method bypasses validation of the destination column to ensure it is within this Table range.
|
||||
* Nor does it verify whether any column rule already exists at $toColumn, but will simply override any existing value.
|
||||
* Use with caution.
|
||||
*
|
||||
* @param string $fromColumn Column name (e.g. A)
|
||||
* @param string $toColumn Column name (e.g. B)
|
||||
*/
|
||||
public function shiftColumn($fromColumn, $toColumn): self
|
||||
{
|
||||
$fromColumn = strtoupper($fromColumn);
|
||||
$toColumn = strtoupper($toColumn);
|
||||
|
||||
if (($fromColumn !== null) && (isset($this->columns[$fromColumn])) && ($toColumn !== null)) {
|
||||
$this->columns[$fromColumn]->setTable();
|
||||
$this->columns[$fromColumn]->setColumnIndex($toColumn);
|
||||
$this->columns[$toColumn] = $this->columns[$fromColumn];
|
||||
$this->columns[$toColumn]->setTable($this);
|
||||
unset($this->columns[$fromColumn]);
|
||||
|
||||
ksort($this->columns);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get table Style.
|
||||
*/
|
||||
public function getStyle(): Table\TableStyle
|
||||
{
|
||||
return $this->style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set table Style.
|
||||
*/
|
||||
public function setStyle(TableStyle $style): self
|
||||
{
|
||||
$this->style = $style;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement PHP __clone to create a deep clone, not just a shallow copy.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$vars = get_object_vars($this);
|
||||
foreach ($vars as $key => $value) {
|
||||
if (is_object($value)) {
|
||||
if ($key === 'workSheet') {
|
||||
// Detach from worksheet
|
||||
$this->{$key} = null;
|
||||
} else {
|
||||
$this->{$key} = clone $value;
|
||||
}
|
||||
} elseif ((is_array($value)) && ($key === 'columns')) {
|
||||
// The columns array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\Table objects
|
||||
$this->{$key} = [];
|
||||
foreach ($value as $k => $v) {
|
||||
$this->{$key}[$k] = clone $v;
|
||||
// attach the new cloned Column to this new cloned Table object
|
||||
$this->{$key}[$k]->setTable($this);
|
||||
}
|
||||
} else {
|
||||
$this->{$key} = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* toString method replicates previous behavior by returning the range if object is
|
||||
* referenced as a property of its worksheet.
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return (string) $this->range;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Worksheet\Table;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
|
||||
|
||||
class Column
|
||||
{
|
||||
/**
|
||||
* Table Column Index.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $columnIndex = '';
|
||||
|
||||
/**
|
||||
* Show Filter Button.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $showFilterButton = true;
|
||||
|
||||
/**
|
||||
* Total Row Label.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $totalsRowLabel;
|
||||
|
||||
/**
|
||||
* Total Row Function.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $totalsRowFunction;
|
||||
|
||||
/**
|
||||
* Total Row Formula.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $totalsRowFormula;
|
||||
|
||||
/**
|
||||
* Column Formula.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $columnFormula;
|
||||
|
||||
/**
|
||||
* Table.
|
||||
*
|
||||
* @var null|Table
|
||||
*/
|
||||
private $table;
|
||||
|
||||
/**
|
||||
* Create a new Column.
|
||||
*
|
||||
* @param string $column Column (e.g. A)
|
||||
* @param Table $table Table for this column
|
||||
*/
|
||||
public function __construct($column, ?Table $table = null)
|
||||
{
|
||||
$this->columnIndex = $column;
|
||||
$this->table = $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Table column index as string eg: 'A'.
|
||||
*/
|
||||
public function getColumnIndex(): string
|
||||
{
|
||||
return $this->columnIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Table column index as string eg: 'A'.
|
||||
*
|
||||
* @param string $column Column (e.g. A)
|
||||
*/
|
||||
public function setColumnIndex($column): self
|
||||
{
|
||||
// Uppercase coordinate
|
||||
$column = strtoupper($column);
|
||||
if ($this->table !== null) {
|
||||
$this->table->isColumnInRange($column);
|
||||
}
|
||||
|
||||
$this->columnIndex = $column;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get show Filter Button.
|
||||
*/
|
||||
public function getShowFilterButton(): bool
|
||||
{
|
||||
return $this->showFilterButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set show Filter Button.
|
||||
*/
|
||||
public function setShowFilterButton(bool $showFilterButton): self
|
||||
{
|
||||
$this->showFilterButton = $showFilterButton;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total Row Label.
|
||||
*/
|
||||
public function getTotalsRowLabel(): ?string
|
||||
{
|
||||
return $this->totalsRowLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set total Row Label.
|
||||
*/
|
||||
public function setTotalsRowLabel(string $totalsRowLabel): self
|
||||
{
|
||||
$this->totalsRowLabel = $totalsRowLabel;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total Row Function.
|
||||
*/
|
||||
public function getTotalsRowFunction(): ?string
|
||||
{
|
||||
return $this->totalsRowFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set total Row Function.
|
||||
*/
|
||||
public function setTotalsRowFunction(string $totalsRowFunction): self
|
||||
{
|
||||
$this->totalsRowFunction = $totalsRowFunction;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total Row Formula.
|
||||
*/
|
||||
public function getTotalsRowFormula(): ?string
|
||||
{
|
||||
return $this->totalsRowFormula;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set total Row Formula.
|
||||
*/
|
||||
public function setTotalsRowFormula(string $totalsRowFormula): self
|
||||
{
|
||||
$this->totalsRowFormula = $totalsRowFormula;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get column Formula.
|
||||
*/
|
||||
public function getColumnFormula(): ?string
|
||||
{
|
||||
return $this->columnFormula;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set column Formula.
|
||||
*/
|
||||
public function setColumnFormula(string $columnFormula): self
|
||||
{
|
||||
$this->columnFormula = $columnFormula;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this Column's Table.
|
||||
*/
|
||||
public function getTable(): ?Table
|
||||
{
|
||||
return $this->table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this Column's Table.
|
||||
*/
|
||||
public function setTable(?Table $table = null): self
|
||||
{
|
||||
$this->table = $table;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Worksheet\Table;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
|
||||
|
||||
class TableStyle
|
||||
{
|
||||
const TABLE_STYLE_NONE = '';
|
||||
const TABLE_STYLE_LIGHT1 = 'TableStyleLight1';
|
||||
const TABLE_STYLE_LIGHT2 = 'TableStyleLight2';
|
||||
const TABLE_STYLE_LIGHT3 = 'TableStyleLight3';
|
||||
const TABLE_STYLE_LIGHT4 = 'TableStyleLight4';
|
||||
const TABLE_STYLE_LIGHT5 = 'TableStyleLight5';
|
||||
const TABLE_STYLE_LIGHT6 = 'TableStyleLight6';
|
||||
const TABLE_STYLE_LIGHT7 = 'TableStyleLight7';
|
||||
const TABLE_STYLE_LIGHT8 = 'TableStyleLight8';
|
||||
const TABLE_STYLE_LIGHT9 = 'TableStyleLight9';
|
||||
const TABLE_STYLE_LIGHT10 = 'TableStyleLight10';
|
||||
const TABLE_STYLE_LIGHT11 = 'TableStyleLight11';
|
||||
const TABLE_STYLE_LIGHT12 = 'TableStyleLight12';
|
||||
const TABLE_STYLE_LIGHT13 = 'TableStyleLight13';
|
||||
const TABLE_STYLE_LIGHT14 = 'TableStyleLight14';
|
||||
const TABLE_STYLE_LIGHT15 = 'TableStyleLight15';
|
||||
const TABLE_STYLE_LIGHT16 = 'TableStyleLight16';
|
||||
const TABLE_STYLE_LIGHT17 = 'TableStyleLight17';
|
||||
const TABLE_STYLE_LIGHT18 = 'TableStyleLight18';
|
||||
const TABLE_STYLE_LIGHT19 = 'TableStyleLight19';
|
||||
const TABLE_STYLE_LIGHT20 = 'TableStyleLight20';
|
||||
const TABLE_STYLE_LIGHT21 = 'TableStyleLight21';
|
||||
const TABLE_STYLE_MEDIUM1 = 'TableStyleMedium1';
|
||||
const TABLE_STYLE_MEDIUM2 = 'TableStyleMedium2';
|
||||
const TABLE_STYLE_MEDIUM3 = 'TableStyleMedium3';
|
||||
const TABLE_STYLE_MEDIUM4 = 'TableStyleMedium4';
|
||||
const TABLE_STYLE_MEDIUM5 = 'TableStyleMedium5';
|
||||
const TABLE_STYLE_MEDIUM6 = 'TableStyleMedium6';
|
||||
const TABLE_STYLE_MEDIUM7 = 'TableStyleMedium7';
|
||||
const TABLE_STYLE_MEDIUM8 = 'TableStyleMedium8';
|
||||
const TABLE_STYLE_MEDIUM9 = 'TableStyleMedium9';
|
||||
const TABLE_STYLE_MEDIUM10 = 'TableStyleMedium10';
|
||||
const TABLE_STYLE_MEDIUM11 = 'TableStyleMedium11';
|
||||
const TABLE_STYLE_MEDIUM12 = 'TableStyleMedium12';
|
||||
const TABLE_STYLE_MEDIUM13 = 'TableStyleMedium13';
|
||||
const TABLE_STYLE_MEDIUM14 = 'TableStyleMedium14';
|
||||
const TABLE_STYLE_MEDIUM15 = 'TableStyleMedium15';
|
||||
const TABLE_STYLE_MEDIUM16 = 'TableStyleMedium16';
|
||||
const TABLE_STYLE_MEDIUM17 = 'TableStyleMedium17';
|
||||
const TABLE_STYLE_MEDIUM18 = 'TableStyleMedium18';
|
||||
const TABLE_STYLE_MEDIUM19 = 'TableStyleMedium19';
|
||||
const TABLE_STYLE_MEDIUM20 = 'TableStyleMedium20';
|
||||
const TABLE_STYLE_MEDIUM21 = 'TableStyleMedium21';
|
||||
const TABLE_STYLE_MEDIUM22 = 'TableStyleMedium22';
|
||||
const TABLE_STYLE_MEDIUM23 = 'TableStyleMedium23';
|
||||
const TABLE_STYLE_MEDIUM24 = 'TableStyleMedium24';
|
||||
const TABLE_STYLE_MEDIUM25 = 'TableStyleMedium25';
|
||||
const TABLE_STYLE_MEDIUM26 = 'TableStyleMedium26';
|
||||
const TABLE_STYLE_MEDIUM27 = 'TableStyleMedium27';
|
||||
const TABLE_STYLE_MEDIUM28 = 'TableStyleMedium28';
|
||||
const TABLE_STYLE_DARK1 = 'TableStyleDark1';
|
||||
const TABLE_STYLE_DARK2 = 'TableStyleDark2';
|
||||
const TABLE_STYLE_DARK3 = 'TableStyleDark3';
|
||||
const TABLE_STYLE_DARK4 = 'TableStyleDark4';
|
||||
const TABLE_STYLE_DARK5 = 'TableStyleDark5';
|
||||
const TABLE_STYLE_DARK6 = 'TableStyleDark6';
|
||||
const TABLE_STYLE_DARK7 = 'TableStyleDark7';
|
||||
const TABLE_STYLE_DARK8 = 'TableStyleDark8';
|
||||
const TABLE_STYLE_DARK9 = 'TableStyleDark9';
|
||||
const TABLE_STYLE_DARK10 = 'TableStyleDark10';
|
||||
const TABLE_STYLE_DARK11 = 'TableStyleDark11';
|
||||
|
||||
/**
|
||||
* Theme.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $theme;
|
||||
|
||||
/**
|
||||
* Show First Column.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $showFirstColumn = false;
|
||||
|
||||
/**
|
||||
* Show Last Column.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $showLastColumn = false;
|
||||
|
||||
/**
|
||||
* Show Row Stripes.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $showRowStripes = false;
|
||||
|
||||
/**
|
||||
* Show Column Stripes.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $showColumnStripes = false;
|
||||
|
||||
/**
|
||||
* Table.
|
||||
*
|
||||
* @var null|Table
|
||||
*/
|
||||
private $table;
|
||||
|
||||
/**
|
||||
* Create a new Table Style.
|
||||
*
|
||||
* @param string $theme (e.g. TableStyle::TABLE_STYLE_MEDIUM2)
|
||||
*/
|
||||
public function __construct(string $theme = self::TABLE_STYLE_MEDIUM2)
|
||||
{
|
||||
$this->theme = $theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get theme.
|
||||
*/
|
||||
public function getTheme(): string
|
||||
{
|
||||
return $this->theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set theme.
|
||||
*/
|
||||
public function setTheme(string $theme): self
|
||||
{
|
||||
$this->theme = $theme;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get show First Column.
|
||||
*/
|
||||
public function getShowFirstColumn(): bool
|
||||
{
|
||||
return $this->showFirstColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set show First Column.
|
||||
*/
|
||||
public function setShowFirstColumn(bool $showFirstColumn): self
|
||||
{
|
||||
$this->showFirstColumn = $showFirstColumn;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get show Last Column.
|
||||
*/
|
||||
public function getShowLastColumn(): bool
|
||||
{
|
||||
return $this->showLastColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set show Last Column.
|
||||
*/
|
||||
public function setShowLastColumn(bool $showLastColumn): self
|
||||
{
|
||||
$this->showLastColumn = $showLastColumn;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get show Row Stripes.
|
||||
*/
|
||||
public function getShowRowStripes(): bool
|
||||
{
|
||||
return $this->showRowStripes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set show Row Stripes.
|
||||
*/
|
||||
public function setShowRowStripes(bool $showRowStripes): self
|
||||
{
|
||||
$this->showRowStripes = $showRowStripes;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get show Column Stripes.
|
||||
*/
|
||||
public function getShowColumnStripes(): bool
|
||||
{
|
||||
return $this->showColumnStripes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set show Column Stripes.
|
||||
*/
|
||||
public function setShowColumnStripes(bool $showColumnStripes): self
|
||||
{
|
||||
$this->showColumnStripes = $showColumnStripes;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this Style's Table.
|
||||
*/
|
||||
public function getTable(): ?Table
|
||||
{
|
||||
return $this->table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this Style's Table.
|
||||
*/
|
||||
public function setTable(?Table $table = null): self
|
||||
{
|
||||
$this->table = $table;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -111,6 +111,13 @@ class Worksheet implements IComparable
|
|||
*/
|
||||
private $chartCollection;
|
||||
|
||||
/**
|
||||
* Collection of Table objects.
|
||||
*
|
||||
* @var ArrayObject<int, Table>
|
||||
*/
|
||||
private $tableCollection;
|
||||
|
||||
/**
|
||||
* Worksheet title.
|
||||
*
|
||||
|
|
@ -375,7 +382,10 @@ class Worksheet implements IComparable
|
|||
$this->defaultRowDimension = new RowDimension(null);
|
||||
// Default column dimension
|
||||
$this->defaultColumnDimension = new ColumnDimension(null);
|
||||
// AutoFilter
|
||||
$this->autoFilter = new AutoFilter('', $this);
|
||||
// Table collection
|
||||
$this->tableCollection = new ArrayObject();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2095,6 +2105,58 @@ class Worksheet implements IComparable
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get collection of Tables.
|
||||
*
|
||||
* @return ArrayObject<int, Table>
|
||||
*/
|
||||
public function getTableCollection()
|
||||
{
|
||||
return $this->tableCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Table.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addTable(Table $table): self
|
||||
{
|
||||
$table->setWorksheet($this);
|
||||
$this->tableCollection[] = $table;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove Table by name.
|
||||
*
|
||||
* @param string $name Table name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeTableByName(string $name): self
|
||||
{
|
||||
$name = Shared\StringHelper::strToUpper($name);
|
||||
foreach ($this->tableCollection as $key => $table) {
|
||||
if (Shared\StringHelper::strToUpper($table->getName()) === $name) {
|
||||
unset($this->tableCollection[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove collection of Tables.
|
||||
*/
|
||||
public function removeTableCollection(): self
|
||||
{
|
||||
$this->tableCollection = new ArrayObject();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Freeze Pane.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ use PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsRibbon;
|
|||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsVBA;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\StringTable;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Style;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Table;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Theme;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Workbook;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet;
|
||||
|
|
@ -167,6 +168,11 @@ class Xlsx extends BaseWriter
|
|||
*/
|
||||
private $writerPartTheme;
|
||||
|
||||
/**
|
||||
* @var Table
|
||||
*/
|
||||
private $writerPartTable;
|
||||
|
||||
/**
|
||||
* @var Workbook
|
||||
*/
|
||||
|
|
@ -196,6 +202,7 @@ class Xlsx extends BaseWriter
|
|||
$this->writerPartStringTable = new StringTable($this);
|
||||
$this->writerPartStyle = new Style($this);
|
||||
$this->writerPartTheme = new Theme($this);
|
||||
$this->writerPartTable = new Table($this);
|
||||
$this->writerPartWorkbook = new Workbook($this);
|
||||
$this->writerPartWorksheet = new Worksheet($this);
|
||||
|
||||
|
|
@ -271,6 +278,11 @@ class Xlsx extends BaseWriter
|
|||
return $this->writerPartTheme;
|
||||
}
|
||||
|
||||
public function getWriterPartTable(): Table
|
||||
{
|
||||
return $this->writerPartTable;
|
||||
}
|
||||
|
||||
public function getWriterPartWorkbook(): Workbook
|
||||
{
|
||||
return $this->writerPartWorkbook;
|
||||
|
|
@ -389,10 +401,11 @@ class Xlsx extends BaseWriter
|
|||
}
|
||||
|
||||
$chartRef1 = 0;
|
||||
$tableRef1 = 1;
|
||||
// Add worksheet relationships (drawings, ...)
|
||||
for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) {
|
||||
// Add relationships
|
||||
$zipContent['xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels'] = $this->getWriterPartRels()->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts);
|
||||
$zipContent['xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels'] = $this->getWriterPartRels()->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts, $tableRef1);
|
||||
|
||||
// Add unparsedLoadedData
|
||||
$sheetCodeName = $this->spreadSheet->getSheet($i)->getCodeName();
|
||||
|
|
@ -478,6 +491,12 @@ class Xlsx extends BaseWriter
|
|||
$zipContent['xl/media/' . $image->getIndexedFilename()] = file_get_contents($image->getPath());
|
||||
}
|
||||
}
|
||||
|
||||
// Add Table parts
|
||||
$tables = $this->spreadSheet->getSheet($i)->getTableCollection();
|
||||
foreach ($tables as $table) {
|
||||
$zipContent['xl/tables/table' . $tableRef1 . '.xml'] = $this->getWriterPartTable()->writeTable($table, $tableRef1++);
|
||||
}
|
||||
}
|
||||
|
||||
// Add media
|
||||
|
|
|
|||
|
|
@ -85,6 +85,16 @@ class ContentTypes extends WriterPart
|
|||
// Shared strings
|
||||
$this->writeOverrideContentType($objWriter, '/xl/sharedStrings.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml');
|
||||
|
||||
// Table
|
||||
$table = 1;
|
||||
for ($i = 0; $i < $sheetCount; ++$i) {
|
||||
$tableCount = $spreadsheet->getSheet($i)->getTableCollection()->count();
|
||||
|
||||
for ($t = 1; $t <= $tableCount; ++$t) {
|
||||
$this->writeOverrideContentType($objWriter, '/xl/tables/table' . $table++ . '.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml');
|
||||
}
|
||||
}
|
||||
|
||||
// Add worksheet relationship content types
|
||||
$unparsedLoadedData = $spreadsheet->getUnparsedLoadedData();
|
||||
$chart = 1;
|
||||
|
|
|
|||
|
|
@ -163,10 +163,11 @@ class Rels extends WriterPart
|
|||
*
|
||||
* @param int $worksheetId
|
||||
* @param bool $includeCharts Flag indicating if we should write charts
|
||||
* @param int $tableRef Table ID
|
||||
*
|
||||
* @return string XML Output
|
||||
*/
|
||||
public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, $worksheetId = 1, $includeCharts = false)
|
||||
public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, $worksheetId = 1, $includeCharts = false, $tableRef = 1)
|
||||
{
|
||||
// Create XML writer
|
||||
$objWriter = null;
|
||||
|
|
@ -252,6 +253,17 @@ class Rels extends WriterPart
|
|||
);
|
||||
}
|
||||
|
||||
// Write Table
|
||||
$tableCount = $worksheet->getTableCollection()->count();
|
||||
for ($i = 1; $i <= $tableCount; ++$i) {
|
||||
$this->writeRelationship(
|
||||
$objWriter,
|
||||
'_table_' . $i,
|
||||
'http://schemas.openxmlformats.org/officeDocument/2006/relationships/table',
|
||||
'../tables/table' . $tableRef++ . '.xml'
|
||||
);
|
||||
}
|
||||
|
||||
// Write header/footer relationship?
|
||||
$i = 1;
|
||||
if (count($worksheet->getHeaderFooter()->getImages()) > 0) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Table as WorksheetTable;
|
||||
|
||||
class Table extends WriterPart
|
||||
{
|
||||
/**
|
||||
* Write Table to XML format.
|
||||
*
|
||||
* @param int $tableRef Table ID
|
||||
*
|
||||
* @return string XML Output
|
||||
*/
|
||||
public function writeTable(WorksheetTable $table, $tableRef): string
|
||||
{
|
||||
// Create XML writer
|
||||
$objWriter = null;
|
||||
if ($this->getParentWriter()->getUseDiskCaching()) {
|
||||
$objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
|
||||
} else {
|
||||
$objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
|
||||
}
|
||||
|
||||
// XML header
|
||||
$objWriter->startDocument('1.0', 'UTF-8', 'yes');
|
||||
|
||||
// Table
|
||||
$name = 'Table' . $tableRef;
|
||||
$range = $table->getRange();
|
||||
|
||||
$objWriter->startElement('table');
|
||||
$objWriter->writeAttribute('xml:space', 'preserve');
|
||||
$objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
|
||||
$objWriter->writeAttribute('id', (string) $tableRef);
|
||||
$objWriter->writeAttribute('name', $name);
|
||||
$objWriter->writeAttribute('displayName', $table->getName() ?: $name);
|
||||
$objWriter->writeAttribute('ref', $range);
|
||||
$objWriter->writeAttribute('headerRowCount', $table->getShowHeaderRow() ? '1' : '0');
|
||||
$objWriter->writeAttribute('totalsRowCount', $table->getShowTotalsRow() ? '1' : '0');
|
||||
|
||||
// Table Boundaries
|
||||
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($table->getRange());
|
||||
|
||||
// Table Auto Filter
|
||||
if ($table->getShowHeaderRow()) {
|
||||
$objWriter->startElement('autoFilter');
|
||||
$objWriter->writeAttribute('ref', $range);
|
||||
foreach (range($rangeStart[0], $rangeEnd[0]) as $offset => $columnIndex) {
|
||||
$column = $table->getColumnByOffset($offset);
|
||||
|
||||
if (!$column->getShowFilterButton()) {
|
||||
$objWriter->startElement('filterColumn');
|
||||
$objWriter->writeAttribute('colId', (string) $offset);
|
||||
$objWriter->writeAttribute('hiddenButton', '1');
|
||||
$objWriter->endElement();
|
||||
}
|
||||
}
|
||||
$objWriter->endElement();
|
||||
}
|
||||
|
||||
// Table Columns
|
||||
$objWriter->startElement('tableColumns');
|
||||
$objWriter->writeAttribute('count', (string) ($rangeEnd[0] - $rangeStart[0] + 1));
|
||||
foreach (range($rangeStart[0], $rangeEnd[0]) as $offset => $columnIndex) {
|
||||
$worksheet = $table->getWorksheet();
|
||||
if (!$worksheet) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$column = $table->getColumnByOffset($offset);
|
||||
$cell = $worksheet->getCellByColumnAndRow($columnIndex, $rangeStart[1]);
|
||||
|
||||
$objWriter->startElement('tableColumn');
|
||||
$objWriter->writeAttribute('id', (string) ($offset + 1));
|
||||
$objWriter->writeAttribute('name', $table->getShowHeaderRow() ? $cell->getValue() : 'Column' . ($offset + 1));
|
||||
|
||||
if ($table->getShowTotalsRow()) {
|
||||
if ($column->getTotalsRowLabel()) {
|
||||
$objWriter->writeAttribute('totalsRowLabel', $column->getTotalsRowLabel());
|
||||
}
|
||||
if ($column->getTotalsRowFunction()) {
|
||||
$objWriter->writeAttribute('totalsRowFunction', $column->getTotalsRowFunction());
|
||||
}
|
||||
}
|
||||
if ($column->getColumnFormula()) {
|
||||
$objWriter->writeElement('calculatedColumnFormula', $column->getColumnFormula());
|
||||
}
|
||||
|
||||
$objWriter->endElement();
|
||||
}
|
||||
$objWriter->endElement();
|
||||
|
||||
// Table Styles
|
||||
$objWriter->startElement('tableStyleInfo');
|
||||
$objWriter->writeAttribute('name', $table->getStyle()->getTheme());
|
||||
$objWriter->writeAttribute('showFirstColumn', $table->getStyle()->getShowFirstColumn() ? '1' : '0');
|
||||
$objWriter->writeAttribute('showLastColumn', $table->getStyle()->getShowLastColumn() ? '1' : '0');
|
||||
$objWriter->writeAttribute('showRowStripes', $table->getStyle()->getShowRowStripes() ? '1' : '0');
|
||||
$objWriter->writeAttribute('showColumnStripes', $table->getStyle()->getShowColumnStripes() ? '1' : '0');
|
||||
$objWriter->endElement();
|
||||
|
||||
$objWriter->endElement();
|
||||
|
||||
// Return
|
||||
return $objWriter->getData();
|
||||
}
|
||||
}
|
||||
|
|
@ -120,6 +120,9 @@ class Worksheet extends WriterPart
|
|||
// AlternateContent
|
||||
$this->writeAlternateContent($objWriter, $worksheet);
|
||||
|
||||
// Table
|
||||
$this->writeTable($objWriter, $worksheet);
|
||||
|
||||
// ConditionalFormattingRuleExtensionList
|
||||
// (Must be inserted last. Not insert last, an Excel parse error will occur)
|
||||
$this->writeExtLst($objWriter, $worksheet);
|
||||
|
|
@ -993,6 +996,25 @@ class Worksheet extends WriterPart
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write Table.
|
||||
*/
|
||||
private function writeTable(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
|
||||
{
|
||||
$tableCount = $worksheet->getTableCollection()->count();
|
||||
|
||||
$objWriter->startElement('tableParts');
|
||||
$objWriter->writeAttribute('count', (string) $tableCount);
|
||||
|
||||
for ($t = 1; $t <= $tableCount; ++$t) {
|
||||
$objWriter->startElement('tablePart');
|
||||
$objWriter->writeAttribute('r:id', 'rId_table_' . $t);
|
||||
$objWriter->endElement();
|
||||
}
|
||||
|
||||
$objWriter->endElement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write PageSetup.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Worksheet\Table;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Table\Column;
|
||||
|
||||
class ColumnTest extends SetupTeardown
|
||||
{
|
||||
protected function initTable(): Table
|
||||
{
|
||||
$sheet = $this->getSheet();
|
||||
$sheet->getCell('G1')->setValue('Heading');
|
||||
$sheet->getCell('G2')->setValue(2);
|
||||
$sheet->getCell('G3')->setValue(3);
|
||||
$sheet->getCell('G4')->setValue(4);
|
||||
$sheet->getCell('H1')->setValue('Heading2');
|
||||
$sheet->getCell('H2')->setValue(1);
|
||||
$sheet->getCell('H3')->setValue(2);
|
||||
$sheet->getCell('H4')->setValue(3);
|
||||
$this->maxRow = $maxRow = 4;
|
||||
$table = new Table();
|
||||
$table->setRange("G1:H$maxRow");
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
public function testVariousGets(): void
|
||||
{
|
||||
$table = $this->initTable();
|
||||
$column = $table->getColumn('H');
|
||||
$result = $column->getColumnIndex();
|
||||
self::assertEquals('H', $result);
|
||||
}
|
||||
|
||||
public function testGetBadColumnIndex(): void
|
||||
{
|
||||
$this->expectException(PhpSpreadsheetException::class);
|
||||
$this->expectExceptionMessage('Column is outside of current table range.');
|
||||
$table = $this->initTable();
|
||||
$table->getColumn('B');
|
||||
}
|
||||
|
||||
public function testSetColumnIndex(): void
|
||||
{
|
||||
$table = $this->initTable();
|
||||
$column = $table->getColumn('H');
|
||||
$column->setShowFilterButton(false);
|
||||
$expectedResult = 'G';
|
||||
|
||||
$result = $column->setColumnIndex($expectedResult);
|
||||
self::assertInstanceOf(Column::class, $result);
|
||||
|
||||
$result = $result->getColumnIndex();
|
||||
self::assertEquals($expectedResult, $result);
|
||||
}
|
||||
|
||||
public function testVariousSets(): void
|
||||
{
|
||||
$table = $this->initTable();
|
||||
$column = $table->getColumn('H');
|
||||
|
||||
$result = $column->setShowFilterButton(false);
|
||||
self::assertInstanceOf(Column::class, $result);
|
||||
self::assertFalse($column->getShowFilterButton());
|
||||
|
||||
$label = 'Total';
|
||||
$result = $column->setTotalsRowLabel($label);
|
||||
self::assertInstanceOf(Column::class, $result);
|
||||
self::assertEquals($label, $column->getTotalsRowLabel());
|
||||
|
||||
$function = 'sum';
|
||||
$result = $column->setTotalsRowFunction($function);
|
||||
self::assertInstanceOf(Column::class, $result);
|
||||
self::assertEquals($function, $column->getTotalsRowFunction());
|
||||
|
||||
$formula = '=SUM(Sales_Data[[#This Row],[Q1]:[Q4]])';
|
||||
$result = $column->setColumnFormula($formula);
|
||||
self::assertInstanceOf(Column::class, $result);
|
||||
self::assertEquals($formula, $column->getColumnFormula());
|
||||
}
|
||||
|
||||
public function testTable(): void
|
||||
{
|
||||
$table = $this->initTable();
|
||||
$column = new Column('H');
|
||||
$column->setTable($table);
|
||||
self::assertEquals($table, $column->getTable());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Worksheet\Table;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
|
||||
|
||||
class RemoveTableTest extends SetupTeardown
|
||||
{
|
||||
private const INITIAL_RANGE = 'H2:O256';
|
||||
|
||||
public function testRemoveTable(): void
|
||||
{
|
||||
$sheet = $this->getSheet();
|
||||
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
$table->setName('Table1');
|
||||
$sheet->addTable($table);
|
||||
|
||||
self::assertEquals(1, $sheet->getTableCollection()->count());
|
||||
|
||||
$sheet->removeTableByName('table1'); // case insensitive
|
||||
self::assertEquals(0, $sheet->getTableCollection()->count());
|
||||
}
|
||||
|
||||
public function testRemoveCollection(): void
|
||||
{
|
||||
$sheet = $this->getSheet();
|
||||
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
$table->setName('Table1');
|
||||
$sheet->addTable($table);
|
||||
|
||||
self::assertEquals(1, $sheet->getTableCollection()->count());
|
||||
|
||||
$sheet->removeTableCollection();
|
||||
self::assertEquals(0, $sheet->getTableCollection()->count());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Worksheet\Table;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class SetupTeardown extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var ?Spreadsheet
|
||||
*/
|
||||
private $spreadsheet;
|
||||
|
||||
/**
|
||||
* @var ?Worksheet
|
||||
*/
|
||||
private $sheet;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $maxRow = 4;
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
$this->sheet = null;
|
||||
if ($this->spreadsheet !== null) {
|
||||
$this->spreadsheet->disconnectWorksheets();
|
||||
$this->spreadsheet = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getSpreadsheet(): Spreadsheet
|
||||
{
|
||||
if ($this->spreadsheet !== null) {
|
||||
return $this->spreadsheet;
|
||||
}
|
||||
$this->spreadsheet = new Spreadsheet();
|
||||
|
||||
return $this->spreadsheet;
|
||||
}
|
||||
|
||||
protected function getSheet(): Worksheet
|
||||
{
|
||||
if ($this->sheet !== null) {
|
||||
return $this->sheet;
|
||||
}
|
||||
$this->sheet = $this->getSpreadsheet()->getActiveSheet();
|
||||
|
||||
return $this->sheet;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Worksheet\Table;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Table\TableStyle;
|
||||
|
||||
class TableStyleTest extends SetupTeardown
|
||||
{
|
||||
private const INITIAL_RANGE = 'H2:O256';
|
||||
|
||||
public function testVariousSets(): void
|
||||
{
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
$style = $table->getStyle();
|
||||
|
||||
$result = $style->setTheme(TableStyle::TABLE_STYLE_DARK1);
|
||||
self::assertInstanceOf(TableStyle::class, $result);
|
||||
self::assertEquals(TableStyle::TABLE_STYLE_DARK1, $style->getTheme());
|
||||
|
||||
$result = $style->setShowFirstColumn(true);
|
||||
self::assertInstanceOf(TableStyle::class, $result);
|
||||
self::assertTrue($style->getShowFirstColumn());
|
||||
|
||||
$result = $style->setShowLastColumn(true);
|
||||
self::assertInstanceOf(TableStyle::class, $result);
|
||||
self::assertTrue($style->getShowLastColumn());
|
||||
|
||||
$result = $style->setShowRowStripes(true);
|
||||
self::assertInstanceOf(TableStyle::class, $result);
|
||||
self::assertTrue($style->getShowRowStripes());
|
||||
|
||||
$result = $style->setShowColumnStripes(true);
|
||||
self::assertInstanceOf(TableStyle::class, $result);
|
||||
self::assertTrue($style->getShowColumnStripes());
|
||||
}
|
||||
|
||||
public function testTable(): void
|
||||
{
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
$style = new TableStyle();
|
||||
$style->setTable($table);
|
||||
self::assertEquals($table, $style->getTable());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,558 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Worksheet\Table;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Cell\CellAddress;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\CellRange;
|
||||
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Table\Column;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class TableTest extends SetupTeardown
|
||||
{
|
||||
private const INITIAL_RANGE = 'H2:O256';
|
||||
|
||||
public function testToString(): void
|
||||
{
|
||||
$expectedResult = self::INITIAL_RANGE;
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
|
||||
// magic __toString should return the active table range
|
||||
$result = (string) $table;
|
||||
self::assertEquals($expectedResult, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider validTableNamesProvider
|
||||
*/
|
||||
public function testValidTableNames(string $name, string $expected): void
|
||||
{
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
|
||||
$result = $table->setName($name);
|
||||
self::assertInstanceOf(Table::class, $result);
|
||||
self::assertEquals($expected, $table->getName());
|
||||
}
|
||||
|
||||
public function validTableNamesProvider(): array
|
||||
{
|
||||
return [
|
||||
['', ''],
|
||||
['Table_1', 'Table_1'],
|
||||
['_table_2', '_table_2'],
|
||||
['\table_3', '\table_3'],
|
||||
[" Table_4 \n", 'Table_4'],
|
||||
['table.5', 'table.5'],
|
||||
['தமிழ்', 'தமிழ்'], // UTF-8 letters with combined character
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider invalidTableNamesProvider
|
||||
*/
|
||||
public function testInvalidTableNames(string $name): void
|
||||
{
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
|
||||
$this->expectException(PhpSpreadsheetException::class);
|
||||
|
||||
$table->setName($name);
|
||||
}
|
||||
|
||||
public function invalidTableNamesProvider(): array
|
||||
{
|
||||
return [
|
||||
['C'],
|
||||
['c'],
|
||||
['R'],
|
||||
['r'],
|
||||
['Z100'],
|
||||
['Z$100'],
|
||||
['R1C1'],
|
||||
['R1C'],
|
||||
['R11C11'],
|
||||
['123'],
|
||||
['=Table'],
|
||||
['ிக'], // starting with UTF-8 combined character
|
||||
[bin2hex(random_bytes(255))], // random string with length greater than 255
|
||||
];
|
||||
}
|
||||
|
||||
public function testUniqueTableName(): void
|
||||
{
|
||||
$this->expectException(PhpSpreadsheetException::class);
|
||||
$sheet = $this->getSheet();
|
||||
|
||||
$table1 = new Table();
|
||||
$table1->setName('Table_1');
|
||||
$sheet->addTable($table1);
|
||||
|
||||
$table2 = new Table();
|
||||
$table2->setName('table_1'); // case insensitive
|
||||
$sheet->addTable($table2);
|
||||
}
|
||||
|
||||
public function testVariousSets(): void
|
||||
{
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
|
||||
$result = $table->setShowHeaderRow(false);
|
||||
self::assertInstanceOf(Table::class, $result);
|
||||
self::assertFalse($table->getShowHeaderRow());
|
||||
|
||||
$result = $table->setShowTotalsRow(true);
|
||||
self::assertInstanceOf(Table::class, $result);
|
||||
self::assertTrue($table->getShowTotalsRow());
|
||||
}
|
||||
|
||||
public function testGetWorksheet(): void
|
||||
{
|
||||
$sheet = $this->getSheet();
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
$sheet->addTable($table);
|
||||
$result = $table->getWorksheet();
|
||||
self::assertSame($sheet, $result);
|
||||
}
|
||||
|
||||
public function testSetWorksheet(): void
|
||||
{
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
$spreadsheet = $this->getSpreadsheet();
|
||||
$sheet2 = $spreadsheet->createSheet();
|
||||
// Setters return the instance to implement the fluent interface
|
||||
$result = $table->setWorksheet($sheet2);
|
||||
self::assertInstanceOf(Table::class, $result);
|
||||
}
|
||||
|
||||
public function testGetRange(): void
|
||||
{
|
||||
$expectedResult = self::INITIAL_RANGE;
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
|
||||
// Result should be the active table range
|
||||
$result = $table->getRange();
|
||||
self::assertEquals($expectedResult, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider validTableRangeProvider
|
||||
*
|
||||
* @param AddressRange|array<int>|string $fullRange
|
||||
* @param string $fullRange
|
||||
*/
|
||||
public function testSetRangeValidRange($fullRange, string $actualRange): void
|
||||
{
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
|
||||
$result = $table->setRange($fullRange);
|
||||
self::assertInstanceOf(Table::class, $result);
|
||||
self::assertEquals($actualRange, $table->getRange());
|
||||
}
|
||||
|
||||
public function validTableRangeProvider(): array
|
||||
{
|
||||
$sheet = $this->getSheet();
|
||||
$title = $sheet->getTitle();
|
||||
|
||||
return [
|
||||
["$title!G1:J512", 'G1:J512'],
|
||||
['K1:N20', 'K1:N20'],
|
||||
[[3, 5, 6, 8], 'C5:F8'],
|
||||
[new CellRange(new CellAddress('C5', $sheet), new CellAddress('F8', $sheet)), 'C5:F8'],
|
||||
];
|
||||
}
|
||||
|
||||
public function testClearRange(): void
|
||||
{
|
||||
$expectedResult = '';
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
|
||||
// Setters return the instance to implement the fluent interface
|
||||
$result = $table->setRange('');
|
||||
self::assertInstanceOf(Table::class, $result);
|
||||
|
||||
// Result should be a clear range
|
||||
$result = $table->getRange();
|
||||
self::assertEquals($expectedResult, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider invalidTableRangeProvider
|
||||
*/
|
||||
public function testSetRangeInvalidRange(string $range): void
|
||||
{
|
||||
$this->expectException(PhpSpreadsheetException::class);
|
||||
|
||||
new Table($range);
|
||||
}
|
||||
|
||||
public function invalidTableRangeProvider(): array
|
||||
{
|
||||
return [
|
||||
['A1'],
|
||||
['A1:A1'],
|
||||
['B1:A4'],
|
||||
['A1:D1'],
|
||||
['D1:A1'],
|
||||
];
|
||||
}
|
||||
|
||||
public function testGetColumnsEmpty(): void
|
||||
{
|
||||
// There should be no columns yet defined
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
$result = $table->getColumns();
|
||||
self::assertIsArray($result);
|
||||
self::assertCount(0, $result);
|
||||
}
|
||||
|
||||
public function testGetColumnOffset(): void
|
||||
{
|
||||
$columnIndexes = [
|
||||
'H' => 0,
|
||||
'K' => 3,
|
||||
'M' => 5,
|
||||
];
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
|
||||
// If we request a specific column by its column ID, we should get an
|
||||
// integer returned representing the column offset within the range
|
||||
foreach ($columnIndexes as $columnIndex => $columnOffset) {
|
||||
$result = $table->getColumnOffset($columnIndex);
|
||||
self::assertEquals($columnOffset, $result);
|
||||
}
|
||||
}
|
||||
|
||||
public function testRemoveColumns(): void
|
||||
{
|
||||
$sheet = $this->getSheet();
|
||||
$sheet->fromArray(range('H', 'O'), null, 'H2');
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
$table->getColumn('L')->setShowFilterButton(false);
|
||||
$sheet->addTable($table);
|
||||
|
||||
$sheet->removeColumn('K', 2);
|
||||
$result = $table->getRange();
|
||||
self::assertEquals('H2:M256', $result);
|
||||
|
||||
// Check that the prop that was set for column L is no longer set
|
||||
self::assertTrue($table->getColumn('L')->getShowFilterButton());
|
||||
}
|
||||
|
||||
public function testRemoveRows(): void
|
||||
{
|
||||
$sheet = $this->getSheet();
|
||||
$sheet->fromArray(range('H', 'O'), null, 'H2');
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
$sheet->addTable($table);
|
||||
|
||||
$sheet->removeRow(42, 128);
|
||||
$result = $table->getRange();
|
||||
self::assertEquals('H2:O128', $result);
|
||||
}
|
||||
|
||||
public function testInsertColumns(): void
|
||||
{
|
||||
$sheet = $this->getSheet();
|
||||
$sheet->fromArray(range('H', 'O'), null, 'H2');
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
$table->getColumn('N')->setShowFilterButton(false);
|
||||
$sheet->addTable($table);
|
||||
|
||||
$sheet->insertNewColumnBefore('N', 3);
|
||||
$result = $table->getRange();
|
||||
self::assertEquals('H2:R256', $result);
|
||||
|
||||
// Check that column N no longer has a prop
|
||||
self::assertTrue($table->getColumn('N')->getShowFilterButton());
|
||||
// Check that the prop originally set in column N has been moved to column Q
|
||||
self::assertFalse($table->getColumn('Q')->getShowFilterButton());
|
||||
}
|
||||
|
||||
public function testInsertRows(): void
|
||||
{
|
||||
$sheet = $this->getSheet();
|
||||
$sheet->fromArray(range('H', 'O'), null, 'H2');
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
$sheet->addTable($table);
|
||||
|
||||
$sheet->insertNewRowBefore(3, 4);
|
||||
$result = $table->getRange();
|
||||
self::assertEquals('H2:O260', $result);
|
||||
}
|
||||
|
||||
public function testGetInvalidColumnOffset(): void
|
||||
{
|
||||
$this->expectException(PhpSpreadsheetException::class);
|
||||
|
||||
$invalidColumn = 'G';
|
||||
$sheet = $this->getSheet();
|
||||
$table = new Table();
|
||||
$table->setWorksheet($sheet);
|
||||
|
||||
$table->getColumnOffset($invalidColumn);
|
||||
}
|
||||
|
||||
public function testSetColumnWithString(): void
|
||||
{
|
||||
$expectedResult = 'L';
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
|
||||
// Setters return the instance to implement the fluent interface
|
||||
$result = $table->setColumn($expectedResult);
|
||||
self::assertInstanceOf(Table::class, $result);
|
||||
|
||||
$result = $table->getColumns();
|
||||
// Result should be an array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\Table\Column
|
||||
// objects for each column we set indexed by the column ID
|
||||
self::assertIsArray($result);
|
||||
self::assertCount(1, $result);
|
||||
self::assertArrayHasKey($expectedResult, $result);
|
||||
self::assertInstanceOf(Column::class, $result[$expectedResult]);
|
||||
}
|
||||
|
||||
public function testSetInvalidColumnWithString(): void
|
||||
{
|
||||
$this->expectException(PhpSpreadsheetException::class);
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
|
||||
$invalidColumn = 'A';
|
||||
$table->setColumn($invalidColumn);
|
||||
}
|
||||
|
||||
public function testSetColumnWithColumnObject(): void
|
||||
{
|
||||
$expectedResult = 'M';
|
||||
$columnObject = new Column($expectedResult);
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
|
||||
// Setters return the instance to implement the fluent interface
|
||||
$result = $table->setColumn($columnObject);
|
||||
self::assertInstanceOf(Table::class, $result);
|
||||
|
||||
$result = $table->getColumns();
|
||||
// Result should be an array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\Table\Column
|
||||
// objects for each column we set indexed by the column ID
|
||||
self::assertIsArray($result);
|
||||
self::assertCount(1, $result);
|
||||
self::assertArrayHasKey($expectedResult, $result);
|
||||
self::assertInstanceOf(Column::class, $result[$expectedResult]);
|
||||
}
|
||||
|
||||
public function testSetInvalidColumnWithObject(): void
|
||||
{
|
||||
$this->expectException(PhpSpreadsheetException::class);
|
||||
|
||||
$invalidColumn = 'E';
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
$table->setColumn($invalidColumn);
|
||||
}
|
||||
|
||||
public function testSetColumnWithInvalidDataType(): void
|
||||
{
|
||||
$this->expectException(PhpSpreadsheetException::class);
|
||||
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
$invalidColumn = 123.456;
|
||||
// @phpstan-ignore-next-line
|
||||
$table->setColumn($invalidColumn);
|
||||
}
|
||||
|
||||
public function testGetColumns(): void
|
||||
{
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
|
||||
$columnIndexes = ['L', 'M'];
|
||||
|
||||
foreach ($columnIndexes as $columnIndex) {
|
||||
$table->setColumn($columnIndex);
|
||||
}
|
||||
|
||||
$result = $table->getColumns();
|
||||
// Result should be an array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\Table\Column
|
||||
// objects for each column we set indexed by the column ID
|
||||
self::assertIsArray($result);
|
||||
self::assertCount(count($columnIndexes), $result);
|
||||
foreach ($columnIndexes as $columnIndex) {
|
||||
self::assertArrayHasKey($columnIndex, $result);
|
||||
self::assertInstanceOf(Column::class, $result[$columnIndex]);
|
||||
}
|
||||
|
||||
$table->setRange('');
|
||||
self::assertCount(0, $table->getColumns());
|
||||
self::assertSame('', $table->getRange());
|
||||
}
|
||||
|
||||
public function testGetColumn(): void
|
||||
{
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
|
||||
$columnIndexes = ['L', 'M'];
|
||||
|
||||
foreach ($columnIndexes as $columnIndex) {
|
||||
$table->setColumn($columnIndex);
|
||||
}
|
||||
|
||||
// If we request a specific column by its column ID, we should
|
||||
// get a \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\Table\Column object returned
|
||||
foreach ($columnIndexes as $columnIndex) {
|
||||
$result = $table->getColumn($columnIndex);
|
||||
self::assertInstanceOf(Column::class, $result);
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetColumnByOffset(): void
|
||||
{
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
|
||||
$columnIndexes = [
|
||||
0 => 'H',
|
||||
3 => 'K',
|
||||
5 => 'M',
|
||||
];
|
||||
|
||||
// If we request a specific column by its offset, we should
|
||||
// get a \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\Table\Column object returned
|
||||
foreach ($columnIndexes as $columnIndex => $columnID) {
|
||||
$result = $table->getColumnByOffset($columnIndex);
|
||||
self::assertInstanceOf(Column::class, $result);
|
||||
self::assertEquals($result->getColumnIndex(), $columnID);
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetColumnIfNotSet(): void
|
||||
{
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
// If we request a specific column by its column ID, we should
|
||||
// get a \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\Table\Column object returned
|
||||
$result = $table->getColumn('K');
|
||||
self::assertInstanceOf(Column::class, $result);
|
||||
}
|
||||
|
||||
public function testGetColumnWithoutRangeSet(): void
|
||||
{
|
||||
$this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class);
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
|
||||
// Clear the range
|
||||
$table->setRange('');
|
||||
$table->getColumn('A');
|
||||
}
|
||||
|
||||
public function testClearRangeWithExistingColumns(): void
|
||||
{
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
$expectedResult = '';
|
||||
|
||||
$columnIndexes = ['L', 'M', 'N'];
|
||||
foreach ($columnIndexes as $columnIndex) {
|
||||
$table->setColumn($columnIndex);
|
||||
}
|
||||
|
||||
// Setters return the instance to implement the fluent interface
|
||||
$result = $table->setRange('');
|
||||
self::assertInstanceOf(Table::class, $result);
|
||||
|
||||
// Range should be cleared
|
||||
$result = $table->getRange();
|
||||
self::assertEquals($expectedResult, $result);
|
||||
|
||||
// Column array should be cleared
|
||||
$result = $table->getColumns();
|
||||
self::assertIsArray($result);
|
||||
self::assertCount(0, $result);
|
||||
}
|
||||
|
||||
public function testSetRangeWithExistingColumns(): void
|
||||
{
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
$expectedResult = 'G1:J512';
|
||||
|
||||
// These columns should be retained
|
||||
$columnIndexes1 = ['I', 'J'];
|
||||
foreach ($columnIndexes1 as $columnIndex) {
|
||||
$table->setColumn($columnIndex);
|
||||
}
|
||||
// These columns should be discarded
|
||||
$columnIndexes2 = ['K', 'L', 'M'];
|
||||
foreach ($columnIndexes2 as $columnIndex) {
|
||||
$table->setColumn($columnIndex);
|
||||
}
|
||||
|
||||
// Setters return the instance to implement the fluent interface
|
||||
$result = $table->setRange($expectedResult);
|
||||
self::assertInstanceOf(Table::class, $result);
|
||||
|
||||
// Range should be correctly set
|
||||
$result = $table->getRange();
|
||||
self::assertEquals($expectedResult, $result);
|
||||
|
||||
// Only columns that existed in the original range and that
|
||||
// still fall within the new range should be retained
|
||||
$result = $table->getColumns();
|
||||
self::assertIsArray($result);
|
||||
self::assertCount(count($columnIndexes1), $result);
|
||||
}
|
||||
|
||||
public function testClone(): void
|
||||
{
|
||||
$sheet = $this->getSheet();
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
$sheet->addTable($table);
|
||||
$columnIndexes = ['L', 'M'];
|
||||
|
||||
foreach ($columnIndexes as $columnIndex) {
|
||||
$table->setColumn($columnIndex);
|
||||
}
|
||||
|
||||
$result = clone $table;
|
||||
self::assertInstanceOf(Table::class, $result);
|
||||
self::assertSame($table->getRange(), $result->getRange());
|
||||
self::assertNull($result->getWorksheet());
|
||||
self::assertNotNull($table->getWorksheet());
|
||||
self::assertInstanceOf(Worksheet::class, $table->getWorksheet());
|
||||
$tableColumns = $table->getColumns();
|
||||
$resultColumns = $result->getColumns();
|
||||
self::assertIsArray($tableColumns);
|
||||
self::assertIsArray($resultColumns);
|
||||
self::assertCount(2, $tableColumns);
|
||||
self::assertCount(2, $resultColumns);
|
||||
self::assertArrayHasKey('L', $tableColumns);
|
||||
self::assertArrayHasKey('L', $resultColumns);
|
||||
self::assertArrayHasKey('M', $tableColumns);
|
||||
self::assertArrayHasKey('M', $resultColumns);
|
||||
self::assertInstanceOf(Column::class, $tableColumns['L']);
|
||||
self::assertInstanceOf(Column::class, $resultColumns['L']);
|
||||
self::assertInstanceOf(Column::class, $tableColumns['M']);
|
||||
self::assertInstanceOf(Column::class, $resultColumns['M']);
|
||||
}
|
||||
|
||||
public function testNoWorksheet(): void
|
||||
{
|
||||
$table = new Table();
|
||||
self::assertNull($table->getWorksheet());
|
||||
}
|
||||
|
||||
public function testClearColumn(): void
|
||||
{
|
||||
$table = new Table(self::INITIAL_RANGE);
|
||||
$columnIndexes = ['J', 'K', 'L', 'M'];
|
||||
|
||||
foreach ($columnIndexes as $columnIndex) {
|
||||
$table->setColumn($columnIndex);
|
||||
}
|
||||
$columns = $table->getColumns();
|
||||
self::assertCount(4, $columns);
|
||||
self::assertArrayHasKey('J', $columns);
|
||||
self::assertArrayHasKey('K', $columns);
|
||||
self::assertArrayHasKey('L', $columns);
|
||||
self::assertArrayHasKey('M', $columns);
|
||||
$table->clearColumn('K');
|
||||
$columns = $table->getColumns();
|
||||
self::assertCount(3, $columns);
|
||||
self::assertArrayHasKey('J', $columns);
|
||||
self::assertArrayHasKey('L', $columns);
|
||||
self::assertArrayHasKey('M', $columns);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue