Skip to content

Commit bd2e7b6

Browse files
authored
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.
1 parent 0d5d9eb commit bd2e7b6

File tree

6 files changed

+121
-43
lines changed

6 files changed

+121
-43
lines changed

phpstan-baseline.neon

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4125,16 +4125,6 @@ parameters:
41254125
count: 1
41264126
path: src/PhpSpreadsheet/Spreadsheet.php
41274127

4128-
-
4129-
message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:\\$condition \\(array\\<string\\>\\) does not accept array\\<bool\\|float\\|int\\|string\\>\\.$#"
4130-
count: 1
4131-
path: src/PhpSpreadsheet/Style/Conditional.php
4132-
4133-
-
4134-
message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:\\$style \\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\|null\\.$#"
4135-
count: 1
4136-
path: src/PhpSpreadsheet/Style/Conditional.php
4137-
41384128
-
41394129
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\:\\:setConditionalFormattingRuleExt\\(\\) has no return type specified\\.$#"
41404130
count: 1
@@ -4295,11 +4285,6 @@ parameters:
42954285
count: 1
42964286
path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php
42974287

4298-
-
4299-
message: "#^Parameter \\#1 \\$conditions of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:setConditions\\(\\) expects array\\<string\\>\\|bool\\|float\\|int\\|string, array\\<int, float\\|int\\|string\\> given\\.$#"
4300-
count: 1
4301-
path: src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/CellValue.php
4302-
43034288
-
43044289
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\DateFormatter\\:\\:escapeQuotesCallback\\(\\) has parameter \\$matches with no type specified\\.$#"
43054290
count: 1
@@ -5810,11 +5795,6 @@ parameters:
58105795
count: 1
58115796
path: tests/PhpSpreadsheetTests/Functional/CommentsTest.php
58125797

5813-
-
5814-
message: "#^Parameter \\#1 \\$condition of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:addCondition\\(\\) expects string, float given\\.$#"
5815-
count: 2
5816-
path: tests/PhpSpreadsheetTests/Functional/ConditionalStopIfTrueTest.php
5817-
58185798
-
58195799
message: "#^Cannot call method getPageSetup\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#"
58205800
count: 5
@@ -5960,16 +5940,6 @@ parameters:
59605940
count: 1
59615941
path: tests/PhpSpreadsheetTests/SpreadsheetTest.php
59625942

5963-
-
5964-
message: "#^Parameter \\#1 \\$condition of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:addCondition\\(\\) expects string, float given\\.$#"
5965-
count: 2
5966-
path: tests/PhpSpreadsheetTests/Style/ConditionalTest.php
5967-
5968-
-
5969-
message: "#^Parameter \\#1 \\$conditions of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:setConditions\\(\\) expects array\\<string\\>\\|bool\\|float\\|int\\|string, array\\<int, float\\> given\\.$#"
5970-
count: 1
5971-
path: tests/PhpSpreadsheetTests/Style/ConditionalTest.php
5972-
59735943
-
59745944
message: "#^Parameter \\#2 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\:\\:setAttribute\\(\\) expects string, int given\\.$#"
59755945
count: 1

src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,19 @@ private function readStyleRules($cfRules, $extLst)
196196
$objConditional->setStopIfTrue(true);
197197
}
198198

199-
if (count($cfRule->formula) > 1) {
200-
foreach ($cfRule->formula as $formula) {
201-
$objConditional->addCondition((string) $formula);
199+
if (count($cfRule->formula) >= 1) {
200+
foreach ($cfRule->formula as $formulax) {
201+
$formula = (string) $formulax;
202+
if ($formula === 'TRUE') {
203+
$objConditional->addCondition(true);
204+
} elseif ($formula === 'FALSE') {
205+
$objConditional->addCondition(false);
206+
} else {
207+
$objConditional->addCondition($formula);
208+
}
202209
}
203210
} else {
204-
$objConditional->addCondition((string) $cfRule->formula);
211+
$objConditional->addCondition('');
205212
}
206213

207214
if (isset($cfRule->dataBar)) {

src/PhpSpreadsheet/Style/Conditional.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ class Conditional implements IComparable
9999
/**
100100
* Condition.
101101
*
102-
* @var string[]
102+
* @var (bool|float|int|string)[]
103103
*/
104104
private $condition = [];
105105

@@ -223,7 +223,7 @@ public function setStopIfTrue($stopIfTrue)
223223
/**
224224
* Get Conditions.
225225
*
226-
* @return string[]
226+
* @return (bool|float|int|string)[]
227227
*/
228228
public function getConditions()
229229
{
@@ -233,7 +233,7 @@ public function getConditions()
233233
/**
234234
* Set Conditions.
235235
*
236-
* @param bool|float|int|string|string[] $conditions Condition
236+
* @param bool|float|int|string|(bool|float|int|string)[] $conditions Condition
237237
*
238238
* @return $this
239239
*/
@@ -250,7 +250,7 @@ public function setConditions($conditions)
250250
/**
251251
* Add Condition.
252252
*
253-
* @param string $condition Condition
253+
* @param bool|float|int|string $condition Condition
254254
*
255255
* @return $this
256256
*/
@@ -276,7 +276,7 @@ public function getStyle()
276276
*
277277
* @return $this
278278
*/
279-
public function setStyle(?Style $style = null)
279+
public function setStyle(Style $style)
280280
{
281281
$this->style = $style;
282282

src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public static function fromConditional(Conditional $conditional, string $cellRan
5151
$wizard = new self($cellRange);
5252
$wizard->style = $conditional->getStyle();
5353
$wizard->stopIfTrue = $conditional->getStopIfTrue();
54-
$wizard->expression = self::reverseAdjustCellRef($conditional->getConditions()[0], $cellRange);
54+
$wizard->expression = self::reverseAdjustCellRef((string) ($conditional->getConditions()[0]), $cellRange);
5555

5656
return $wizard;
5757
}

src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,10 @@ private static function writeOtherCondElements(XMLWriter $objWriter, Conditional
478478
) {
479479
foreach ($conditions as $formula) {
480480
// Formula
481-
$objWriter->writeElement('formula', Xlfn::addXlfn($formula));
481+
if (is_bool($formula)) {
482+
$formula = $formula ? 'TRUE' : 'FALSE';
483+
}
484+
$objWriter->writeElement('formula', Xlfn::addXlfn("$formula"));
482485
}
483486
} else {
484487
if ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSBLANKS) {
@@ -525,7 +528,7 @@ private static function writeTimePeriodCondElements(XMLWriter $objWriter, Condit
525528
$objWriter->writeElement('formula', 'AND(MONTH(' . $cellCoordinate . ')=MONTH(EDATE(TODAY(),0+1)),YEAR(' . $cellCoordinate . ')=YEAR(EDATE(TODAY(),0+1)))');
526529
}
527530
} else {
528-
$objWriter->writeElement('formula', $conditional->getConditions()[0]);
531+
$objWriter->writeElement('formula', (string) ($conditional->getConditions()[0]));
529532
}
530533
}
531534
}
@@ -546,7 +549,7 @@ private static function writeTextCondElements(XMLWriter $objWriter, Conditional
546549
$objWriter->writeElement('formula', 'ISERROR(SEARCH("' . $txt . '",' . $cellCoordinate . '))');
547550
}
548551
} else {
549-
$objWriter->writeElement('formula', $conditional->getConditions()[0]);
552+
$objWriter->writeElement('formula', (string) ($conditional->getConditions()[0]));
550553
}
551554
}
552555
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheetTests\Style;
4+
5+
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
6+
use PhpOffice\PhpSpreadsheet\Shared\File;
7+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
8+
use PhpOffice\PhpSpreadsheet\Style\Conditional;
9+
use PhpOffice\PhpSpreadsheet\Style\Fill;
10+
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
11+
use PHPUnit\Framework\TestCase;
12+
13+
class ConditionalBoolTest extends TestCase
14+
{
15+
/** @var string */
16+
private $outfile = '';
17+
18+
protected function tearDown(): void
19+
{
20+
if ($this->outfile !== '') {
21+
unlink($this->outfile);
22+
$this->outfile = '';
23+
}
24+
}
25+
26+
public function testBool(): void
27+
{
28+
$spreadsheet = new Spreadsheet();
29+
30+
$sheet = $spreadsheet->getActiveSheet();
31+
$condition1 = new Conditional();
32+
$condition1->setConditionType(Conditional::CONDITION_CELLIS);
33+
$condition1->setOperatorType(Conditional::OPERATOR_EQUAL);
34+
$condition1->addCondition(false);
35+
$condition1->getStyle()->getFill()
36+
->setFillType(Fill::FILL_SOLID)
37+
->getEndColor()->setARGB('FFFFFF00');
38+
$conditionalStyles = $sheet->getStyle('A1:A10')->getConditionalStyles();
39+
$conditionalStyles[] = $condition1;
40+
$sheet->getStyle('A1:A20')->setConditionalStyles($conditionalStyles);
41+
$sheet->setCellValue('A1', 1);
42+
$sheet->setCellValue('A2', true);
43+
$sheet->setCellValue('A3', false);
44+
$sheet->setCellValue('A4', 0.6);
45+
$sheet->setCellValue('A6', 0);
46+
$sheet->setSelectedCell('B1');
47+
48+
$sheet = $spreadsheet->createSheet();
49+
$condition1 = new Conditional();
50+
$condition1->setConditionType(Conditional::CONDITION_CELLIS);
51+
$condition1->setOperatorType(Conditional::OPERATOR_EQUAL);
52+
$condition1->addCondition(true);
53+
$condition1->getStyle()->getFill()
54+
->setFillType(Fill::FILL_SOLID)
55+
->getEndColor()->setARGB('FF00FF00');
56+
$conditionalStyles = $sheet->getStyle('A1:A10')->getConditionalStyles();
57+
$conditionalStyles[] = $condition1;
58+
$sheet->getStyle('A1:A20')->setConditionalStyles($conditionalStyles);
59+
$sheet->setCellValue('A1', 1);
60+
$sheet->setCellValue('A2', true);
61+
$sheet->setCellValue('A3', false);
62+
$sheet->setCellValue('A4', 0.6);
63+
$sheet->setCellValue('A6', 0);
64+
$sheet->setSelectedCell('B1');
65+
66+
$writer = new XlsxWriter($spreadsheet);
67+
$this->outfile = File::temporaryFilename();
68+
$writer->save($this->outfile);
69+
$spreadsheet->disconnectWorksheets();
70+
71+
$file = 'zip://' . $this->outfile . '#xl/worksheets/sheet1.xml';
72+
$contents = file_get_contents($file);
73+
self::assertNotFalse($contents);
74+
self::assertStringContainsString('<formula>FALSE</formula>', $contents);
75+
$file = 'zip://' . $this->outfile . '#xl/worksheets/sheet2.xml';
76+
$contents = file_get_contents($file);
77+
self::assertNotFalse($contents);
78+
self::assertStringContainsString('<formula>TRUE</formula>', $contents);
79+
80+
$reader = new XlsxReader();
81+
$spreadsheet2 = $reader->load($this->outfile);
82+
$sheet1 = $spreadsheet2->getSheet(0);
83+
$condArray = $sheet1->getStyle('A1:A20')->getConditionalStyles();
84+
self::assertNotEmpty($condArray);
85+
$cond1 = $condArray[0];
86+
self::assertSame(Conditional::CONDITION_CELLIS, $cond1->getConditionType());
87+
self::assertSame(Conditional::OPERATOR_EQUAL, $cond1->getOperatorType());
88+
self::assertFalse(($cond1->getConditions())[0]);
89+
$sheet2 = $spreadsheet2->getSheet(1);
90+
$condArray = $sheet2->getStyle('A1:A20')->getConditionalStyles();
91+
self::assertNotEmpty($condArray);
92+
$cond1 = $condArray[0];
93+
self::assertSame(Conditional::CONDITION_CELLIS, $cond1->getConditionType());
94+
self::assertSame(Conditional::OPERATOR_EQUAL, $cond1->getOperatorType());
95+
self::assertTrue(($cond1->getConditions())[0]);
96+
$spreadsheet2->disconnectWorksheets();
97+
}
98+
}

0 commit comments

Comments
 (0)