Skip to content

Commit 0327b49

Browse files
committed
Added Conditional Formatting: IconSet for Xlsx (#4560)
1 parent ea97afb commit 0327b49

File tree

8 files changed

+329
-6
lines changed

8 files changed

+329
-6
lines changed

src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar;
1010
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormattingRuleExtension;
1111
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormatValueObject;
12+
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalIconSet;
13+
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\IconSetValues;
1214
use PhpOffice\PhpSpreadsheet\Style\Style as Style;
1315
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
1416
use SimpleXMLElement;
@@ -265,6 +267,8 @@ private function readStyleRules(array $cfRules, SimpleXMLElement $extLst): array
265267
$objConditional->setColorScale(
266268
$this->readColorScale($cfRule)
267269
);
270+
} elseif (isset($cfRule->iconSet)) {
271+
$objConditional->setIconSet($this->readIconSet($cfRule));
268272
} elseif (isset($cfRule['dxfId'])) {
269273
$objConditional->setStyle(clone $this->dxfs[(int) ($cfRule['dxfId'])]);
270274
}
@@ -349,6 +353,40 @@ private function readColorScale(SimpleXMLElement|stdClass $cfRule): ConditionalC
349353
return $colorScale;
350354
}
351355

356+
private function readIconSet(SimpleXMLElement $cfRule): ConditionalIconSet
357+
{
358+
$iconSet = new ConditionalIconSet();
359+
360+
if (isset($cfRule->iconSet['iconSet'])) {
361+
$iconSet->setIconSetType(IconSetValues::from($cfRule->iconSet['iconSet']));
362+
}
363+
if (isset($cfRule->iconSet['reverse'])) {
364+
$iconSet->setReverse('1' === (string) $cfRule->iconSet['reverse']);
365+
}
366+
if (isset($cfRule->iconSet['showValue'])) {
367+
$iconSet->setShowValue('1' === (string) $cfRule->iconSet['showValue']);
368+
}
369+
if (isset($cfRule->iconSet['custom'])) {
370+
$iconSet->setCustom('1' === (string) $cfRule->iconSet['custom']);
371+
}
372+
373+
$cfvos = [];
374+
foreach ($cfRule->iconSet->cfvo as $cfvoXml) {
375+
$type = (string) $cfvoXml['type'];
376+
$value = (string) ($cfvoXml['val'] ?? '');
377+
$cfvo = new ConditionalFormatValueObject($type, $value);
378+
if (isset($cfvoXml['gte'])) {
379+
$cfvo->setGreaterThanOrEqual('1' === (string) $cfvoXml['gte']);
380+
}
381+
$cfvos[] = $cfvo;
382+
}
383+
$iconSet->setCfvos($cfvos);
384+
385+
// TODO: The cfIcon element is not implemented yet.
386+
387+
return $iconSet;
388+
}
389+
352390
/** @param ConditionalFormattingRuleExtension[] $conditionalFormattingRuleExtensions */
353391
private function readDataBarExtLstOfConditionalRule(ConditionalDataBar $dataBar, SimpleXMLElement $cfRule, array $conditionalFormattingRuleExtensions): void
354392
{

src/PhpSpreadsheet/Style/Conditional.php

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PhpOffice\PhpSpreadsheet\IComparable;
66
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalColorScale;
77
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar;
8+
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalIconSet;
89

910
class Conditional implements IComparable
1011
{
@@ -25,6 +26,7 @@ class Conditional implements IComparable
2526
const CONDITION_TIMEPERIOD = 'timePeriod';
2627
const CONDITION_DUPLICATES = 'duplicateValues';
2728
const CONDITION_UNIQUE = 'uniqueValues';
29+
const CONDITION_ICONSET = 'iconSet';
2830

2931
private const CONDITION_TYPES = [
3032
self::CONDITION_BEGINSWITH,
@@ -43,6 +45,7 @@ class Conditional implements IComparable
4345
self::CONDITION_NOTCONTAINSTEXT,
4446
self::CONDITION_TIMEPERIOD,
4547
self::CONDITION_UNIQUE,
48+
self::CONDITION_ICONSET,
4649
];
4750

4851
// Operator types
@@ -102,6 +105,8 @@ class Conditional implements IComparable
102105

103106
private ?ConditionalColorScale $colorScale = null;
104107

108+
private ?ConditionalIconSet $iconSet = null;
109+
105110
private Style $style;
106111

107112
private bool $noFormatSet = false;
@@ -318,6 +323,18 @@ public function setColorScale(ConditionalColorScale $colorScale): static
318323
return $this;
319324
}
320325

326+
public function getIconSet(): ?ConditionalIconSet
327+
{
328+
return $this->iconSet;
329+
}
330+
331+
public function setIconSet(ConditionalIconSet $iconSet): static
332+
{
333+
$this->iconSet = $iconSet;
334+
335+
return $this;
336+
}
337+
321338
/**
322339
* Get hash code.
323340
*
@@ -327,10 +344,10 @@ public function getHashCode(): string
327344
{
328345
return md5(
329346
$this->conditionType
330-
. $this->operatorType
331-
. implode(';', $this->condition)
332-
. $this->style->getHashCode()
333-
. __CLASS__
347+
. $this->operatorType
348+
. implode(';', $this->condition)
349+
. $this->style->getHashCode()
350+
. __CLASS__
334351
);
335352
}
336353

src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ class ConditionalFormatValueObject
1010

1111
private ?string $cellFormula;
1212

13+
/**
14+
* For icon sets, determines whether this threshold value uses the greater
15+
* than or equal to operator. False indicates 'greater than' is used instead
16+
* of 'greater than or equal to'.
17+
*/
18+
private ?bool $greaterThanOrEqual = null;
19+
1320
public function __construct(string $type, null|float|int|string $value = null, ?string $cellFormula = null)
1421
{
1522
$this->type = $type;
@@ -52,4 +59,16 @@ public function setCellFormula(?string $cellFormula): self
5259

5360
return $this;
5461
}
62+
63+
public function getGreaterThanOrEqual(): ?bool
64+
{
65+
return $this->greaterThanOrEqual;
66+
}
67+
68+
public function setGreaterThanOrEqual(?bool $greaterThanOrEqual): self
69+
{
70+
$this->greaterThanOrEqual = $greaterThanOrEqual;
71+
72+
return $this;
73+
}
5574
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting;
4+
5+
class ConditionalIconSet
6+
{
7+
/** The icon set to display. */
8+
private ?IconSetValues $iconSetType = null;
9+
10+
/** If true, reverses the default order of the icons in this icon set. */
11+
private ?bool $reverse = null;
12+
13+
/** Indicates whether to show the values of the cells on which this icon set is applied. */
14+
private ?bool $showValue = null;
15+
16+
/**
17+
* If true, indicates that the icon set is a custom icon set.
18+
* If this value is "true", there MUST be the same number of cfIcon elements
19+
* as cfvo elements.
20+
* If this value is "false", there MUST be 0 cfIcon elements.
21+
*/
22+
private ?bool $custom = null;
23+
24+
/** @var ConditionalFormatValueObject[] */
25+
private array $cfvos = [];
26+
27+
public function getIconSetType(): ?IconSetValues
28+
{
29+
return $this->iconSetType;
30+
}
31+
32+
public function setIconSetType(IconSetValues $type): self
33+
{
34+
$this->iconSetType = $type;
35+
36+
return $this;
37+
}
38+
39+
public function getReverse(): ?bool
40+
{
41+
return $this->reverse;
42+
}
43+
44+
public function setReverse(bool $reverse): self
45+
{
46+
$this->reverse = $reverse;
47+
48+
return $this;
49+
}
50+
51+
public function getShowValue(): ?bool
52+
{
53+
return $this->showValue;
54+
}
55+
56+
public function setShowValue(bool $showValue): self
57+
{
58+
$this->showValue = $showValue;
59+
60+
return $this;
61+
}
62+
63+
public function getCustom(): ?bool
64+
{
65+
return $this->custom;
66+
}
67+
68+
public function setCustom(bool $custom): self
69+
{
70+
$this->custom = $custom;
71+
72+
return $this;
73+
}
74+
75+
/**
76+
* Get the conditional format value objects.
77+
*
78+
* @return ConditionalFormatValueObject[]
79+
*/
80+
public function getCfvos(): array
81+
{
82+
return $this->cfvos;
83+
}
84+
85+
/**
86+
* Set the conditional format value objects.
87+
*
88+
* @param ConditionalFormatValueObject[] $cfvos
89+
*/
90+
public function setCfvos(array $cfvos): self
91+
{
92+
$this->cfvos = $cfvos;
93+
94+
return $this;
95+
}
96+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting;
4+
5+
enum IconSetValues: string
6+
{
7+
case ThreeArrows = '3Arrows';
8+
case ThreeArrowsGray = '3ArrowsGray';
9+
case ThreeFlags = '3Flags';
10+
case ThreeTrafficLights1 = '3TrafficLights1';
11+
case ThreeTrafficLights2 = '3TrafficLights2';
12+
case ThreeSigns = '3Signs';
13+
case ThreeSymbols = '3Symbols';
14+
case ThreeSymbols2 = '3Symbols2';
15+
case FourArrows = '4Arrows';
16+
case FourArrowsGray = '4ArrowsGray';
17+
case FourRedToBlack = '4RedToBlack';
18+
case FourRating = '4Rating';
19+
case FourTrafficLights = '4TrafficLights';
20+
case FiveArrows = '5Arrows';
21+
case FiveArrowsGray = '5ArrowsGray';
22+
case FiveRating = '5Rating';
23+
case FiveQuarters = '5Quarters';
24+
// The following icon sets are not implemented yet:
25+
// case ThreeStars = "3Stars";
26+
// case ThreeTriangles = "3Triangles";
27+
// case FiveBoxes = "5Boxes";
28+
}

src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalColorScale;
1717
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar;
1818
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormattingRuleExtension;
19+
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalIconSet;
1920
use PhpOffice\PhpSpreadsheet\Style\Font;
2021
use PhpOffice\PhpSpreadsheet\Worksheet\RowDimension;
2122
use PhpOffice\PhpSpreadsheet\Worksheet\SheetView;
@@ -863,6 +864,47 @@ private static function writeColorScaleElements(XMLWriter $objWriter, ?Condition
863864
}
864865
}
865866

867+
private function writeIconSetElements(XMLWriter $objWriter, ?ConditionalIconSet $iconSet): void
868+
{
869+
if ($iconSet === null) {
870+
return;
871+
}
872+
873+
$objWriter->startElement('iconSet');
874+
if ($iconSet->getIconSetType() !== null) {
875+
$objWriter->writeAttribute('iconSet', $iconSet->getIconSetType()->value);
876+
}
877+
foreach (
878+
[
879+
'reverse' => $iconSet->getReverse(),
880+
'showValue' => $iconSet->getShowValue(),
881+
'custom' => $iconSet->getCustom(),
882+
] as $attr => $value
883+
) {
884+
self::writeAttributeIf($objWriter, $value !== null, $attr, $value ? '1' : '0');
885+
}
886+
887+
foreach ($iconSet->getCfvos() as $cfvo) {
888+
$objWriter->startElement('cfvo');
889+
$objWriter->writeAttribute('type', $cfvo->getType());
890+
self::writeAttributeIf(
891+
$objWriter,
892+
$cfvo->getValue() !== null,
893+
'val',
894+
(string) $cfvo->getValue(),
895+
);
896+
self::writeAttributeIf(
897+
$objWriter,
898+
$cfvo->getGreaterThanOrEqual() !== null,
899+
'gte',
900+
$cfvo->getGreaterThanOrEqual() ? '1' : '0',
901+
);
902+
$objWriter->endElement(); // end cfvo
903+
}
904+
905+
$objWriter->endElement(); // end iconSet
906+
}
907+
866908
/**
867909
* Write ConditionalFormatting.
868910
*/
@@ -897,6 +939,7 @@ private function writeConditionalFormatting(XMLWriter $objWriter, Phpspreadsheet
897939
$objWriter,
898940
($conditional->getConditionType() !== Conditional::CONDITION_COLORSCALE
899941
&& $conditional->getConditionType() !== Conditional::CONDITION_DATABAR
942+
&& $conditional->getConditionType() !== Conditional::CONDITION_ICONSET
900943
&& $conditional->getNoFormatSet() === false),
901944
'dxfId',
902945
(string) $this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode())
@@ -933,6 +976,8 @@ private function writeConditionalFormatting(XMLWriter $objWriter, Phpspreadsheet
933976
self::writeTimePeriodCondElements($objWriter, $conditional, $topLeftCell);
934977
} elseif ($conditional->getConditionType() === Conditional::CONDITION_COLORSCALE) {
935978
self::writeColorScaleElements($objWriter, $conditional->getColorScale());
979+
} elseif ($conditional->getConditionType() === Conditional::CONDITION_ICONSET) {
980+
self::writeIconSetElements($objWriter, $conditional->getIconSet());
936981
} else {
937982
self::writeOtherCondElements($objWriter, $conditional, $topLeftCell);
938983
}
@@ -1566,8 +1611,8 @@ private function writeCellFormula(XMLWriter $objWriter, string $cellValue, Cell
15661611
self::writeElementIf(
15671612
$objWriter,
15681613
$this->getParentWriter()->getOffice2003Compatibility() === false
1569-
&& $this->getParentWriter()->getPreCalculateFormulas()
1570-
&& $calculatedValue !== null,
1614+
&& $this->getParentWriter()->getPreCalculateFormulas()
1615+
&& $calculatedValue !== null,
15711616
'v',
15721617
(!is_array($calculatedValue) && !str_starts_with($calculatedValueString, '#'))
15731618
? StringHelper::formatNumber($calculatedValueString) : '0'

0 commit comments

Comments
 (0)