Skip to content

Commit a713e15

Browse files
Added Conditional Formatting: ColorScale for Xlsx (#3738)
* Added Conditional Formatting: ColorScale for Xlsx * Add Reader Support, Tests, Sample Also correct Phpstan and phpcs problems. * Update cond08_colorscale.php * Improve Coverage * More Coverage Improvements * Use StyleReader for Colors for ColorScale and DataBar The implementation of DataBar looks for an rgb attribute, but the color may be provided via theme attribute instead. The initial implementation for ColorScale did the same. Change both to use the existing code in Reader\Xlsx\Styles to parse the color. * Change Some Doc Blocks to Type Declarations --------- Co-authored-by: oleibman <10341515+oleibman@users.noreply.github.com>
1 parent 7328e1e commit a713e15

File tree

10 files changed

+339
-78
lines changed

10 files changed

+339
-78
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
4+
use PhpOffice\PhpSpreadsheet\Style\Color;
5+
use PhpOffice\PhpSpreadsheet\Style\Conditional;
6+
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalColorScale;
7+
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormatValueObject;
8+
9+
require __DIR__ . '/../Header.php';
10+
11+
// Create new Spreadsheet object
12+
$helper->log('Create new Spreadsheet object');
13+
$spreadsheet = new Spreadsheet();
14+
$sheet = $spreadsheet->getActiveSheet();
15+
16+
// Set document properties
17+
$helper->log('Set document properties');
18+
$spreadsheet->getProperties()->setCreator('Owen Leibman')
19+
->setLastModifiedBy('Owen Leibman')
20+
->setTitle('PhpSpreadsheet Test Document')
21+
->setSubject('PhpSpreadsheet Test Document')
22+
->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.')
23+
->setKeywords('office PhpSpreadsheet php')
24+
->setCategory('Test result file');
25+
26+
// Create the worksheet
27+
$helper->log('Add data');
28+
$sheet
29+
->setCellValue('A1', 1)
30+
->setCellValue('A2', 2)
31+
->setCellValue('A3', 8)
32+
->setCellValue('A4', 4)
33+
->setCellValue('A5', 5)
34+
->setCellValue('A6', 6)
35+
->setCellValue('A7', 7)
36+
->setCellValue('A8', 3)
37+
->setCellValue('A9', 9)
38+
->setCellValue('A10', 10);
39+
40+
// Set conditional formatting rules and styles
41+
$helper->log('Define conditional formatting using Color Scales');
42+
43+
$cellRange = 'A1:A10';
44+
$condition1 = new Conditional();
45+
$condition1->setConditionType(Conditional::CONDITION_COLORSCALE);
46+
$colorScale = new ConditionalColorScale();
47+
$condition1->setColorScale($colorScale);
48+
$colorScale
49+
->setMinimumConditionalFormatValueObject(new ConditionalFormatValueObject('min'))
50+
->setMidpointConditionalFormatValueObject(new ConditionalFormatValueObject('percentile', '40'))
51+
->setMaximumConditionalFormatValueObject(new ConditionalFormatValueObject('max'))
52+
->setMinimumColor(new Color('FFF8696B'))
53+
->setMidpointColor(new Color('FFFFEB84'))
54+
->setMaximumColor(new Color('FF63BE7B'));
55+
56+
$conditionalStyles = [$condition1];
57+
58+
$sheet
59+
->getStyle($cellRange)
60+
->setConditionalStyles($conditionalStyles);
61+
$sheet->setSelectedCells('B1');
62+
63+
// Save
64+
$helper->write($spreadsheet, __FILE__, ['Xlsx']);

src/PhpSpreadsheet/Reader/Xlsx.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -790,10 +790,10 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
790790
// Setting Conditional Styles adjusts selected cells, so we need to execute this
791791
// before reading the sheet view data to get the actual selected cells
792792
if (!$this->readDataOnly && ($xmlSheet->conditionalFormatting)) {
793-
(new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->load();
793+
(new ConditionalStyles($docSheet, $xmlSheet, $dxfs, $this->styleReader))->load();
794794
}
795795
if (!$this->readDataOnly && $xmlSheet->extLst) {
796-
(new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->loadFromExt($this->styleReader);
796+
(new ConditionalStyles($docSheet, $xmlSheet, $dxfs, $this->styleReader))->loadFromExt();
797797
}
798798
if (isset($xmlSheetMain->sheetViews, $xmlSheetMain->sheetViews->sheetView)) {
799799
$sheetViews = new SheetViews($xmlSheetMain->sheetViews->sheetView, $docSheet);

src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
44

55
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Styles as StyleReader;
6+
use PhpOffice\PhpSpreadsheet\Style\Color;
67
use PhpOffice\PhpSpreadsheet\Style\Conditional;
8+
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalColorScale;
79
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar;
810
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormattingRuleExtension;
911
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormatValueObject;
@@ -25,11 +27,14 @@ class ConditionalStyles
2527

2628
private array $dxfs;
2729

28-
public function __construct(Worksheet $workSheet, SimpleXMLElement $worksheetXml, array $dxfs = [])
30+
private StyleReader $styleReader;
31+
32+
public function __construct(Worksheet $workSheet, SimpleXMLElement $worksheetXml, array $dxfs, StyleReader $styleReader)
2933
{
3034
$this->worksheet = $workSheet;
3135
$this->worksheetXml = $worksheetXml;
3236
$this->dxfs = $dxfs;
37+
$this->styleReader = $styleReader;
3338
}
3439

3540
public function load(): void
@@ -45,13 +50,13 @@ public function load(): void
4550
$this->worksheet->setSelectedCells($selectedCells);
4651
}
4752

48-
public function loadFromExt(StyleReader $styleReader): void
53+
public function loadFromExt(): void
4954
{
5055
$selectedCells = $this->worksheet->getSelectedCells();
5156

5257
$this->ns = $this->worksheetXml->getNamespaces(true);
5358
$this->setConditionalsFromExt(
54-
$this->readConditionalsFromExt($this->worksheetXml->extLst, $styleReader)
59+
$this->readConditionalsFromExt($this->worksheetXml->extLst)
5560
);
5661

5762
$this->worksheet->setSelectedCells($selectedCells);
@@ -68,7 +73,7 @@ private function setConditionalsFromExt(array $conditionals): void
6873
}
6974
}
7075

71-
private function readConditionalsFromExt(SimpleXMLElement $extLst, StyleReader $styleReader): array
76+
private function readConditionalsFromExt(SimpleXMLElement $extLst): array
7277
{
7378
$conditionals = [];
7479
if (!isset($extLst->ext)) {
@@ -110,7 +115,7 @@ private function readConditionalsFromExt(SimpleXMLElement $extLst, StyleReader $
110115
$priority = (int) $attributes->priority;
111116

112117
$conditional = $this->readConditionalRuleFromExt($extCfRuleXml, $attributes);
113-
$cfStyle = $this->readStyleFromExt($extCfRuleXml, $styleReader);
118+
$cfStyle = $this->readStyleFromExt($extCfRuleXml);
114119
$conditional->setStyle($cfStyle);
115120
$conditionals[$sqref][$priority] = $conditional;
116121
}
@@ -146,17 +151,17 @@ private function readConditionalRuleFromExt(SimpleXMLElement $cfRuleXml, SimpleX
146151
return $conditional;
147152
}
148153

149-
private function readStyleFromExt(SimpleXMLElement $extCfRuleXml, StyleReader $styleReader): Style
154+
private function readStyleFromExt(SimpleXMLElement $extCfRuleXml): Style
150155
{
151156
$cfStyle = new Style(false, true);
152157
if ($extCfRuleXml->dxf) {
153158
$styleXML = $extCfRuleXml->dxf->children();
154159

155160
if ($styleXML->borders) {
156-
$styleReader->readBorderStyle($cfStyle->getBorders(), $styleXML->borders);
161+
$this->styleReader->readBorderStyle($cfStyle->getBorders(), $styleXML->borders);
157162
}
158163
if ($styleXML->fill) {
159-
$styleReader->readFillStyle($cfStyle->getFill(), $styleXML->fill);
164+
$this->styleReader->readFillStyle($cfStyle->getFill(), $styleXML->fill);
160165
}
161166
}
162167

@@ -198,6 +203,7 @@ private function readStyleRules(array $cfRules, SimpleXMLElement $extLst): array
198203
$conditionalFormattingRuleExtensions = ConditionalFormattingRuleExtension::parseExtLstXml($extLst);
199204
$conditionalStyles = [];
200205

206+
/** @var SimpleXMLElement $cfRule */
201207
foreach ($cfRules as $cfRule) {
202208
$objConditional = new Conditional();
203209
$objConditional->setConditionType((string) $cfRule['type']);
@@ -231,7 +237,11 @@ private function readStyleRules(array $cfRules, SimpleXMLElement $extLst): array
231237

232238
if (isset($cfRule->dataBar)) {
233239
$objConditional->setDataBar(
234-
$this->readDataBarOfConditionalRule($cfRule, $conditionalFormattingRuleExtensions) // @phpstan-ignore-line
240+
$this->readDataBarOfConditionalRule($cfRule, $conditionalFormattingRuleExtensions)
241+
);
242+
} elseif (isset($cfRule->colorScale)) {
243+
$objConditional->setColorScale(
244+
$this->readColorScale($cfRule)
235245
);
236246
} elseif (isset($cfRule['dxfId'])) {
237247
$objConditional->setStyle(clone $this->dxfs[(int) ($cfRule['dxfId'])]);
@@ -270,14 +280,48 @@ private function readDataBarOfConditionalRule($cfRule, array $conditionalFormatt
270280

271281
//color
272282
if (isset($cfRule->dataBar->color)) {
273-
$dataBar->setColor((string) $cfRule->dataBar->color['rgb']);
283+
$dataBar->setColor($this->styleReader->readColor($cfRule->dataBar->color));
274284
}
275285
//extLst
276286
$this->readDataBarExtLstOfConditionalRule($dataBar, $cfRule, $conditionalFormattingRuleExtensions);
277287

278288
return $dataBar;
279289
}
280290

291+
private function readColorScale(simpleXMLElement|stdClass $cfRule): ConditionalColorScale
292+
{
293+
$colorScale = new ConditionalColorScale();
294+
$types = [];
295+
foreach ($cfRule->colorScale->cfvo as $cfvoXml) {
296+
$attr = $cfvoXml->attributes() ?? [];
297+
$type = (string) ($attr['type'] ?? '');
298+
$types[] = $type;
299+
$val = $attr['val'] ?? null;
300+
if ($type === 'min') {
301+
$colorScale->setMinimumConditionalFormatValueObject(new ConditionalFormatValueObject($type, $val));
302+
} elseif ($type === 'percentile') {
303+
$colorScale->setMidpointConditionalFormatValueObject(new ConditionalFormatValueObject($type, $val));
304+
} elseif ($type === 'max') {
305+
$colorScale->setMaximumConditionalFormatValueObject(new ConditionalFormatValueObject($type, $val));
306+
}
307+
}
308+
$idx = 0;
309+
foreach ($cfRule->colorScale->color as $color) {
310+
$type = $types[$idx];
311+
$rgb = $this->styleReader->readColor($color);
312+
if ($type === 'min') {
313+
$colorScale->setMinimumColor(new Color($rgb));
314+
} elseif ($type === 'percentile') {
315+
$colorScale->setMidpointColor(new Color($rgb));
316+
} elseif ($type === 'max') {
317+
$colorScale->setMaximumColor(new Color($rgb));
318+
}
319+
++$idx;
320+
}
321+
322+
return $colorScale;
323+
}
324+
281325
/**
282326
* @param SimpleXMLElement|stdClass $cfRule
283327
*/

src/PhpSpreadsheet/Style/Conditional.php

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PhpOffice\PhpSpreadsheet\Style;
44

55
use PhpOffice\PhpSpreadsheet\IComparable;
6+
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalColorScale;
67
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar;
78

89
class Conditional implements IComparable
@@ -11,6 +12,7 @@ class Conditional implements IComparable
1112
const CONDITION_NONE = 'none';
1213
const CONDITION_BEGINSWITH = 'beginsWith';
1314
const CONDITION_CELLIS = 'cellIs';
15+
const CONDITION_COLORSCALE = 'colorScale';
1416
const CONDITION_CONTAINSBLANKS = 'containsBlanks';
1517
const CONDITION_CONTAINSERRORS = 'containsErrors';
1618
const CONDITION_CONTAINSTEXT = 'containsText';
@@ -27,6 +29,7 @@ class Conditional implements IComparable
2729
private const CONDITION_TYPES = [
2830
self::CONDITION_BEGINSWITH,
2931
self::CONDITION_CELLIS,
32+
self::CONDITION_COLORSCALE,
3033
self::CONDITION_CONTAINSBLANKS,
3134
self::CONDITION_CONTAINSERRORS,
3235
self::CONDITION_CONTAINSTEXT,
@@ -91,10 +94,8 @@ class Conditional implements IComparable
9194

9295
/**
9396
* Stop on this condition, if it matches.
94-
*
95-
* @var bool
9697
*/
97-
private $stopIfTrue = false;
98+
private bool $stopIfTrue = false;
9899

99100
/**
100101
* Condition.
@@ -103,18 +104,13 @@ class Conditional implements IComparable
103104
*/
104105
private $condition = [];
105106

106-
/**
107-
* @var ConditionalDataBar
108-
*/
109-
private $dataBar;
107+
private ?ConditionalDataBar $dataBar = null;
108+
109+
private ?ConditionalColorScale $colorScale = null;
110110

111-
/**
112-
* Style.
113-
*/
114111
private Style $style;
115112

116-
/** @var bool */
117-
private $noFormatSet = false;
113+
private bool $noFormatSet = false;
118114

119115
/**
120116
* Create a new Conditional.
@@ -296,28 +292,30 @@ public function setStyle(Style $style): static
296292
return $this;
297293
}
298294

299-
/**
300-
* get DataBar.
301-
*
302-
* @return null|ConditionalDataBar
303-
*/
304-
public function getDataBar()
295+
public function getDataBar(): ?ConditionalDataBar
305296
{
306297
return $this->dataBar;
307298
}
308299

309-
/**
310-
* set DataBar.
311-
*
312-
* @return $this
313-
*/
314300
public function setDataBar(ConditionalDataBar $dataBar): static
315301
{
316302
$this->dataBar = $dataBar;
317303

318304
return $this;
319305
}
320306

307+
public function getColorScale(): ?ConditionalColorScale
308+
{
309+
return $this->colorScale;
310+
}
311+
312+
public function setColorScale(ConditionalColorScale $colorScale): static
313+
{
314+
$this->colorScale = $colorScale;
315+
316+
return $this;
317+
}
318+
321319
/**
322320
* Get hash code.
323321
*

0 commit comments

Comments
 (0)