mirror of
https://github.com/PHPOffice/PhpSpreadsheet.git
synced 2026-09-04 06:33:33 +00:00
Added Conditional Formatting: IconSet for Xlsx (#4560)
This commit is contained in:
@@ -9,6 +9,8 @@ use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalColorScale;
|
||||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar;
|
||||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormattingRuleExtension;
|
||||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormatValueObject;
|
||||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalIconSet;
|
||||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\IconSetValues;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Style as Style;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use SimpleXMLElement;
|
||||
@@ -265,6 +267,8 @@ class ConditionalStyles
|
||||
$objConditional->setColorScale(
|
||||
$this->readColorScale($cfRule)
|
||||
);
|
||||
} elseif (isset($cfRule->iconSet)) {
|
||||
$objConditional->setIconSet($this->readIconSet($cfRule));
|
||||
} elseif (isset($cfRule['dxfId'])) {
|
||||
$objConditional->setStyle(clone $this->dxfs[(int) ($cfRule['dxfId'])]);
|
||||
}
|
||||
@@ -349,6 +353,40 @@ class ConditionalStyles
|
||||
return $colorScale;
|
||||
}
|
||||
|
||||
private function readIconSet(SimpleXMLElement $cfRule): ConditionalIconSet
|
||||
{
|
||||
$iconSet = new ConditionalIconSet();
|
||||
|
||||
if (isset($cfRule->iconSet['iconSet'])) {
|
||||
$iconSet->setIconSetType(IconSetValues::from($cfRule->iconSet['iconSet']));
|
||||
}
|
||||
if (isset($cfRule->iconSet['reverse'])) {
|
||||
$iconSet->setReverse('1' === (string) $cfRule->iconSet['reverse']);
|
||||
}
|
||||
if (isset($cfRule->iconSet['showValue'])) {
|
||||
$iconSet->setShowValue('1' === (string) $cfRule->iconSet['showValue']);
|
||||
}
|
||||
if (isset($cfRule->iconSet['custom'])) {
|
||||
$iconSet->setCustom('1' === (string) $cfRule->iconSet['custom']);
|
||||
}
|
||||
|
||||
$cfvos = [];
|
||||
foreach ($cfRule->iconSet->cfvo as $cfvoXml) {
|
||||
$type = (string) $cfvoXml['type'];
|
||||
$value = (string) ($cfvoXml['val'] ?? '');
|
||||
$cfvo = new ConditionalFormatValueObject($type, $value);
|
||||
if (isset($cfvoXml['gte'])) {
|
||||
$cfvo->setGreaterThanOrEqual('1' === (string) $cfvoXml['gte']);
|
||||
}
|
||||
$cfvos[] = $cfvo;
|
||||
}
|
||||
$iconSet->setCfvos($cfvos);
|
||||
|
||||
// TODO: The cfIcon element is not implemented yet.
|
||||
|
||||
return $iconSet;
|
||||
}
|
||||
|
||||
/** @param ConditionalFormattingRuleExtension[] $conditionalFormattingRuleExtensions */
|
||||
private function readDataBarExtLstOfConditionalRule(ConditionalDataBar $dataBar, SimpleXMLElement $cfRule, array $conditionalFormattingRuleExtensions): void
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace PhpOffice\PhpSpreadsheet\Style;
|
||||
use PhpOffice\PhpSpreadsheet\IComparable;
|
||||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalColorScale;
|
||||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar;
|
||||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalIconSet;
|
||||
|
||||
class Conditional implements IComparable
|
||||
{
|
||||
@@ -25,6 +26,7 @@ class Conditional implements IComparable
|
||||
const CONDITION_TIMEPERIOD = 'timePeriod';
|
||||
const CONDITION_DUPLICATES = 'duplicateValues';
|
||||
const CONDITION_UNIQUE = 'uniqueValues';
|
||||
const CONDITION_ICONSET = 'iconSet';
|
||||
|
||||
private const CONDITION_TYPES = [
|
||||
self::CONDITION_BEGINSWITH,
|
||||
@@ -43,6 +45,7 @@ class Conditional implements IComparable
|
||||
self::CONDITION_NOTCONTAINSTEXT,
|
||||
self::CONDITION_TIMEPERIOD,
|
||||
self::CONDITION_UNIQUE,
|
||||
self::CONDITION_ICONSET,
|
||||
];
|
||||
|
||||
// Operator types
|
||||
@@ -102,6 +105,8 @@ class Conditional implements IComparable
|
||||
|
||||
private ?ConditionalColorScale $colorScale = null;
|
||||
|
||||
private ?ConditionalIconSet $iconSet = null;
|
||||
|
||||
private Style $style;
|
||||
|
||||
private bool $noFormatSet = false;
|
||||
@@ -318,6 +323,18 @@ class Conditional implements IComparable
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIconSet(): ?ConditionalIconSet
|
||||
{
|
||||
return $this->iconSet;
|
||||
}
|
||||
|
||||
public function setIconSet(ConditionalIconSet $iconSet): static
|
||||
{
|
||||
$this->iconSet = $iconSet;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hash code.
|
||||
*
|
||||
@@ -327,10 +344,10 @@ class Conditional implements IComparable
|
||||
{
|
||||
return md5(
|
||||
$this->conditionType
|
||||
. $this->operatorType
|
||||
. implode(';', $this->condition)
|
||||
. $this->style->getHashCode()
|
||||
. __CLASS__
|
||||
. $this->operatorType
|
||||
. implode(';', $this->condition)
|
||||
. $this->style->getHashCode()
|
||||
. __CLASS__
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,13 @@ class ConditionalFormatValueObject
|
||||
|
||||
private ?string $cellFormula;
|
||||
|
||||
/**
|
||||
* For icon sets, determines whether this threshold value uses the greater
|
||||
* than or equal to operator. False indicates 'greater than' is used instead
|
||||
* of 'greater than or equal to'.
|
||||
*/
|
||||
private ?bool $greaterThanOrEqual = null;
|
||||
|
||||
public function __construct(string $type, null|float|int|string $value = null, ?string $cellFormula = null)
|
||||
{
|
||||
$this->type = $type;
|
||||
@@ -52,4 +59,16 @@ class ConditionalFormatValueObject
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getGreaterThanOrEqual(): ?bool
|
||||
{
|
||||
return $this->greaterThanOrEqual;
|
||||
}
|
||||
|
||||
public function setGreaterThanOrEqual(?bool $greaterThanOrEqual): self
|
||||
{
|
||||
$this->greaterThanOrEqual = $greaterThanOrEqual;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting;
|
||||
|
||||
class ConditionalIconSet
|
||||
{
|
||||
/** The icon set to display. */
|
||||
private ?IconSetValues $iconSetType = null;
|
||||
|
||||
/** If true, reverses the default order of the icons in this icon set. */
|
||||
private ?bool $reverse = null;
|
||||
|
||||
/** Indicates whether to show the values of the cells on which this icon set is applied. */
|
||||
private ?bool $showValue = null;
|
||||
|
||||
/**
|
||||
* If true, indicates that the icon set is a custom icon set.
|
||||
* If this value is "true", there MUST be the same number of cfIcon elements
|
||||
* as cfvo elements.
|
||||
* If this value is "false", there MUST be 0 cfIcon elements.
|
||||
*/
|
||||
private ?bool $custom = null;
|
||||
|
||||
/** @var ConditionalFormatValueObject[] */
|
||||
private array $cfvos = [];
|
||||
|
||||
public function getIconSetType(): ?IconSetValues
|
||||
{
|
||||
return $this->iconSetType;
|
||||
}
|
||||
|
||||
public function setIconSetType(IconSetValues $type): self
|
||||
{
|
||||
$this->iconSetType = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getReverse(): ?bool
|
||||
{
|
||||
return $this->reverse;
|
||||
}
|
||||
|
||||
public function setReverse(bool $reverse): self
|
||||
{
|
||||
$this->reverse = $reverse;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getShowValue(): ?bool
|
||||
{
|
||||
return $this->showValue;
|
||||
}
|
||||
|
||||
public function setShowValue(bool $showValue): self
|
||||
{
|
||||
$this->showValue = $showValue;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCustom(): ?bool
|
||||
{
|
||||
return $this->custom;
|
||||
}
|
||||
|
||||
public function setCustom(bool $custom): self
|
||||
{
|
||||
$this->custom = $custom;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the conditional format value objects.
|
||||
*
|
||||
* @return ConditionalFormatValueObject[]
|
||||
*/
|
||||
public function getCfvos(): array
|
||||
{
|
||||
return $this->cfvos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the conditional format value objects.
|
||||
*
|
||||
* @param ConditionalFormatValueObject[] $cfvos
|
||||
*/
|
||||
public function setCfvos(array $cfvos): self
|
||||
{
|
||||
$this->cfvos = $cfvos;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting;
|
||||
|
||||
enum IconSetValues: string
|
||||
{
|
||||
case ThreeArrows = '3Arrows';
|
||||
case ThreeArrowsGray = '3ArrowsGray';
|
||||
case ThreeFlags = '3Flags';
|
||||
case ThreeTrafficLights1 = '3TrafficLights1';
|
||||
case ThreeTrafficLights2 = '3TrafficLights2';
|
||||
case ThreeSigns = '3Signs';
|
||||
case ThreeSymbols = '3Symbols';
|
||||
case ThreeSymbols2 = '3Symbols2';
|
||||
case FourArrows = '4Arrows';
|
||||
case FourArrowsGray = '4ArrowsGray';
|
||||
case FourRedToBlack = '4RedToBlack';
|
||||
case FourRating = '4Rating';
|
||||
case FourTrafficLights = '4TrafficLights';
|
||||
case FiveArrows = '5Arrows';
|
||||
case FiveArrowsGray = '5ArrowsGray';
|
||||
case FiveRating = '5Rating';
|
||||
case FiveQuarters = '5Quarters';
|
||||
// The following icon sets are not implemented yet:
|
||||
// case ThreeStars = "3Stars";
|
||||
// case ThreeTriangles = "3Triangles";
|
||||
// case FiveBoxes = "5Boxes";
|
||||
}
|
||||
@@ -16,6 +16,7 @@ use PhpOffice\PhpSpreadsheet\Style\Conditional;
|
||||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalColorScale;
|
||||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar;
|
||||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormattingRuleExtension;
|
||||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalIconSet;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Font;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\RowDimension;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\SheetView;
|
||||
@@ -863,6 +864,47 @@ class Worksheet extends WriterPart
|
||||
}
|
||||
}
|
||||
|
||||
private function writeIconSetElements(XMLWriter $objWriter, ?ConditionalIconSet $iconSet): void
|
||||
{
|
||||
if ($iconSet === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$objWriter->startElement('iconSet');
|
||||
if ($iconSet->getIconSetType() !== null) {
|
||||
$objWriter->writeAttribute('iconSet', $iconSet->getIconSetType()->value);
|
||||
}
|
||||
foreach (
|
||||
[
|
||||
'reverse' => $iconSet->getReverse(),
|
||||
'showValue' => $iconSet->getShowValue(),
|
||||
'custom' => $iconSet->getCustom(),
|
||||
] as $attr => $value
|
||||
) {
|
||||
self::writeAttributeIf($objWriter, $value !== null, $attr, $value ? '1' : '0');
|
||||
}
|
||||
|
||||
foreach ($iconSet->getCfvos() as $cfvo) {
|
||||
$objWriter->startElement('cfvo');
|
||||
$objWriter->writeAttribute('type', $cfvo->getType());
|
||||
self::writeAttributeIf(
|
||||
$objWriter,
|
||||
$cfvo->getValue() !== null,
|
||||
'val',
|
||||
(string) $cfvo->getValue(),
|
||||
);
|
||||
self::writeAttributeIf(
|
||||
$objWriter,
|
||||
$cfvo->getGreaterThanOrEqual() !== null,
|
||||
'gte',
|
||||
$cfvo->getGreaterThanOrEqual() ? '1' : '0',
|
||||
);
|
||||
$objWriter->endElement(); // end cfvo
|
||||
}
|
||||
|
||||
$objWriter->endElement(); // end iconSet
|
||||
}
|
||||
|
||||
/**
|
||||
* Write ConditionalFormatting.
|
||||
*/
|
||||
@@ -897,6 +939,7 @@ class Worksheet extends WriterPart
|
||||
$objWriter,
|
||||
($conditional->getConditionType() !== Conditional::CONDITION_COLORSCALE
|
||||
&& $conditional->getConditionType() !== Conditional::CONDITION_DATABAR
|
||||
&& $conditional->getConditionType() !== Conditional::CONDITION_ICONSET
|
||||
&& $conditional->getNoFormatSet() === false),
|
||||
'dxfId',
|
||||
(string) $this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode())
|
||||
@@ -933,6 +976,8 @@ class Worksheet extends WriterPart
|
||||
self::writeTimePeriodCondElements($objWriter, $conditional, $topLeftCell);
|
||||
} elseif ($conditional->getConditionType() === Conditional::CONDITION_COLORSCALE) {
|
||||
self::writeColorScaleElements($objWriter, $conditional->getColorScale());
|
||||
} elseif ($conditional->getConditionType() === Conditional::CONDITION_ICONSET) {
|
||||
self::writeIconSetElements($objWriter, $conditional->getIconSet());
|
||||
} else {
|
||||
self::writeOtherCondElements($objWriter, $conditional, $topLeftCell);
|
||||
}
|
||||
@@ -1566,8 +1611,8 @@ class Worksheet extends WriterPart
|
||||
self::writeElementIf(
|
||||
$objWriter,
|
||||
$this->getParentWriter()->getOffice2003Compatibility() === false
|
||||
&& $this->getParentWriter()->getPreCalculateFormulas()
|
||||
&& $calculatedValue !== null,
|
||||
&& $this->getParentWriter()->getPreCalculateFormulas()
|
||||
&& $calculatedValue !== null,
|
||||
'v',
|
||||
(!is_array($calculatedValue) && !str_starts_with($calculatedValueString, '#'))
|
||||
? StringHelper::formatNumber($calculatedValueString) : '0'
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Reader\Xlsx;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\IconSetValues;
|
||||
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
|
||||
|
||||
class ConditionalIconSetTest extends AbstractFunctional
|
||||
{
|
||||
public function testIconSet(): void
|
||||
{
|
||||
$filename = 'tests/data/Reader/XLSX/conditionalFormattingIconSet.xlsx';
|
||||
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx');
|
||||
$spreadsheet = $reader->load($filename);
|
||||
$reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx');
|
||||
$spreadsheet->disconnectWorksheets();
|
||||
$worksheet = $reloadedSpreadsheet->getActiveSheet();
|
||||
|
||||
$columnIndex = 'A';
|
||||
foreach (IconSetValues::cases() as $iconSetValue) {
|
||||
// styles
|
||||
$styles = $worksheet->getConditionalStyles("{$columnIndex}2:{$columnIndex}11");
|
||||
self::assertCount(1, $styles);
|
||||
|
||||
// icon set
|
||||
$iconSet = $styles[0]->getIconSet();
|
||||
self::assertNotNull($iconSet);
|
||||
self::assertSame($iconSetValue, $iconSet->getIconSetType() ?? IconSetValues::ThreeTrafficLights1);
|
||||
|
||||
++$columnIndex;
|
||||
}
|
||||
|
||||
// icon set attributes
|
||||
$columnIndex = 'A';
|
||||
foreach (
|
||||
[
|
||||
['reverse' => false, 'showValue' => false],
|
||||
['reverse' => true, 'showValue' => false],
|
||||
['reverse' => false, 'showValue' => true],
|
||||
] as $expected
|
||||
) {
|
||||
$styles = $worksheet->getConditionalStyles("{$columnIndex}2:{$columnIndex}11");
|
||||
$iconSet = $styles[0]->getIconSet();
|
||||
self::assertNotNull($iconSet);
|
||||
self::assertSame($expected['reverse'], $iconSet->getReverse() ?? false);
|
||||
self::assertSame($expected['showValue'], $iconSet->getShowValue() ?? true);
|
||||
self::assertFalse($iconSet->getCustom() ?? false);
|
||||
|
||||
++$columnIndex;
|
||||
}
|
||||
|
||||
// cfvos
|
||||
$columnIndex = 'A';
|
||||
foreach (
|
||||
[
|
||||
[['percent', '0', true], ['percent', '33', false], ['percent', '67', true]],
|
||||
[['percent', '0', true], ['num', '3', false], ['num', '7', true]],
|
||||
[['percent', '0', true], ['formula', '10/3', false], ['formula', '10/2', true]],
|
||||
[['percent', '0', true], ['percentile', '33', false], ['percentile', '67', true]],
|
||||
] as $expected
|
||||
) {
|
||||
$styles = $worksheet->getConditionalStyles("{$columnIndex}2:{$columnIndex}11");
|
||||
$iconSet = $styles[0]->getIconSet();
|
||||
self::assertNotNull($iconSet);
|
||||
$cfvos = $iconSet->getCfvos();
|
||||
self::assertCount(count($expected), $cfvos);
|
||||
foreach ($expected as $i => [$type, $value, $gte]) {
|
||||
$cfvo = $cfvos[$i];
|
||||
self::assertSame($type, $cfvo->getType());
|
||||
self::assertSame($value, $cfvo->getValue());
|
||||
self::assertSame($gte, $cfvo->getGreaterThanOrEqual() ?? true);
|
||||
self::assertNull($cfvo->getCellFormula());
|
||||
}
|
||||
|
||||
++$columnIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Reference in New Issue
Block a user