From bd2e7b64a63e72bf7ed0fb2c6c442b6e133096f0 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 9 Mar 2022 18:30:57 -0800 Subject: [PATCH] Handle Booleans in Conditional Styles (#2654) You can set up a conditional style to, say, apply to cells equal to boolean values. For such conditions, the Excel XML specifies `TRUE` or `FALSE`. It is noteworthy that false matches empty cells as well as FALSE, but not 0; similarly TRUE does not match 1. The Xlsx Writer just casts these values to string, which will not work properly. The Xlsx Reader treats the values as strings, so it won't work properly either. This PR corrects both. Also the doc blocks in Style/Conditional allow bool in some places, but not in others; these are corrected but no executable code is changed there. --- phpstan-baseline.neon | 30 ------ .../Reader/Xlsx/ConditionalStyles.php | 15 ++- src/PhpSpreadsheet/Style/Conditional.php | 10 +- .../Wizard/Expression.php | 2 +- src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php | 9 +- .../Style/ConditionalBoolTest.php | 98 +++++++++++++++++++ 6 files changed, 121 insertions(+), 43 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Style/ConditionalBoolTest.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 5d5194e4..f2941bf6 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -4125,16 +4125,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Spreadsheet.php - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:\\$condition \\(array\\\\) does not accept array\\\\.$#" - count: 1 - path: src/PhpSpreadsheet/Style/Conditional.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:\\$style \\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Style/Conditional.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\:\\:setConditionalFormattingRuleExt\\(\\) has no return type specified\\.$#" count: 1 @@ -4295,11 +4285,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php - - - message: "#^Parameter \\#1 \\$conditions of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:setConditions\\(\\) expects array\\\\|bool\\|float\\|int\\|string, array\\ given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/CellValue.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\DateFormatter\\:\\:escapeQuotesCallback\\(\\) has parameter \\$matches with no type specified\\.$#" count: 1 @@ -5810,11 +5795,6 @@ parameters: count: 1 path: tests/PhpSpreadsheetTests/Functional/CommentsTest.php - - - message: "#^Parameter \\#1 \\$condition of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:addCondition\\(\\) expects string, float given\\.$#" - count: 2 - path: tests/PhpSpreadsheetTests/Functional/ConditionalStopIfTrueTest.php - - message: "#^Cannot call method getPageSetup\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" count: 5 @@ -5960,16 +5940,6 @@ parameters: count: 1 path: tests/PhpSpreadsheetTests/SpreadsheetTest.php - - - message: "#^Parameter \\#1 \\$condition of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:addCondition\\(\\) expects string, float given\\.$#" - count: 2 - path: tests/PhpSpreadsheetTests/Style/ConditionalTest.php - - - - message: "#^Parameter \\#1 \\$conditions of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:setConditions\\(\\) expects array\\\\|bool\\|float\\|int\\|string, array\\ given\\.$#" - count: 1 - path: tests/PhpSpreadsheetTests/Style/ConditionalTest.php - - message: "#^Parameter \\#2 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\:\\:setAttribute\\(\\) expects string, int given\\.$#" count: 1 diff --git a/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php b/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php index ce1e1e63..c631a0fe 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php @@ -196,12 +196,19 @@ class ConditionalStyles $objConditional->setStopIfTrue(true); } - if (count($cfRule->formula) > 1) { - foreach ($cfRule->formula as $formula) { - $objConditional->addCondition((string) $formula); + if (count($cfRule->formula) >= 1) { + foreach ($cfRule->formula as $formulax) { + $formula = (string) $formulax; + if ($formula === 'TRUE') { + $objConditional->addCondition(true); + } elseif ($formula === 'FALSE') { + $objConditional->addCondition(false); + } else { + $objConditional->addCondition($formula); + } } } else { - $objConditional->addCondition((string) $cfRule->formula); + $objConditional->addCondition(''); } if (isset($cfRule->dataBar)) { diff --git a/src/PhpSpreadsheet/Style/Conditional.php b/src/PhpSpreadsheet/Style/Conditional.php index e67964c2..019c0648 100644 --- a/src/PhpSpreadsheet/Style/Conditional.php +++ b/src/PhpSpreadsheet/Style/Conditional.php @@ -99,7 +99,7 @@ class Conditional implements IComparable /** * Condition. * - * @var string[] + * @var (bool|float|int|string)[] */ private $condition = []; @@ -223,7 +223,7 @@ class Conditional implements IComparable /** * Get Conditions. * - * @return string[] + * @return (bool|float|int|string)[] */ public function getConditions() { @@ -233,7 +233,7 @@ class Conditional implements IComparable /** * Set Conditions. * - * @param bool|float|int|string|string[] $conditions Condition + * @param bool|float|int|string|(bool|float|int|string)[] $conditions Condition * * @return $this */ @@ -250,7 +250,7 @@ class Conditional implements IComparable /** * Add Condition. * - * @param string $condition Condition + * @param bool|float|int|string $condition Condition * * @return $this */ @@ -276,7 +276,7 @@ class Conditional implements IComparable * * @return $this */ - public function setStyle(?Style $style = null) + public function setStyle(Style $style) { $this->style = $style; diff --git a/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php b/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php index 8c8c7f27..ee71e34b 100644 --- a/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php +++ b/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php @@ -51,7 +51,7 @@ class Expression extends WizardAbstract implements WizardInterface $wizard = new self($cellRange); $wizard->style = $conditional->getStyle(); $wizard->stopIfTrue = $conditional->getStopIfTrue(); - $wizard->expression = self::reverseAdjustCellRef($conditional->getConditions()[0], $cellRange); + $wizard->expression = self::reverseAdjustCellRef((string) ($conditional->getConditions()[0]), $cellRange); return $wizard; } diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index 06c8becf..2cc35a28 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -478,7 +478,10 @@ class Worksheet extends WriterPart ) { foreach ($conditions as $formula) { // Formula - $objWriter->writeElement('formula', Xlfn::addXlfn($formula)); + if (is_bool($formula)) { + $formula = $formula ? 'TRUE' : 'FALSE'; + } + $objWriter->writeElement('formula', Xlfn::addXlfn("$formula")); } } else { if ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSBLANKS) { @@ -525,7 +528,7 @@ class Worksheet extends WriterPart $objWriter->writeElement('formula', 'AND(MONTH(' . $cellCoordinate . ')=MONTH(EDATE(TODAY(),0+1)),YEAR(' . $cellCoordinate . ')=YEAR(EDATE(TODAY(),0+1)))'); } } else { - $objWriter->writeElement('formula', $conditional->getConditions()[0]); + $objWriter->writeElement('formula', (string) ($conditional->getConditions()[0])); } } } @@ -546,7 +549,7 @@ class Worksheet extends WriterPart $objWriter->writeElement('formula', 'ISERROR(SEARCH("' . $txt . '",' . $cellCoordinate . '))'); } } else { - $objWriter->writeElement('formula', $conditional->getConditions()[0]); + $objWriter->writeElement('formula', (string) ($conditional->getConditions()[0])); } } } diff --git a/tests/PhpSpreadsheetTests/Style/ConditionalBoolTest.php b/tests/PhpSpreadsheetTests/Style/ConditionalBoolTest.php new file mode 100644 index 00000000..2e50fced --- /dev/null +++ b/tests/PhpSpreadsheetTests/Style/ConditionalBoolTest.php @@ -0,0 +1,98 @@ +outfile !== '') { + unlink($this->outfile); + $this->outfile = ''; + } + } + + public function testBool(): void + { + $spreadsheet = new Spreadsheet(); + + $sheet = $spreadsheet->getActiveSheet(); + $condition1 = new Conditional(); + $condition1->setConditionType(Conditional::CONDITION_CELLIS); + $condition1->setOperatorType(Conditional::OPERATOR_EQUAL); + $condition1->addCondition(false); + $condition1->getStyle()->getFill() + ->setFillType(Fill::FILL_SOLID) + ->getEndColor()->setARGB('FFFFFF00'); + $conditionalStyles = $sheet->getStyle('A1:A10')->getConditionalStyles(); + $conditionalStyles[] = $condition1; + $sheet->getStyle('A1:A20')->setConditionalStyles($conditionalStyles); + $sheet->setCellValue('A1', 1); + $sheet->setCellValue('A2', true); + $sheet->setCellValue('A3', false); + $sheet->setCellValue('A4', 0.6); + $sheet->setCellValue('A6', 0); + $sheet->setSelectedCell('B1'); + + $sheet = $spreadsheet->createSheet(); + $condition1 = new Conditional(); + $condition1->setConditionType(Conditional::CONDITION_CELLIS); + $condition1->setOperatorType(Conditional::OPERATOR_EQUAL); + $condition1->addCondition(true); + $condition1->getStyle()->getFill() + ->setFillType(Fill::FILL_SOLID) + ->getEndColor()->setARGB('FF00FF00'); + $conditionalStyles = $sheet->getStyle('A1:A10')->getConditionalStyles(); + $conditionalStyles[] = $condition1; + $sheet->getStyle('A1:A20')->setConditionalStyles($conditionalStyles); + $sheet->setCellValue('A1', 1); + $sheet->setCellValue('A2', true); + $sheet->setCellValue('A3', false); + $sheet->setCellValue('A4', 0.6); + $sheet->setCellValue('A6', 0); + $sheet->setSelectedCell('B1'); + + $writer = new XlsxWriter($spreadsheet); + $this->outfile = File::temporaryFilename(); + $writer->save($this->outfile); + $spreadsheet->disconnectWorksheets(); + + $file = 'zip://' . $this->outfile . '#xl/worksheets/sheet1.xml'; + $contents = file_get_contents($file); + self::assertNotFalse($contents); + self::assertStringContainsString('FALSE', $contents); + $file = 'zip://' . $this->outfile . '#xl/worksheets/sheet2.xml'; + $contents = file_get_contents($file); + self::assertNotFalse($contents); + self::assertStringContainsString('TRUE', $contents); + + $reader = new XlsxReader(); + $spreadsheet2 = $reader->load($this->outfile); + $sheet1 = $spreadsheet2->getSheet(0); + $condArray = $sheet1->getStyle('A1:A20')->getConditionalStyles(); + self::assertNotEmpty($condArray); + $cond1 = $condArray[0]; + self::assertSame(Conditional::CONDITION_CELLIS, $cond1->getConditionType()); + self::assertSame(Conditional::OPERATOR_EQUAL, $cond1->getOperatorType()); + self::assertFalse(($cond1->getConditions())[0]); + $sheet2 = $spreadsheet2->getSheet(1); + $condArray = $sheet2->getStyle('A1:A20')->getConditionalStyles(); + self::assertNotEmpty($condArray); + $cond1 = $condArray[0]; + self::assertSame(Conditional::CONDITION_CELLIS, $cond1->getConditionType()); + self::assertSame(Conditional::OPERATOR_EQUAL, $cond1->getOperatorType()); + self::assertTrue(($cond1->getConditions())[0]); + $spreadsheet2->disconnectWorksheets(); + } +}