From a86392662c84beeedfa1ef3d0eb2ca0a07482e4a Mon Sep 17 00:00:00 2001 From: Takahiro Morimoto Date: Sun, 13 Dec 2020 16:53:41 +0900 Subject: [PATCH] Support DataBar of conditional formatting rule Implemented the databar of Conditional Type. - DataBar can be read, written, and added for basic use. - Supports reading, writing and adding using "extLst". About "extLst" - https://docs.microsoft.com/en-us/openspecs/office_standards/ms-xlsx/07d607af-5618-4ca2-b683-6a78dc0d9627 The following setting items on the Excel setting screen can be read, written, and added. - (minimum, maximum)type: Automatic, LowestValue, Number, Percent, Formula, Percentile - Direction: context, leftToRight, rightToLeft (show data bar only) - Fills Solid, Gradient - FillColor: PositiveValues, NegativeValues - Borders: Solid, None - BorderColor: PositiveValues, NegativeValues - Axis position: Automatic, Midpoint, None - Axis color --- .../Reader/Xlsx/ConditionalStyles.php | 66 +++- src/PhpSpreadsheet/Style/Conditional.php | 29 ++ .../ConditionalDataBar.php | 112 ++++++ .../ConditionalDataBarExtension.php | 278 ++++++++++++++ .../ConditionalFormatValueObject.php | 80 ++++ .../ConditionalFormattingRuleExtension.php | 171 +++++++++ src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php | 121 +++++- .../ConditionalFormattingDataBarXlsxTest.php | 344 ++++++++++++++++++ .../conditionalFormattingDataBarTest.xlsx | Bin 0 -> 10443 bytes 9 files changed, 1189 insertions(+), 12 deletions(-) create mode 100644 src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php create mode 100644 src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php create mode 100644 src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php create mode 100644 src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php create mode 100644 tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php create mode 100644 tests/data/Reader/XLSX/conditionalFormattingDataBarTest.xlsx diff --git a/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php b/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php index 4aa48e17ce..5c1a9186c4 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php @@ -2,7 +2,10 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; +use PhpOffice\PhpSpreadsheet\Style\Color; use PhpOffice\PhpSpreadsheet\Style\Conditional; +use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar; +use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormattingRuleExtension; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use SimpleXMLElement; @@ -25,7 +28,8 @@ public function load(): void { $this->setConditionalStyles( $this->worksheet, - $this->readConditionalStyles($this->worksheetXml) + $this->readConditionalStyles($this->worksheetXml), + $this->worksheetXml->extLst ); } @@ -36,14 +40,16 @@ private function readConditionalStyles($xmlSheet) foreach ($conditional->cfRule as $cfRule) { if ( ((string) $cfRule['type'] == Conditional::CONDITION_NONE - || (string) $cfRule['type'] == Conditional::CONDITION_CELLIS - || (string) $cfRule['type'] == Conditional::CONDITION_CONTAINSTEXT - || (string) $cfRule['type'] == Conditional::CONDITION_CONTAINSBLANKS - || (string) $cfRule['type'] == Conditional::CONDITION_NOTCONTAINSBLANKS - || (string) $cfRule['type'] == Conditional::CONDITION_EXPRESSION) + || (string) $cfRule['type'] == Conditional::CONDITION_CELLIS + || (string) $cfRule['type'] == Conditional::CONDITION_CONTAINSTEXT + || (string) $cfRule['type'] == Conditional::CONDITION_CONTAINSBLANKS + || (string) $cfRule['type'] == Conditional::CONDITION_NOTCONTAINSBLANKS + || (string) $cfRule['type'] == Conditional::CONDITION_EXPRESSION) && isset($this->dxfs[(int) ($cfRule['dxfId'])]) ) { $conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule; + } elseif ((string) $cfRule['type'] == Conditional::CONDITION_DATABAR) { + $conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule; } } } @@ -51,11 +57,11 @@ private function readConditionalStyles($xmlSheet) return $conditionals; } - private function setConditionalStyles(Worksheet $worksheet, array $conditionals): void + private function setConditionalStyles(Worksheet $worksheet, array $conditionals, $xmlExtLst): void { foreach ($conditionals as $ref => $cfRules) { ksort($cfRules); - $conditionalStyles = $this->readStyleRules($cfRules); + $conditionalStyles = $this->readStyleRules($cfRules, $xmlExtLst); // Extract all cell references in $ref $cellBlocks = explode(' ', str_replace('$', '', strtoupper($ref))); @@ -65,8 +71,9 @@ private function setConditionalStyles(Worksheet $worksheet, array $conditionals) } } - private function readStyleRules($cfRules) + private function readStyleRules($cfRules, $extLst) { + $conditionalFormattingRuleExtensions = ConditionalFormattingRuleExtension::parseExtLstXml($extLst); $conditionalStyles = []; foreach ($cfRules as $cfRule) { $objConditional = new Conditional(); @@ -88,10 +95,49 @@ private function readStyleRules($cfRules) } else { $objConditional->addCondition((string) $cfRule->formula); } - $objConditional->setStyle(clone $this->dxfs[(int) ($cfRule['dxfId'])]); + + if (isset($cfRule->dataBar)) { + $objConditional->setDataBar($this->readDataBarOfConditionalRule($cfRule, $conditionalFormattingRuleExtensions)); + } else { + $objConditional->setStyle(clone $this->dxfs[(int) ($cfRule['dxfId'])]); + } + $conditionalStyles[] = $objConditional; } return $conditionalStyles; } + + private function readDataBarOfConditionalRule($cfRule, $conditionalFormattingRuleExtensions): ConditionalDataBar + { + $dataBar = new ConditionalDataBar(); + //dataBar attribute + if (isset($cfRule->dataBar['showValue'])) { + $dataBar->setShowValue((int) $cfRule->dataBar['showValue']); + } + + //dataBar children + //conditionalFormatValueObjects + $cfvoXml = $cfRule->dataBar->cfvo; + foreach ((count($cfvoXml) > 1 ? $cfvoXml : [$cfvoXml]) as $cfvo) { + $dataBar->addConditionalFormatValueObject((string) $cfvo['type'], (string) $cfvo['val']); + } + + //color + if (isset($cfRule->dataBar->color)) { + $dataBar->setColor(new Color((string) $cfRule->dataBar->color['rgb'])); + } + //extLst + if (isset($cfRule->extLst)) { + $ns = $cfRule->extLst->getNamespaces(true); + foreach ((count($cfRule->extLst) > 0 ? $cfRule->extLst->ext : [$cfRule->extLst->ext]) as $ext) { + $extId = (string) $ext->children($ns['x14'])->id; + if (isset($conditionalFormattingRuleExtensions[$extId]) && (string) $ext['uri'] === '{B025F937-C7B1-47D3-B67F-A62EFF666E3E}') { + $dataBar->addConditionalFormattingRuleExtList($conditionalFormattingRuleExtensions[$extId]); + } + } + } + + return $dataBar; + } } diff --git a/src/PhpSpreadsheet/Style/Conditional.php b/src/PhpSpreadsheet/Style/Conditional.php index 35ec479ba5..cc6532a32b 100644 --- a/src/PhpSpreadsheet/Style/Conditional.php +++ b/src/PhpSpreadsheet/Style/Conditional.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheet\Style; use PhpOffice\PhpSpreadsheet\IComparable; +use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar; class Conditional implements IComparable { @@ -13,6 +14,7 @@ class Conditional implements IComparable const CONDITION_EXPRESSION = 'expression'; const CONDITION_CONTAINSBLANKS = 'containsBlanks'; const CONDITION_NOTCONTAINSBLANKS = 'notContainsBlanks'; + const CONDITION_DATABAR = 'dataBar'; // Operator types const OPERATOR_NONE = ''; @@ -64,6 +66,11 @@ class Conditional implements IComparable */ private $condition = []; + /** + * @var ConditionalDataBar + */ + private $dataBar; + /** * Style. * @@ -241,6 +248,28 @@ public function setStyle(?Style $pValue = null) return $this; } + /** + * get DataBar. + * + * @return ConditionalDataBar | null + */ + public function getDataBar() + { + return $this->dataBar; + } + + /** + * set DataBar. + * + * @return $this + */ + public function setDataBar(ConditionalDataBar $dataBar) + { + $this->dataBar = $dataBar; + + return $this; + } + /** * Get hash code. * diff --git a/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php b/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php new file mode 100644 index 0000000000..66a0700b51 --- /dev/null +++ b/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php @@ -0,0 +1,112 @@ + attribute */ + private $showValue; + + /** children */ + + /** @var ConditionalFormatValueObject[] */ + private $conditionalFormatValueObjects = []; + + /** @var Color */ + private $color; + + /** @var ConditionalFormattingRuleExtension[] */ + private $conditionalFormattingRuleExtList = []; + + /** + * @return null|string + */ + public function getShowValue() + { + return $this->showValue; + } + + /** + * @param int $showValue + */ + public function setShowValue($showValue) + { + $this->showValue = $showValue; + + return $this; + } + + /** + * @return ConditionalFormatValueObject[] + */ + public function getConditionalFormatValueObjects(): array + { + return $this->conditionalFormatValueObjects; + } + + /** + * @param ConditionalFormatValueObject[] $conditionalFormatValueObjects + */ + public function setConditionalFormatValueObjects(array $conditionalFormatValueObjects) + { + $this->conditionalFormatValueObjects = $conditionalFormatValueObjects; + + return $this; + } + + /** + * @param mixed $type + * @param null|mixed $value + * @param null|mixed $cellFomula + * + * @return $this + */ + public function addConditionalFormatValueObject($type, $value = null, $cellFomula = null): self + { + $this->conditionalFormatValueObjects[] = new ConditionalFormatValueObject($type, $value, $cellFomula); + + return $this; + } + + public function getColor(): Color + { + return $this->color; + } + + public function setColor(Color $color): self + { + $this->color = $color; + + return $this; + } + + /** + * @return ConditionalFormattingRuleExtension[] + */ + public function getConditionalFormattingRuleExtList(): array + { + return $this->conditionalFormattingRuleExtList; + } + + /** + * @param ConditionalFormattingRuleExtension[] $conditionalFormattingRuleExtList + */ + public function setConditionalFormattingRuleExtList(array $conditionalFormattingRuleExtList): self + { + $this->conditionalFormattingRuleExtList = $conditionalFormattingRuleExtList; + + return $this; + } + + /** + * @return $this + */ + public function addConditionalFormattingRuleExtList(ConditionalFormattingRuleExtension $conditionalFormattingRuleExt): self + { + $this->conditionalFormattingRuleExtList[] = $conditionalFormattingRuleExt; + + return $this; + } +} diff --git a/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php b/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php new file mode 100644 index 0000000000..d23b3a0782 --- /dev/null +++ b/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php @@ -0,0 +1,278 @@ + attributes */ + + /** @var int */ + private $minLength; + + /** @var int */ + private $maxLength; + + /** @var int */ + private $border; + + /** @var int */ + private $gradient; + + /** @var string */ + private $direction; + + /** @var int */ + private $negativeBarBorderColorSameAsPositive; + + /** @var string */ + private $axisPosition; + + // children + + /** @var ConditionalFormatValueObject[] */ + private $conditionalFormatValueObjects = []; + + /** @var string */ + private $borderColor; + + /** @var string */ + private $negativeFillColor; + + /** @var string */ + private $negativeBorderColor; + + /** @var array */ + private $axisColor = [ + 'rgb' => null, + 'theme' => null, + 'tint' => null, + ]; + + public function getXmlAttributes() + { + $ret = []; + $attributes = [ + 'minLength', 'maxLength', 'border', 'gradient', 'direction', 'negativeBarBorderColorSameAsPositive', 'axisPosition', + ]; + foreach ($attributes as $attrKey) { + if (null !== $this->{$attrKey}) { + $ret[$attrKey] = $this->{$attrKey}; + } + } + + return $ret; + } + + public function getXmlElements() + { + $ret = []; + $elms = ['borderColor', 'negativeFillColor', 'negativeBorderColor']; + foreach ($elms as $elmKey) { + if (null !== $this->{$elmKey}) { + $ret[$elmKey] = ['rgb' => $this->{$elmKey}]; + } + } + foreach (array_filter($this->axisColor) as $attrKey => $axisColorAttr) { + if (!isset($ret['axisColor'])) { + $ret['axisColor'] = []; + } + $ret['axisColor'][$attrKey] = $axisColorAttr; + } + + return $ret; + } + + /** + * @return int + */ + public function getMinLength() + { + return $this->minLength; + } + + public function setMinLength(int $minLength): self + { + $this->minLength = $minLength; + + return $this; + } + + /** + * @return int + */ + public function getMaxLength() + { + return $this->maxLength; + } + + public function setMaxLength(int $maxLength): self + { + $this->maxLength = $maxLength; + + return $this; + } + + /** + * @return int + */ + public function getBorder() + { + return $this->border; + } + + public function setBorder(int $border): self + { + $this->border = $border; + + return $this; + } + + /** + * @return int + */ + public function getGradient() + { + return $this->gradient; + } + + public function setGradient(int $gradient): self + { + $this->gradient = $gradient; + + return $this; + } + + /** + * @return string + */ + public function getDirection() + { + return $this->direction; + } + + public function setDirection(string $direction): self + { + $this->direction = $direction; + + return $this; + } + + /** + * @return int + */ + public function getNegativeBarBorderColorSameAsPositive() + { + return $this->negativeBarBorderColorSameAsPositive; + } + + public function setNegativeBarBorderColorSameAsPositive(int $negativeBarBorderColorSameAsPositive): self + { + $this->negativeBarBorderColorSameAsPositive = $negativeBarBorderColorSameAsPositive; + + return $this; + } + + /** + * @return string + */ + public function getAxisPosition() + { + return $this->axisPosition; + } + + public function setAxisPosition(string $axisPosition): self + { + $this->axisPosition = $axisPosition; + + return $this; + } + + /** + * @return ConditionalFormatValueObject[] + */ + public function getConditionalFormatValueObjects(): array + { + return $this->conditionalFormatValueObjects; + } + + /** + * @param ConditionalFormatValueObject[] $conditionalFormatValueObjects + */ + public function setConditionalFormatValueObjects(array $conditionalFormatValueObjects): self + { + $this->conditionalFormatValueObjects = $conditionalFormatValueObjects; + + return $this; + } + + public function addConditionalFormatValueObject($type, $value = null, $cellFormula = null): void + { + $this->conditionalFormatValueObjects[] = new ConditionalFormatValueObject($type, $value, $cellFormula); + } + + /** + * @return string + */ + public function getBorderColor() + { + return $this->borderColor; + } + + public function setBorderColor(string $borderColor): self + { + $this->borderColor = $borderColor; + + return $this; + } + + /** + * @return string + */ + public function getNegativeFillColor() + { + return $this->negativeFillColor; + } + + public function setNegativeFillColor(string $negativeFillColor): self + { + $this->negativeFillColor = $negativeFillColor; + + return $this; + } + + /** + * @return string + */ + public function getNegativeBorderColor() + { + return $this->negativeBorderColor; + } + + public function setNegativeBorderColor(string $negativeBorderColor): self + { + $this->negativeBorderColor = $negativeBorderColor; + + return $this; + } + + public function getAxisColor(): array + { + return $this->axisColor; + } + + /** + * @param mixed $rgb + * @param null|mixed $theme + * @param null|mixed $tint + */ + public function setAxisColor($rgb, $theme = null, $tint = null): self + { + $this->axisColor = [ + 'rgb' => $rgb, + 'theme' => $theme, + 'tint' => $tint, + ]; + + return $this; + } +} diff --git a/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php b/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php new file mode 100644 index 0000000000..c6370b86b8 --- /dev/null +++ b/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php @@ -0,0 +1,80 @@ +type = $type; + $this->value = $value; + $this->cellFormula = $cellFormula; + } + + /** + * @return mixed + */ + public function getType() + { + return $this->type; + } + + /** + * @param mixed $type + */ + public function setType($type) + { + $this->type = $type; + + return $this; + } + + /** + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * @param mixed $value + */ + public function setValue($value) + { + $this->value = $value; + + return $this; + } + + /** + * @return mixed + */ + public function getCellFormula() + { + return $this->cellFormula; + } + + /** + * @param mixed $cellFormula + */ + public function setCellFormula($cellFormula) + { + $this->cellFormula = $cellFormula; + + return $this; + } +} diff --git a/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php b/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php new file mode 100644 index 0000000000..ce50a120f7 --- /dev/null +++ b/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php @@ -0,0 +1,171 @@ + attributes */ + private $id; + + /** @var string Conditional Formatting Rule */ + private $cfRule; + + /** children */ + + /** @var ConditionalDataBarExtension */ + private $dataBar; + + /** @var string Sequence of References */ + private $sqref; + + /** + * ConditionalFormattingRuleExtension constructor. + * + * @param $id + */ + public function __construct($id, string $cfRule = self::CONDITION_EXTENSION_DATABAR) + { + $this->id = $id; + $this->cfRule = $cfRule; + } + + public static function parseExtLstXml($extLstXml) + { + $conditionalFormattingRuleExtensions = []; + $conditionalFormattingRuleExtensionXml = null; + if ($extLstXml instanceof SimpleXMLElement) { + foreach ((count($extLstXml) > 0 ? $extLstXml : [$extLstXml]) as $extLst) { + //this uri is conditionalFormattings + //https://docs.microsoft.com/en-us/openspecs/office_standards/ms-xlsx/07d607af-5618-4ca2-b683-6a78dc0d9627 + if (isset($extLst->ext['uri']) && (string) $extLst->ext['uri'] === '{78C0D931-6437-407d-A8EE-F0AAD7539E65}') { + $conditionalFormattingRuleExtensionXml = $extLst->ext; + } + } + if ($conditionalFormattingRuleExtensionXml) { + $ns = $conditionalFormattingRuleExtensionXml->getNamespaces(true); + $extFormattingsXml = $conditionalFormattingRuleExtensionXml->children($ns['x14']); + + foreach ($extFormattingsXml->children($ns['x14']) as $extFormattingXml) { + $extCfRuleXml = $extFormattingXml->cfRule; + $extFormattingRuleObj = new self((string) $extCfRuleXml->attributes()->id); + $extFormattingRuleObj->setSqref((string) $extFormattingXml->children($ns['xm'])->sqref); + $conditionalFormattingRuleExtensions[$extFormattingRuleObj->getId()] = $extFormattingRuleObj; + + $extDataBarObj = new ConditionalDataBarExtension(); + $extFormattingRuleObj->setDataBar($extDataBarObj); + + $dataBarXml = $extCfRuleXml->dataBar; + self::parseExtDataBarAttributesFromXml($extDataBarObj, $dataBarXml); + self::parseExtDataBarElementChildrenFromXml($extDataBarObj, $dataBarXml, $ns); + } + } + } + + return $conditionalFormattingRuleExtensions; + } + + private static function parseExtDataBarAttributesFromXml(ConditionalDataBarExtension $extDataBarObj, SimpleXMLElement $dataBarXml): void + { + $dataBarAttribute = $dataBarXml->attributes(); + if ($dataBarAttribute->minLength) { + $extDataBarObj->setMinLength((int) $dataBarAttribute->minLength); + } + if ($dataBarAttribute->maxLength) { + $extDataBarObj->setMaxLength((int) $dataBarAttribute->maxLength); + } + if ($dataBarAttribute->border) { + $extDataBarObj->setBorder((int) $dataBarAttribute->border); + } + if ($dataBarAttribute->gradient) { + $extDataBarObj->setGradient((int) $dataBarAttribute->gradient); + } + if ($dataBarAttribute->direction) { + $extDataBarObj->setDirection((string) $dataBarAttribute->direction); + } + if ($dataBarAttribute->negativeBarBorderColorSameAsPositive) { + $extDataBarObj->setNegativeBarBorderColorSameAsPositive((int) $dataBarAttribute->negativeBarBorderColorSameAsPositive); + } + if ($dataBarAttribute->axisPosition) { + $extDataBarObj->setAxisPosition((string) $dataBarAttribute->axisPosition); + } + } + + private static function parseExtDataBarElementChildrenFromXml(ConditionalDataBarExtension $extDataBarObj, SimpleXMLElement $dataBarXml, $ns): void + { + if ($dataBarXml->borderColor) { + $extDataBarObj->setBorderColor((string) $dataBarXml->borderColor->attributes()['rgb']); + } + if ($dataBarXml->negativeFillColor) { + $extDataBarObj->setNegativeFillColor((string) $dataBarXml->negativeFillColor->attributes()['rgb']); + } + if ($dataBarXml->negativeBorderColor) { + $extDataBarObj->setNegativeBorderColor((string) $dataBarXml->negativeBorderColor->attributes()['rgb']); + } + if ($dataBarXml->axisColor) { + $axisColorAttr = $dataBarXml->axisColor->attributes(); + $extDataBarObj->setAxisColor((string) $axisColorAttr['rgb'], (string) $axisColorAttr['theme'], (string) $axisColorAttr['tint']); + } + foreach ($dataBarXml->cfvo as $cfvo) { + $f = (string) $cfvo->children($ns['xm'])->f; + $extDataBarObj->addConditionalFormatValueObject((string) $cfvo->attributes()['type'], null, (empty($f) ? null : $f)); + } + } + + /** + * @return mixed + */ + public function getId() + { + return $this->id; + } + + /** + * @param mixed $id + */ + public function setId($id): self + { + $this->id = $id; + + return $this; + } + + public function getCfRule(): string + { + return $this->cfRule; + } + + public function setCfRule(string $cfRule): self + { + $this->cfRule = $cfRule; + + return $this; + } + + public function getDataBar(): ConditionalDataBarExtension + { + return $this->dataBar; + } + + public function setDataBar(ConditionalDataBarExtension $dataBar): self + { + $this->dataBar = $dataBar; + + return $this; + } + + public function getSqref(): string + { + return $this->sqref; + } + + public function setSqref(string $sqref): self + { + $this->sqref = $sqref; + + return $this; + } +} diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index b6a6fc390b..cd588ef38a 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -8,6 +8,7 @@ use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; use PhpOffice\PhpSpreadsheet\Style\Conditional; +use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormattingRuleExtension; use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column; use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule; use PhpOffice\PhpSpreadsheet\Worksheet\SheetView; @@ -44,6 +45,7 @@ public function writeWorksheet(PhpspreadsheetWorksheet $pSheet, $pStringTable = $objWriter->writeAttribute('xmlns:xdr', 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing'); $objWriter->writeAttribute('xmlns:x14', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main'); + $objWriter->writeAttribute('xmlns:xm', 'http://schemas.microsoft.com/office/excel/2006/main'); $objWriter->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006'); $objWriter->writeAttribute('mc:Ignorable', 'x14ac'); $objWriter->writeAttribute('xmlns:x14ac', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac'); @@ -114,6 +116,10 @@ public function writeWorksheet(PhpspreadsheetWorksheet $pSheet, $pStringTable = // AlternateContent $this->writeAlternateContent($objWriter, $pSheet); + // ConditionalFormattingRuleExtensionList + // (Must be inserted last. Not insert last, an Excel parse error will occur) + $this->writeExtLst($objWriter, $pSheet); + $objWriter->endElement(); // Return @@ -503,6 +509,73 @@ private static function writeTextCondElements(XMLWriter $objWriter, Conditional } } + private static function writeExtConditionalFormattingElements(XMLWriter $objWriter, ConditionalFormattingRuleExtension $ruleExtension): void + { + $prefix = 'x14'; + $objWriter->startElementNs($prefix, 'conditionalFormatting', null); + + $objWriter->startElementNs($prefix, 'cfRule', null); + $objWriter->writeAttribute('type', $ruleExtension->getCfRule()); + $objWriter->writeAttribute('id', $ruleExtension->getId()); + $objWriter->startElementNs($prefix, 'dataBar', null); + $dataBar = $ruleExtension->getDataBar(); + foreach ($dataBar->getXmlAttributes() as $attrKey => $val) { + $objWriter->writeAttribute($attrKey, $val); + } + foreach ($dataBar->getConditionalFormatValueObjects() as $cfvo) { + $objWriter->startElementNs($prefix, 'cfvo', null); + $objWriter->writeAttribute('type', $cfvo->getType()); + if ($cfvo->getCellFormula()) { + $objWriter->writeElement('xm:f', $cfvo->getCellFormula()); + } + $objWriter->endElement(); //end cfvo + } + foreach ($dataBar->getXmlElements() as $elmKey => $elmAttr) { + $objWriter->startElementNs($prefix, $elmKey, null); + foreach ($elmAttr as $attrKey => $attrVal) { + $objWriter->writeAttribute($attrKey, $attrVal); + } + $objWriter->endElement(); //end elmKey + } + $objWriter->endElement(); //end dataBar + $objWriter->endElement(); //end cfRule + $objWriter->writeElement('xm:sqref', $ruleExtension->getSqref()); + $objWriter->endElement(); //end conditionalFormatting + } + + private static function writeDataBarElements(XMLWriter $objWriter, $dataBar): void + { + if ($dataBar) { + $objWriter->startElement('dataBar'); + self::writeAttributeIf($objWriter, null !== $dataBar->getShowValue(), 'showValue', (string) $dataBar->getShowValue()); + foreach ($dataBar->getConditionalFormatValueObjects() as $cfvo) { + $objWriter->startElement('cfvo'); + self::writeAttributeIf($objWriter, $cfvo->getType(), 'type', (string) $cfvo->getType()); + self::writeAttributeIf($objWriter, $cfvo->getValue(), 'val', (string) $cfvo->getValue()); + $objWriter->endElement(); + } + if ($dataBar->getColor()) { + $objWriter->startElement('color'); + $objWriter->writeAttribute('rgb', $dataBar->getColor()->getARGB()); + $objWriter->endElement(); + } + $objWriter->endElement(); // end dataBar + + if (count($dataBar->getConditionalFormattingRuleExtList()) > 0) { + $objWriter->startElement('extLst'); + foreach ($dataBar->getConditionalFormattingRuleExtList() as $extension) { + $objWriter->startElement('ext'); + $objWriter->writeAttribute('uri', '{B025F937-C7B1-47D3-B67F-A62EFF666E3E}'); + $objWriter->startElementNs('x14', 'id', null); + $objWriter->text($extension->getId()); + $objWriter->endElement(); + $objWriter->endElement(); + } + $objWriter->endElement(); //end extLst + } + } + } + /** * Write ConditionalFormatting. * @@ -529,7 +602,12 @@ private function writeConditionalFormatting(XMLWriter $objWriter, Phpspreadsheet // cfRule $objWriter->startElement('cfRule'); $objWriter->writeAttribute('type', $conditional->getConditionType()); - $objWriter->writeAttribute('dxfId', $this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode())); + self::writeAttributeIf( + $objWriter, + ($conditional->getConditionType() != Conditional::CONDITION_DATABAR), + 'dxfId', + $this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode()) + ); $objWriter->writeAttribute('priority', $id++); self::writeAttributeif( @@ -548,7 +626,10 @@ private function writeConditionalFormatting(XMLWriter $objWriter, Phpspreadsheet self::writeOtherCondElements($objWriter, $conditional, $cellCoordinate); } - $objWriter->endElement(); + // + self::writeDataBarElements($objWriter, $conditional->getDataBar()); + + $objWriter->endElement(); //end cfRule $objWriter->endElement(); } @@ -1279,4 +1360,40 @@ private function writeAlternateContent(XMLWriter $objWriter, PhpspreadsheetWorks $objWriter->writeRaw($alternateContent); } } + + /** + * write + * only implementation conditionalFormattings. + * + * @url https://docs.microsoft.com/en-us/openspecs/office_standards/ms-xlsx/07d607af-5618-4ca2-b683-6a78dc0d9627 + */ + private function writeExtLst(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void + { + $conditionalFormattingRuleExtList = []; + foreach ($pSheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) { + /** @var Conditional $conditional */ + foreach ($conditionalStyles as $conditional) { + $dataBar = $conditional->getDataBar(); + if ($dataBar && count($dataBar->getConditionalFormattingRuleExtList()) > 0) { + foreach ($dataBar->getConditionalFormattingRuleExtList() as $ext) { + $conditionalFormattingRuleExtList[] = $ext; + } + } + } + } + + if (count($conditionalFormattingRuleExtList) > 0) { + $conditionalFormattingRuleExtNsPrefix = 'x14'; + $objWriter->startElement('extLst'); + $objWriter->startElement('ext'); + $objWriter->writeAttribute('uri', '{78C0D931-6437-407d-A8EE-F0AAD7539E65}'); + $objWriter->startElementNs($conditionalFormattingRuleExtNsPrefix, 'conditionalFormattings', null); + foreach ($conditionalFormattingRuleExtList as $extension) { + self::writeExtConditionalFormattingElements($objWriter, $extension); + } + $objWriter->endElement(); //end conditionalFormattings + $objWriter->endElement(); //end ext + $objWriter->endElement(); //end extLst + } + } } diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php new file mode 100644 index 0000000000..f3107baaf4 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php @@ -0,0 +1,344 @@ +load($filename); + $worksheet = $spreadsheet->getActiveSheet(); + + $this->pattern1Assertion($worksheet); + $this->pattern2Assertion($worksheet); + $this->pattern3Assertion($worksheet); + $this->pattern4Assertion($worksheet); + } + + public function testReloadXlsxConditionalFormattingDataBar(): void + { + // Make sure conditionals from existing file are maintained across save + $filename = 'tests/data/Reader/XLSX/conditionalFormattingDataBarTest.xlsx'; + $outfile = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $reader = IOFactory::createReader('Xlsx'); + $spreadshee1 = $reader->load($filename); + $writer = IOFactory::createWriter($spreadshee1, 'Xlsx'); + $writer->save($outfile); + $spreadsheet = $reader->load($outfile); + unlink($outfile); + $worksheet = $spreadsheet->getActiveSheet(); + + $this->pattern1Assertion($worksheet); + $this->pattern2Assertion($worksheet); + $this->pattern3Assertion($worksheet); + $this->pattern4Assertion($worksheet); + } + + public function testNewXlsxConditionalFormattingDataBar(): void + { + // Make sure blanks/non-blanks added by PhpSpreadsheet are handled correctly + $outfile = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $spreadshee1 = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); + $sheet = $spreadshee1->getActiveSheet(); + $sheet->setCellValue('A1', 1); + $sheet->setCellValue('A2', 2); + $sheet->setCellValue('A3', 3); + $sheet->setCellValue('A4', 4); + $sheet->setCellValue('A5', 5); + $cond1 = new Conditional(); + $cond1->setConditionType(Conditional::CONDITION_DATABAR); + $cond1->setDataBar(new ConditionalDataBar()); + $cond1->getDataBar() + ->addConditionalFormatValueObject('min') + ->addConditionalFormatValueObject('max') + ->setColor(new Color(Color::COLOR_GREEN)); + $cond = [$cond1]; + $sheet->getStyle('A1:A5')->setConditionalStyles($cond); + $writer = IOFactory::createWriter($spreadshee1, 'Xlsx'); + $writer->save($outfile); + $reader = IOFactory::createReader('Xlsx'); + $spreadsheet = $reader->load($outfile); + unlink($outfile); + $worksheet = $spreadsheet->getActiveSheet(); + + $conditionalStyle = $worksheet->getConditionalStyles('A1:A5'); + self::assertNotEmpty($conditionalStyle); + /** @var Conditional $conditionalRule */ + $conditionalRule = $conditionalStyle[0]; + $conditions = $conditionalRule->getConditions(); + self::assertNotEmpty($conditions); + self::assertEquals(Conditional::CONDITION_DATABAR, $conditionalRule->getConditionType()); + self::assertNotEmpty($conditionalRule->getDataBar()); + + $dataBar = $conditionalRule->getDataBar(); + self::assertIsArray($dataBar->getConditionalFormatValueObjects()); + self::assertArrayHasKey(0, $dataBar->getConditionalFormatValueObjects()); + self::assertArrayHasKey(1, $dataBar->getConditionalFormatValueObjects()); + self::assertArrayNotHasKey(2, $dataBar->getConditionalFormatValueObjects()); + self::assertEquals('min', $dataBar->getConditionalFormatValueObjects()[0]->getType()); + self::assertEquals('max', $dataBar->getConditionalFormatValueObjects()[1]->getType()); + self::assertEquals(Color::COLOR_GREEN, $dataBar->getColor()->getARGB()); + } + + private function pattern1Assertion(Worksheet $worksheet): void + { + self::assertEquals( + "Type: Automatic, Automatic\nDirection: Automatic\nFills: Gradient\nAxis Position: Automatic", + $worksheet->getCell('A2')->getValue() + ); + + $conditionalStyle = $worksheet->getConditionalStyles('A3:A23'); + self::assertNotEmpty($conditionalStyle); + /** @var Conditional $conditionalRule */ + $conditionalRule = $conditionalStyle[0]; + $dataBar = $conditionalRule->getDataBar(); + + self::assertNotEmpty($dataBar); + self::assertEquals(Conditional::CONDITION_DATABAR, $conditionalRule->getConditionType()); + self::assertIsArray($dataBar->getConditionalFormatValueObjects()); + self::assertArrayHasKey(0, $dataBar->getConditionalFormatValueObjects()); + self::assertArrayHasKey(1, $dataBar->getConditionalFormatValueObjects()); + self::assertArrayNotHasKey(2, $dataBar->getConditionalFormatValueObjects()); + self::assertEquals('min', $dataBar->getConditionalFormatValueObjects()[0]->getType()); + self::assertEquals('max', $dataBar->getConditionalFormatValueObjects()[1]->getType()); + self::assertEquals('FF638EC6', $dataBar->getColor()->getARGB()); + self::assertNotEmpty($dataBar->getConditionalFormattingRuleExtList()); + self::assertArrayHasKey(0, $dataBar->getConditionalFormattingRuleExtList()); + self::assertArrayNotHasKey(1, $dataBar->getConditionalFormattingRuleExtList()); + //ext + $rule1ext = $dataBar->getConditionalFormattingRuleExtList()[0]; + self::assertEquals('{72C64AE0-5CD9-164F-83D1-AB720F263E79}', $rule1ext->getId()); + self::assertEquals('dataBar', $rule1ext->getCfRule()); + self::assertEquals('A3:A23', $rule1ext->getSqref()); + $extDataBar = $rule1ext->getDataBar(); + self::assertNotEmpty($extDataBar); + $pattern1 = [ + 'minLength' => '0', + 'maxLength' => '100', + 'border' => '1', + 'gradient' => null, + 'direction' => null, + 'axisPosition' => null, + 'negativeBarBorderColorSameAsPositive' => '0', + 'borderColor' => 'FF638EC6', + 'negativeFillColor' => 'FFFF0000', + 'negativeBorderColor' => 'FFFF0000', + ]; + foreach ($pattern1 as $key => $value) { + $funcName = 'get' . ucwords($key); + self::assertEquals($value, $extDataBar->$funcName(), $funcName . ' function patten'); + } + + self::assertIsArray($extDataBar->getConditionalFormatValueObjects()); + self::assertArrayHasKey(0, $extDataBar->getConditionalFormatValueObjects()); + self::assertArrayHasKey(1, $extDataBar->getConditionalFormatValueObjects()); + self::assertArrayNotHasKey(2, $extDataBar->getConditionalFormatValueObjects()); + + self::assertEquals('autoMin', $extDataBar->getConditionalFormatValueObjects()[0]->getType()); + self::assertEquals('autoMax', $extDataBar->getConditionalFormatValueObjects()[1]->getType()); + + self::assertArrayHasKey('rgb', $extDataBar->getAxisColor()); + self::assertEquals('FF000000', $extDataBar->getAxisColor()['rgb']); + } + + private function pattern2Assertion(Worksheet $worksheet): void + { + self::assertEquals( + "Type: Number, Number\nValue: -5, 5\nDirection: Automatic\nFills: Solid\nAxis Position: Automatic", + $worksheet->getCell('B2')->getValue() + ); + + $conditionalStyle = $worksheet->getConditionalStyles('B3:B23'); + self::assertNotEmpty($conditionalStyle); + /** @var Conditional $conditionalRule */ + $conditionalRule = $conditionalStyle[0]; + $dataBar = $conditionalRule->getDataBar(); + + self::assertNotEmpty($dataBar); + self::assertEquals(Conditional::CONDITION_DATABAR, $conditionalRule->getConditionType()); + self::assertIsArray($dataBar->getConditionalFormatValueObjects()); + self::assertArrayHasKey(0, $dataBar->getConditionalFormatValueObjects()); + self::assertArrayHasKey(1, $dataBar->getConditionalFormatValueObjects()); + self::assertArrayNotHasKey(2, $dataBar->getConditionalFormatValueObjects()); + self::assertEquals('num', $dataBar->getConditionalFormatValueObjects()[0]->getType()); + self::assertEquals('num', $dataBar->getConditionalFormatValueObjects()[1]->getType()); + self::assertEquals('-5', $dataBar->getConditionalFormatValueObjects()[0]->getValue()); + self::assertEquals('5', $dataBar->getConditionalFormatValueObjects()[1]->getValue()); + self::assertEquals('FF63C384', $dataBar->getColor()->getARGB()); + self::assertNotEmpty($dataBar->getConditionalFormattingRuleExtList()); + self::assertArrayHasKey(0, $dataBar->getConditionalFormattingRuleExtList()); + self::assertArrayNotHasKey(1, $dataBar->getConditionalFormattingRuleExtList()); + //ext + $rule1ext = $dataBar->getConditionalFormattingRuleExtList()[0]; + self::assertEquals('{98904F60-57F0-DF47-B480-691B20D325E3}', $rule1ext->getId()); + self::assertEquals('dataBar', $rule1ext->getCfRule()); + self::assertEquals('B3:B23', $rule1ext->getSqref()); + $extDataBar = $rule1ext->getDataBar(); + self::assertNotEmpty($extDataBar); + $pattern1 = [ + 'minLength' => '0', + 'maxLength' => '100', + 'border' => null, + 'gradient' => '0', + 'direction' => null, + 'axisPosition' => null, + 'negativeBarBorderColorSameAsPositive' => null, + 'borderColor' => null, + 'negativeFillColor' => 'FFFF0000', + 'negativeBorderColor' => null, + ]; + foreach ($pattern1 as $key => $value) { + $funcName = 'get' . ucwords($key); + self::assertEquals($value, $extDataBar->$funcName(), $funcName . ' function patten'); + } + + self::assertIsArray($extDataBar->getConditionalFormatValueObjects()); + self::assertArrayHasKey(0, $extDataBar->getConditionalFormatValueObjects()); + self::assertArrayHasKey(1, $extDataBar->getConditionalFormatValueObjects()); + self::assertArrayNotHasKey(2, $extDataBar->getConditionalFormatValueObjects()); + + self::assertEquals('num', $extDataBar->getConditionalFormatValueObjects()[0]->getType()); + self::assertEquals('num', $extDataBar->getConditionalFormatValueObjects()[1]->getType()); + self::assertEquals('-5', $extDataBar->getConditionalFormatValueObjects()[0]->getCellFormula()); + self::assertEquals('5', $extDataBar->getConditionalFormatValueObjects()[1]->getCellFormula()); + + self::assertArrayHasKey('rgb', $extDataBar->getAxisColor()); + self::assertEquals('FF000000', $extDataBar->getAxisColor()['rgb']); + } + + private function pattern3Assertion(Worksheet $worksheet): void + { + self::assertEquals( + "Type: Automatic, Automatic\nDirection: rightToLeft\nFills: Solid\nAxis Position: None", + $worksheet->getCell('C2')->getValue() + ); + + $conditionalStyle = $worksheet->getConditionalStyles('C3:C23'); + self::assertNotEmpty($conditionalStyle); + /** @var Conditional $conditionalRule */ + $conditionalRule = $conditionalStyle[0]; + $dataBar = $conditionalRule->getDataBar(); + + self::assertNotEmpty($dataBar); + self::assertEquals(Conditional::CONDITION_DATABAR, $conditionalRule->getConditionType()); + self::assertIsArray($dataBar->getConditionalFormatValueObjects()); + self::assertArrayHasKey(0, $dataBar->getConditionalFormatValueObjects()); + self::assertArrayHasKey(1, $dataBar->getConditionalFormatValueObjects()); + self::assertArrayNotHasKey(2, $dataBar->getConditionalFormatValueObjects()); + self::assertEquals('min', $dataBar->getConditionalFormatValueObjects()[0]->getType()); + self::assertEquals('max', $dataBar->getConditionalFormatValueObjects()[1]->getType()); + self::assertEquals('FFFF555A', $dataBar->getColor()->getARGB()); + self::assertNotEmpty($dataBar->getConditionalFormattingRuleExtList()); + self::assertArrayHasKey(0, $dataBar->getConditionalFormattingRuleExtList()); + self::assertArrayNotHasKey(1, $dataBar->getConditionalFormattingRuleExtList()); + //ext + $rule1ext = $dataBar->getConditionalFormattingRuleExtList()[0]; + self::assertEquals('{453C04BA-7ABD-8548-8A17-D9CFD2BDABE9}', $rule1ext->getId()); + self::assertEquals('dataBar', $rule1ext->getCfRule()); + self::assertEquals('C3:C23', $rule1ext->getSqref()); + $extDataBar = $rule1ext->getDataBar(); + self::assertNotEmpty($extDataBar); + $pattern1 = [ + 'minLength' => '0', + 'maxLength' => '100', + 'border' => null, + 'gradient' => '0', + 'direction' => 'rightToLeft', + 'axisPosition' => 'none', + 'negativeBarBorderColorSameAsPositive' => null, + 'borderColor' => null, + 'negativeFillColor' => 'FFFF0000', + 'negativeBorderColor' => null, + ]; + foreach ($pattern1 as $key => $value) { + $funcName = 'get' . ucwords($key); + self::assertEquals($value, $extDataBar->$funcName(), $funcName . ' function patten'); + } + + self::assertIsArray($extDataBar->getConditionalFormatValueObjects()); + self::assertArrayHasKey(0, $extDataBar->getConditionalFormatValueObjects()); + self::assertArrayHasKey(1, $extDataBar->getConditionalFormatValueObjects()); + self::assertArrayNotHasKey(2, $extDataBar->getConditionalFormatValueObjects()); + + self::assertEquals('autoMin', $extDataBar->getConditionalFormatValueObjects()[0]->getType()); + self::assertEquals('autoMax', $extDataBar->getConditionalFormatValueObjects()[1]->getType()); + + self::assertArrayHasKey('rgb', $extDataBar->getAxisColor()); + self::assertEmpty($extDataBar->getAxisColor()['rgb']); + } + + private function pattern4Assertion(Worksheet $worksheet): void + { + self::assertEquals( + "type: formula, formula\nValue: =2+3, =10+10\nDirection: leftToRight\nShowDataBarOnly\nFills: Solid\nBorder: Solid\nAxis Position: Midpoint", + $worksheet->getCell('D2')->getValue() + ); + + $conditionalStyle = $worksheet->getConditionalStyles('D3:D23'); + self::assertNotEmpty($conditionalStyle); + /** @var Conditional $conditionalRule */ + $conditionalRule = $conditionalStyle[0]; + $dataBar = $conditionalRule->getDataBar(); + + self::assertNotEmpty($dataBar); + self::assertEquals(Conditional::CONDITION_DATABAR, $conditionalRule->getConditionType()); + self::assertIsArray($dataBar->getConditionalFormatValueObjects()); + self::assertArrayHasKey(0, $dataBar->getConditionalFormatValueObjects()); + self::assertArrayHasKey(1, $dataBar->getConditionalFormatValueObjects()); + self::assertArrayNotHasKey(2, $dataBar->getConditionalFormatValueObjects()); + self::assertEquals('formula', $dataBar->getConditionalFormatValueObjects()[0]->getType()); + self::assertEquals('formula', $dataBar->getConditionalFormatValueObjects()[1]->getType()); + self::assertEquals('3+2', $dataBar->getConditionalFormatValueObjects()[0]->getValue()); + self::assertEquals('10+10', $dataBar->getConditionalFormatValueObjects()[1]->getValue()); + self::assertEquals('FFFF555A', $dataBar->getColor()->getARGB()); + self::assertNotEmpty($dataBar->getConditionalFormattingRuleExtList()); + self::assertArrayHasKey(0, $dataBar->getConditionalFormattingRuleExtList()); + self::assertArrayNotHasKey(1, $dataBar->getConditionalFormattingRuleExtList()); + //ext + $rule1ext = $dataBar->getConditionalFormattingRuleExtList()[0]; + self::assertEquals('{6C1E066A-E240-3D4A-98F8-8CC218B0DFD2}', $rule1ext->getId()); + self::assertEquals('dataBar', $rule1ext->getCfRule()); + self::assertEquals('D3:D23', $rule1ext->getSqref()); + $extDataBar = $rule1ext->getDataBar(); + self::assertNotEmpty($extDataBar); + $pattern1 = [ + 'minLength' => '0', + 'maxLength' => '100', + 'border' => '1', + 'gradient' => '0', + 'direction' => 'leftToRight', + 'axisPosition' => 'middle', + 'negativeBarBorderColorSameAsPositive' => null, + 'borderColor' => 'FF000000', + 'negativeFillColor' => 'FFFF0000', + 'negativeBorderColor' => null, + ]; + foreach ($pattern1 as $key => $value) { + $funcName = 'get' . ucwords($key); + self::assertEquals($value, $extDataBar->$funcName(), $funcName . ' function patten'); + } + self::assertIsArray($extDataBar->getConditionalFormatValueObjects()); + self::assertArrayHasKey(0, $extDataBar->getConditionalFormatValueObjects()); + self::assertArrayHasKey(1, $extDataBar->getConditionalFormatValueObjects()); + self::assertArrayNotHasKey(2, $extDataBar->getConditionalFormatValueObjects()); + + self::assertEquals('formula', $extDataBar->getConditionalFormatValueObjects()[0]->getType()); + self::assertEquals('formula', $extDataBar->getConditionalFormatValueObjects()[1]->getType()); + self::assertEquals('3+2', $extDataBar->getConditionalFormatValueObjects()[0]->getCellFormula()); + self::assertEquals('10+10', $extDataBar->getConditionalFormatValueObjects()[1]->getCellFormula()); + + self::assertArrayHasKey('rgb', $extDataBar->getAxisColor()); + self::assertEquals('FF000000', $extDataBar->getAxisColor()['rgb']); + } +} diff --git a/tests/data/Reader/XLSX/conditionalFormattingDataBarTest.xlsx b/tests/data/Reader/XLSX/conditionalFormattingDataBarTest.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..f1434329f001a75621ca07f9e1ae7ec8b2a084db GIT binary patch literal 10443 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 Znk$uL;hskq06>2J_&u{e7tL?q{vS<{lUx7* literal 0 HcmV?d00001