Skip to content

Commit f52ae20

Browse files
authored
Merge pull request #4412 from mikkokoo/master
Conditional and table formatting support for html
2 parents 8e3417d + 584c866 commit f52ae20

31 files changed

+1215
-37
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
1313
- Add FormulaRange to IgnoredErrors. [PR #4393](https://github.com/PHPOffice/PhpSpreadsheet/pull/4393)
1414
- TextGrid improvements. [PR #4418](https://github.com/PHPOffice/PhpSpreadsheet/pull/4418)
1515
- Permit read to class which extends Spreadsheet. [Discussion #4402](https://github.com/PHPOffice/PhpSpreadsheet/discussions/4402) [PR #4404](https://github.com/PHPOffice/PhpSpreadsheet/pull/4404)
16+
- Conditional and table formatting support for html writer [PR #4412](https://github.com/PHPOffice/PhpSpreadsheet/pull/4412)
1617

1718
### Removed
1819

docs/topics/conditional-formatting.md

+22-15
Original file line numberDiff line numberDiff line change
@@ -143,20 +143,28 @@ Currently, the following Conditional Types are supported for the following Reade
143143

144144
MS Excel | Conditional Type | Readers | Writers
145145
---|---|---|---
146-
| Cell Value | Conditional::CONDITION_CELLIS | Xlsx | Xlsx, Xls
147-
Specific Text | Conditional::CONDITION_CONTAINSTEXT | Xlsx | Xlsx
148-
| Conditional::CONDITION_NOTCONTAINSTEXT | Xlsx | Xlsx
149-
| Conditional::CONDITION_BEGINSWITH | Xlsx | Xlsx
150-
| Conditional::CONDITION_ENDSWITH | Xlsx | Xlsx
151-
Dates Occurring | Conditional::CONDITION_TIMEPERIOD | Xlsx | Xlsx
152-
Blanks | Conditional::CONDITION_CONTAINSBLANKS | Xlsx | Xlsx
153-
No Blanks | Conditional::CONDITION_NOTCONTAINSBLANKS | Xlsx | Xlsx
154-
Errors | Conditional::CONDITION_CONTAINSERRORS | Xlsx | Xlsx
155-
No Errors | Conditional::CONDITION_NOTCONTAINSERRORS | Xlsx | Xlsx
156-
Duplicates/Unique | Conditional::CONDITION_DUPLICATES | Xlsx | Xlsx
157-
| Conditional::CONDITION_UNIQUE | Xlsx | Xlsx
158-
Use a formula | Conditional::CONDITION_EXPRESSION | Xlsx | Xlsx, Xls
159-
Data Bars | Conditional::CONDITION_DATABAR | Xlsx | Xlsx
146+
| Cell Value | Conditional::CONDITION_CELLIS | Xlsx | Xlsx, Xls, Html
147+
Specific Text | Conditional::CONDITION_CONTAINSTEXT | Xlsx | Xlsx, Html
148+
| Conditional::CONDITION_NOTCONTAINSTEXT | Xlsx | Xlsx, Html
149+
| Conditional::CONDITION_BEGINSWITH | Xlsx | Xlsx, Html
150+
| Conditional::CONDITION_ENDSWITH | Xlsx | Xlsx, Html
151+
Dates Occurring | Conditional::CONDITION_TIMEPERIOD | Xlsx | Xlsx, Html
152+
Blanks | Conditional::CONDITION_CONTAINSBLANKS | Xlsx | Xlsx, Html
153+
No Blanks | Conditional::CONDITION_NOTCONTAINSBLANKS | Xlsx | Xlsx, Html
154+
Errors | Conditional::CONDITION_CONTAINSERRORS | Xlsx | Xlsx, Html
155+
No Errors | Conditional::CONDITION_NOTCONTAINSERRORS | Xlsx | Xlsx, Html
156+
Duplicates/Unique | Conditional::CONDITION_DUPLICATES | Xlsx | Xlsx, Html
157+
| Conditional::CONDITION_UNIQUE | Xlsx | Xlsx, Html
158+
Use a formula | Conditional::CONDITION_EXPRESSION | Xlsx | Xlsx, Xls, Html
159+
Data Bars | Conditional::CONDITION_DATABAR | Xlsx | Xlsx, Html
160+
Colour Scales | Conditional::COLORSCALE | Xlsx | Html
161+
162+
To enable conditional formatting for Html writer, use:
163+
164+
```php
165+
$writer = new HtmlWriter($spreadsheet);
166+
$writer->setConditionalFormatting(true);
167+
```
160168

161169
The following Conditional Types are currently not supported by any Readers or Writers:
162170

@@ -165,7 +173,6 @@ MS Excel | Conditional Type
165173
Above/Below Average | ?
166174
Top/Bottom Items | ?
167175
Top/Bottom %age | ?
168-
Colour Scales |?
169176
Icon Sets | ?
170177

171178
Unsupported types will by ignored by the Readers, and cannot be created through PHPSpreadsheet.

docs/topics/tables.md

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Tables
2+
3+
## Introduction
4+
5+
To make managing and analyzing a group of related data easier, you can turn a range of cells into an Excel table (previously known as an Excel list).
6+
7+
## Support
8+
9+
Currently tables are supported in Xlsx reader and Html Writer
10+
11+
To enable table formatting for Html writer, use:
12+
13+
```php
14+
$writer = new HtmlWriter($spreadsheet);
15+
$writer->setConditionalFormatting(true);
16+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
use PhpOffice\PhpSpreadsheet\IOFactory;
4+
use PhpOffice\PhpSpreadsheet\Writer\Html as HtmlWriter;
5+
6+
require __DIR__ . '/../Header.php';
7+
8+
$inputFileName = 'BasicConditionalFormatting.xlsx';
9+
$inputFilePath = __DIR__ . '/../templates/' . $inputFileName;
10+
11+
$codePath = $helper->isCli() ? ('samples/templates/' . $inputFileName) : ('<code>' . 'samples/templates/' . $inputFileName . '</code>');
12+
$helper->log('Read ' . $codePath . ' with conditional formatting');
13+
$reader = IOFactory::createReader('Xlsx');
14+
$reader->setReadDataOnly(false);
15+
$spreadsheet = $reader->load($inputFilePath);
16+
$helper->log('Enable conditional formatting output');
17+
18+
function writerCallback(HtmlWriter $writer): void
19+
{
20+
$writer->setPreCalculateFormulas(true);
21+
$writer->setConditionalFormatting(true);
22+
}
23+
24+
// Save
25+
$helper->write($spreadsheet, __FILE__, ['Html'], false, writerCallback: writerCallback(...));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
use PhpOffice\PhpSpreadsheet\IOFactory;
4+
use PhpOffice\PhpSpreadsheet\Writer\Html as HtmlWriter;
5+
6+
require __DIR__ . '/../Header.php';
7+
8+
$inputFileName = 'ConditionalFormattingConditions.xlsx';
9+
$inputFilePath = __DIR__ . '/../templates/' . $inputFileName;
10+
11+
$codePath = $helper->isCli() ? ('samples/templates/' . $inputFileName) : ('<code>' . 'samples/templates/' . $inputFileName . '</code>');
12+
$helper->log('Read ' . $codePath . ' with conditional formatting');
13+
$reader = IOFactory::createReader('Xlsx');
14+
$reader->setReadDataOnly(false);
15+
$spreadsheet = $reader->load($inputFilePath);
16+
$helper->log('Enable conditional formatting output');
17+
18+
function writerCallback(HtmlWriter $writer): void
19+
{
20+
$writer->setPreCalculateFormulas(true);
21+
$writer->setConditionalFormatting(true);
22+
}
23+
24+
// Save
25+
$helper->write($spreadsheet, __FILE__, ['Html'], false, writerCallback: writerCallback(...));

samples/Html/html_03_Color_Scale.php

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
use PhpOffice\PhpSpreadsheet\IOFactory;
4+
use PhpOffice\PhpSpreadsheet\Writer\Html as HtmlWriter;
5+
6+
require __DIR__ . '/../Header.php';
7+
8+
$inputFileName = 'ColourScale.xlsx';
9+
$inputFilePath = __DIR__ . '/../templates/' . $inputFileName;
10+
11+
$codePath = $helper->isCli() ? ('samples/templates/' . $inputFileName) : ('<code>' . 'samples/templates/' . $inputFileName . '</code>');
12+
$helper->log('Read ' . $codePath . ' with color scale');
13+
$reader = IOFactory::createReader('Xlsx');
14+
$reader->setReadDataOnly(false);
15+
$spreadsheet = $reader->load($inputFilePath);
16+
$helper->log('Enable conditional formatting output');
17+
18+
function writerCallback(HtmlWriter $writer): void
19+
{
20+
$writer->setPreCalculateFormulas(true);
21+
$writer->setConditionalFormatting(true);
22+
}
23+
24+
// Save
25+
$helper->write($spreadsheet, __FILE__, ['Html'], false, writerCallback: writerCallback(...));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
use PhpOffice\PhpSpreadsheet\IOFactory;
4+
use PhpOffice\PhpSpreadsheet\Writer\Html as HtmlWriter;
5+
6+
require __DIR__ . '/../Header.php';
7+
8+
$inputFileName = 'TableFormat.xlsx';
9+
$inputFilePath = __DIR__ . '/../templates/' . $inputFileName;
10+
11+
$codePath = $helper->isCli() ? ('samples/templates/' . $inputFileName) : ('<code>' . 'samples/templates/' . $inputFileName . '</code>');
12+
$helper->log('Read ' . $codePath);
13+
$reader = IOFactory::createReader('Xlsx');
14+
$reader->setReadDataOnly(false);
15+
$spreadsheet = $reader->load($inputFilePath);
16+
$helper->log('Enable table formatting output');
17+
18+
function writerCallback(HtmlWriter $writer): void
19+
{
20+
$writer->setPreCalculateFormulas(true);
21+
$writer->setTableFormats(true);
22+
}
23+
24+
// Save
25+
$helper->write($spreadsheet, __FILE__, ['Html'], false, writerCallback: writerCallback(...));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
use PhpOffice\PhpSpreadsheet\IOFactory;
4+
use PhpOffice\PhpSpreadsheet\Writer\Html as HtmlWriter;
5+
6+
require __DIR__ . '/../Header.php';
7+
8+
$inputFileName = 'TableFormat.xlsx';
9+
$inputFilePath = __DIR__ . '/../templates/' . $inputFileName;
10+
11+
$codePath = $helper->isCli() ? ('samples/templates/' . $inputFileName) : ('<code>' . 'samples/templates/' . $inputFileName . '</code>');
12+
$helper->log('Read ' . $codePath);
13+
$reader = IOFactory::createReader('Xlsx');
14+
$reader->setReadDataOnly(false);
15+
$spreadsheet = $reader->load($inputFilePath);
16+
$helper->log('Enable table formatting output');
17+
$helper->log('Enable conditional formatting output');
18+
19+
function writerCallback(HtmlWriter $writer): void
20+
{
21+
$writer->setPreCalculateFormulas(true);
22+
$writer->setTableFormats(true);
23+
$writer->setConditionalFormatting(true);
24+
}
25+
26+
// Save
27+
$helper->write($spreadsheet, __FILE__, ['Html'], false, writerCallback: writerCallback(...));
Binary file not shown.

samples/templates/ColourScale.xlsx

8.59 KB
Binary file not shown.
Binary file not shown.

samples/templates/TableFormat.xlsx

9.36 KB
Binary file not shown.

src/PhpSpreadsheet/Reader/Xlsx.php

+10-5
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
692692
$this->styleReader->setNamespace($mainNS);
693693
$this->styleReader->setStyleBaseData($theme, $styles, $cellStyles);
694694
$dxfs = $this->styleReader->dxfs($this->readDataOnly);
695+
$tableStyles = $this->styleReader->tableStyles($this->readDataOnly);
695696
$styles = $this->styleReader->styles();
696697

697698
// Read content after setting the styles
@@ -1000,7 +1001,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
10001001
$this->readBackgroundImage($xmlSheetNS, $docSheet, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels');
10011002
}
10021003

1003-
$this->readTables($xmlSheetNS, $docSheet, $dir, $fileWorksheet, $zip, $mainNS);
1004+
$this->readTables($xmlSheetNS, $docSheet, $dir, $fileWorksheet, $zip, $mainNS, $tableStyles, $dxfs);
10041005

10051006
if ($xmlSheetNS && $xmlSheetNS->mergeCells && $xmlSheetNS->mergeCells->mergeCell && !$this->readDataOnly) {
10061007
foreach ($xmlSheetNS->mergeCells->mergeCell as $mergeCellx) {
@@ -2311,12 +2312,14 @@ private function readTables(
23112312
string $dir,
23122313
string $fileWorksheet,
23132314
ZipArchive $zip,
2314-
string $namespaceTable
2315+
string $namespaceTable,
2316+
array $tableStyles,
2317+
array $dxfs
23152318
): void {
23162319
if ($xmlSheet && $xmlSheet->tableParts) {
23172320
$attributes = $xmlSheet->tableParts->attributes() ?? ['count' => 0];
23182321
if (((int) $attributes['count']) > 0) {
2319-
$this->readTablesInTablesFile($xmlSheet, $dir, $fileWorksheet, $zip, $docSheet, $namespaceTable);
2322+
$this->readTablesInTablesFile($xmlSheet, $dir, $fileWorksheet, $zip, $docSheet, $namespaceTable, $tableStyles, $dxfs);
23202323
}
23212324
}
23222325
}
@@ -2327,7 +2330,9 @@ private function readTablesInTablesFile(
23272330
string $fileWorksheet,
23282331
ZipArchive $zip,
23292332
Worksheet $docSheet,
2330-
string $namespaceTable
2333+
string $namespaceTable,
2334+
array $tableStyles,
2335+
array $dxfs
23312336
): void {
23322337
foreach ($xmlSheet->tableParts->tablePart as $tablePart) {
23332338
$relation = self::getAttributes($tablePart, Namespaces::SCHEMA_OFFICE_DOCUMENT);
@@ -2346,7 +2351,7 @@ private function readTablesInTablesFile(
23462351

23472352
if ($this->fileExistsInArchive($this->zip, $relationshipFilePath)) {
23482353
$tableXml = $this->loadZip($relationshipFilePath, $namespaceTable);
2349-
(new TableReader($docSheet, $tableXml))->load();
2354+
(new TableReader($docSheet, $tableXml))->load($tableStyles, $dxfs);
23502355
}
23512356
}
23522357
}

src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php

+7
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,13 @@ private function setConditionalStyles(Worksheet $worksheet, array $conditionals,
193193
// N.B. In Excel UI, intersection is space and union is comma.
194194
// But in Xml, intersection is comma and union is space.
195195
$cellRangeReference = str_replace(['$', ' ', ',', '^'], ['', '^', ' ', ','], strtoupper($cellRangeReference));
196+
197+
foreach ($conditionalStyles as $cs) {
198+
$scale = $cs->getColorScale();
199+
if ($scale !== null) {
200+
$scale->setSqRef($cellRangeReference, $worksheet);
201+
}
202+
}
196203
$worksheet->getStyle($cellRangeReference)->setConditionalStyles($conditionalStyles);
197204
}
198205
}

src/PhpSpreadsheet/Reader/Xlsx/Styles.php

+41
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
1313
use PhpOffice\PhpSpreadsheet\Style\Protection;
1414
use PhpOffice\PhpSpreadsheet\Style\Style;
15+
use PhpOffice\PhpSpreadsheet\Worksheet\Table\TableDxfsStyle;
1516
use SimpleXMLElement;
1617
use stdClass;
1718

@@ -447,6 +448,46 @@ public function dxfs(bool $readDataOnly = false): array
447448
return $dxfs;
448449
}
449450

451+
// get TableStyles
452+
public function tableStyles(bool $readDataOnly = false): array
453+
{
454+
$tableStyles = [];
455+
if (!$readDataOnly && $this->styleXml) {
456+
// Conditional Styles
457+
if ($this->styleXml->tableStyles) {
458+
foreach ($this->styleXml->tableStyles->tableStyle as $s) {
459+
$attrs = Xlsx::getAttributes($s);
460+
if (isset($attrs['name'][0])) {
461+
$style = new TableDxfsStyle((string) ($attrs['name'][0]));
462+
foreach ($s->tableStyleElement as $e) {
463+
$a = Xlsx::getAttributes($e);
464+
if (isset($a['dxfId'][0], $a['type'][0])) {
465+
switch ($a['type'][0]) {
466+
case 'headerRow':
467+
$style->setHeaderRow((int) ($a['dxfId'][0]));
468+
469+
break;
470+
case 'firstRowStripe':
471+
$style->setFirstRowStripe((int) ($a['dxfId'][0]));
472+
473+
break;
474+
case 'secondRowStripe':
475+
$style->setSecondRowStripe((int) ($a['dxfId'][0]));
476+
477+
break;
478+
default:
479+
}
480+
}
481+
}
482+
$tableStyles[] = $style;
483+
}
484+
}
485+
}
486+
}
487+
488+
return $tableStyles;
489+
}
490+
450491
public function styles(): array
451492
{
452493
return $this->styles;

src/PhpSpreadsheet/Reader/Xlsx/TableReader.php

+11-5
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,20 @@ public function __construct(Worksheet $workSheet, SimpleXMLElement $tableXml)
2525
/**
2626
* Loads Table into the Worksheet.
2727
*/
28-
public function load(): void
28+
public function load(array $tableStyles, array $dxfs): void
2929
{
3030
$this->tableAttributes = $this->tableXml->attributes() ?? [];
3131
// Remove all "$" in the table range
3232
$tableRange = (string) preg_replace('/\$/', '', $this->tableAttributes['ref'] ?? '');
3333
if (str_contains($tableRange, ':')) {
34-
$this->readTable($tableRange);
34+
$this->readTable($tableRange, $tableStyles, $dxfs);
3535
}
3636
}
3737

3838
/**
3939
* Read Table from xml.
4040
*/
41-
private function readTable(string $tableRange): void
41+
private function readTable(string $tableRange, array $tableStyles, array $dxfs): void
4242
{
4343
$table = new Table($tableRange);
4444
$table->setName((string) ($this->tableAttributes['displayName'] ?? ''));
@@ -47,7 +47,7 @@ private function readTable(string $tableRange): void
4747

4848
$this->readTableAutoFilter($table, $this->tableXml->autoFilter);
4949
$this->readTableColumns($table, $this->tableXml->tableColumns);
50-
$this->readTableStyle($table, $this->tableXml->tableStyleInfo);
50+
$this->readTableStyle($table, $this->tableXml->tableStyleInfo, $tableStyles, $dxfs);
5151

5252
(new AutoFilter($table, $this->tableXml))->load();
5353
$this->worksheet->addTable($table);
@@ -100,7 +100,7 @@ private function readTableColumns(Table $table, SimpleXMLElement $tableColumnsXm
100100
/**
101101
* Reads TableStyle from xml.
102102
*/
103-
private function readTableStyle(Table $table, SimpleXMLElement $tableStyleInfoXml): void
103+
private function readTableStyle(Table $table, SimpleXMLElement $tableStyleInfoXml, array $tableStyles, array $dxfs): void
104104
{
105105
$tableStyle = new TableStyle();
106106
$attributes = $tableStyleInfoXml->attributes();
@@ -110,6 +110,12 @@ private function readTableStyle(Table $table, SimpleXMLElement $tableStyleInfoXm
110110
$tableStyle->setShowColumnStripes((string) $attributes['showColumnStripes'] === '1');
111111
$tableStyle->setShowFirstColumn((string) $attributes['showFirstColumn'] === '1');
112112
$tableStyle->setShowLastColumn((string) $attributes['showLastColumn'] === '1');
113+
114+
foreach ($tableStyles as $style) {
115+
if ($style->getName() === (string) $attributes['name']) {
116+
$tableStyle->setTableDxfsStyle($style, $dxfs);
117+
}
118+
}
113119
}
114120
$table->setStyle($tableStyle);
115121
}

0 commit comments

Comments
 (0)