Skip to content

Commit 45c08d6

Browse files
author
MarkBaker
committed
Initial work on reading conditional styles for the Xls Reader
Successfully reading the CF ranges and CF rules; not yet reading the styles
1 parent 9b3c3f4 commit 45c08d6

File tree

5 files changed

+284
-44
lines changed

5 files changed

+284
-44
lines changed

src/PhpSpreadsheet/Reader/Xls.php

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use PhpOffice\PhpSpreadsheet\Spreadsheet;
2323
use PhpOffice\PhpSpreadsheet\Style\Alignment;
2424
use PhpOffice\PhpSpreadsheet\Style\Borders;
25+
use PhpOffice\PhpSpreadsheet\Style\Conditional;
2526
use PhpOffice\PhpSpreadsheet\Style\Font;
2627
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
2728
use PhpOffice\PhpSpreadsheet\Style\Protection;
@@ -1036,11 +1037,11 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
10361037

10371038
break;
10381039
case self::XLS_TYPE_CFHEADER:
1039-
$this->readCFHeader();
1040+
$cellRangeAddresses = $this->readCFHeader();
10401041

10411042
break;
10421043
case self::XLS_TYPE_CFRULE:
1043-
$this->readCFRule();
1044+
$this->readCFRule($cellRangeAddresses ?? []);
10441045

10451046
break;
10461047
case self::XLS_TYPE_SHEETLAYOUT:
@@ -7933,17 +7934,17 @@ public function getMapCellStyleXfIndex(): array
79337934
return $this->mapCellStyleXfIndex;
79347935
}
79357936

7936-
private function readCFHeader(): void
7937+
private function readCFHeader(): array
79377938
{
7938-
var_dump('FOUND CF HEADER');
7939+
// var_dump('FOUND CF HEADER');
79397940
$length = self::getUInt2d($this->data, $this->pos + 2);
79407941
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
79417942

79427943
// move stream pointer forward to next record
79437944
$this->pos += 4 + $length;
79447945

79457946
if ($this->readDataOnly) {
7946-
return;
7947+
return [];
79477948
}
79487949

79497950
// offset: 0; size: 2; Rule Count
@@ -7955,12 +7956,14 @@ private function readCFHeader(): void
79557956
: $this->readBIFF5CellRangeAddressList(substr($recordData, 12));
79567957
$cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses'];
79577958

7958-
var_dump($ruleCount, $cellRangeAddresses);
7959+
// var_dump($ruleCount, $cellRangeAddresses);
7960+
//
7961+
return $cellRangeAddresses;
79597962
}
79607963

7961-
private function readCFRule(): void
7964+
private function readCFRule(array $cellRangeAddresses): void
79627965
{
7963-
var_dump('FOUND CF RULE');
7966+
// var_dump('FOUND CF RULE');
79647967
$length = self::getUInt2d($this->data, $this->pos + 2);
79657968
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
79667969

@@ -8023,14 +8026,15 @@ private function readCFRule(): void
80238026
$offset += 2;
80248027
}
80258028

8026-
var_dump($type, $operator);
8027-
8029+
// var_dump($type, $operator);
8030+
//
8031+
$formula1 = $formula2 = null;
80288032
if ($size1 > 0) {
80298033
$formula1 = $this->readCFFormula($recordData, $offset, $size1);
80308034
if ($formula1 === null) {
80318035
return;
80328036
}
8033-
var_dump($formula1);
8037+
// var_dump($formula1);
80348038

80358039
$offset += $size1;
80368040
}
@@ -8040,20 +8044,57 @@ private function readCFRule(): void
80408044
if ($formula2 === null) {
80418045
return;
80428046
}
8043-
var_dump($formula2);
8047+
// var_dump($formula2);
8048+
8049+
$offset += $size2;
80448050
}
8051+
8052+
$this->setCFRules($cellRangeAddresses, $type, $operator, $formula1, $formula2);
80458053
}
80468054

8047-
private function readCFFormula(string $recordData, int $offset, int $size): ?string
8055+
/**
8056+
* @return null|float|int|string
8057+
*/
8058+
private function readCFFormula(string $recordData, int $offset, int $size)
80488059
{
80498060
try {
80508061
$formula = substr($recordData, $offset, $size);
80518062
$formula = pack('v', $size) . $formula; // prepend the length
80528063

8053-
return $this->getFormulaFromStructure($formula);
8064+
$formula = $this->getFormulaFromStructure($formula);
8065+
if (is_numeric($formula)) {
8066+
return (strpos($formula, '.') !== false) ? (float) $formula : (int) $formula;
8067+
}
8068+
8069+
return $formula;
80548070
} catch (PhpSpreadsheetException $e) {
80558071
}
80568072

80578073
return null;
80588074
}
8075+
8076+
/**
8077+
* @param null|float|int|string $formula1
8078+
* @param null|float|int|string $formula2
8079+
*/
8080+
private function setCFRules(array $cellRanges, string $type, string $operator, $formula1, $formula2): void
8081+
{
8082+
foreach ($cellRanges as $cellRange) {
8083+
$conditional = new Conditional();
8084+
$conditional->setConditionType($type);
8085+
$conditional->setOperatorType($operator);
8086+
if ($formula1 !== null) {
8087+
$conditional->addCondition($formula1);
8088+
}
8089+
if ($formula2 !== null) {
8090+
$conditional->addCondition($formula2);
8091+
}
8092+
8093+
$conditionalStyles = $this->phpSheet->getStyle($cellRange)->getConditionalStyles();
8094+
$conditionalStyles[] = $conditional;
8095+
8096+
$this->phpSheet->getStyle($cellRange)->setConditionalStyles($conditionalStyles);
8097+
$this->phpSheet->getStyle($cellRange)->setConditionalStyles($conditionalStyles);
8098+
}
8099+
}
80598100
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheetTests\Reader\Xls;
4+
5+
use PhpOffice\PhpSpreadsheet\Reader\Xls;
6+
use PhpOffice\PhpSpreadsheet\Style\Conditional;
7+
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
8+
use PHPUnit\Framework\TestCase;
9+
10+
class ConditionalFormattingBasicTest extends TestCase
11+
{
12+
/**
13+
* @var Worksheet
14+
*/
15+
protected $sheet;
16+
17+
protected function setUp(): void
18+
{
19+
$filename = 'tests/data/Reader/XLS/CF_Basic_Comparisons.xls';
20+
$reader = new Xls();
21+
$spreadsheet = $reader->load($filename);
22+
$this->sheet = $spreadsheet->getActiveSheet();
23+
}
24+
25+
/**
26+
* @dataProvider conditionalFormattingProvider
27+
*/
28+
public function testReadConditionalFormatting(string $expectedRange, array $expectedRules): void
29+
{
30+
$hasConditionalStyles = $this->sheet->conditionalStylesExists($expectedRange);
31+
self::assertTrue($hasConditionalStyles);
32+
33+
$conditionalStyles = $this->sheet->getConditionalStyles($expectedRange);
34+
35+
foreach ($conditionalStyles as $index => $conditionalStyle) {
36+
self::assertSame($expectedRules[$index]['type'], $conditionalStyle->getConditionType());
37+
self::assertSame($expectedRules[$index]['operator'], $conditionalStyle->getOperatorType());
38+
self::assertSame($expectedRules[$index]['conditions'], $conditionalStyle->getConditions());
39+
}
40+
}
41+
42+
public function conditionalFormattingProvider(): array
43+
{
44+
return [
45+
[
46+
'A2:E5',
47+
[
48+
[
49+
'type' => Conditional::CONDITION_CELLIS,
50+
'operator' => Conditional::OPERATOR_EQUAL,
51+
'conditions' => [
52+
0,
53+
],
54+
],
55+
[
56+
'type' => Conditional::CONDITION_CELLIS,
57+
'operator' => Conditional::OPERATOR_GREATERTHAN,
58+
'conditions' => [
59+
0,
60+
],
61+
],
62+
[
63+
'type' => Conditional::CONDITION_CELLIS,
64+
'operator' => Conditional::OPERATOR_LESSTHAN,
65+
'conditions' => [
66+
0,
67+
],
68+
],
69+
],
70+
],
71+
[
72+
'A10:E13',
73+
[
74+
[
75+
'type' => Conditional::CONDITION_CELLIS,
76+
'operator' => Conditional::OPERATOR_EQUAL,
77+
'conditions' => [
78+
'$H$9',
79+
],
80+
],
81+
[
82+
'type' => Conditional::CONDITION_CELLIS,
83+
'operator' => Conditional::OPERATOR_GREATERTHAN,
84+
'conditions' => [
85+
'$H$9',
86+
],
87+
],
88+
[
89+
'type' => Conditional::CONDITION_CELLIS,
90+
'operator' => Conditional::OPERATOR_LESSTHAN,
91+
'conditions' => [
92+
'$H$9',
93+
],
94+
],
95+
],
96+
],
97+
[
98+
'A18:A20',
99+
[
100+
[
101+
'type' => Conditional::CONDITION_CELLIS,
102+
'operator' => Conditional::OPERATOR_BETWEEN,
103+
'conditions' => [
104+
'$B1',
105+
'$C1',
106+
],
107+
],
108+
],
109+
],
110+
[
111+
'A24:E27',
112+
[
113+
[
114+
'type' => Conditional::CONDITION_CELLIS,
115+
'operator' => Conditional::OPERATOR_BETWEEN,
116+
'conditions' => [
117+
'AVERAGE($A$24:$E$27)-STDEV($A$24:$E$27)',
118+
'AVERAGE($A$24:$E$27)+STDEV($A$24:$E$27)',
119+
],
120+
],
121+
[
122+
'type' => Conditional::CONDITION_CELLIS,
123+
'operator' => Conditional::OPERATOR_GREATERTHAN,
124+
'conditions' => [
125+
'AVERAGE($A$24:$E$27)+STDEV($A$24:$E$27)',
126+
],
127+
],
128+
[
129+
'type' => Conditional::CONDITION_CELLIS,
130+
'operator' => Conditional::OPERATOR_LESSTHAN,
131+
'conditions' => [
132+
'AVERAGE($A$24:$E$27)-STDEV($A$24:$E$27)',
133+
],
134+
],
135+
],
136+
],
137+
[
138+
'A31:A33',
139+
[
140+
[
141+
'type' => Conditional::CONDITION_CELLIS,
142+
'operator' => Conditional::OPERATOR_EQUAL,
143+
'conditions' => [
144+
'"LOVE"',
145+
],
146+
],
147+
[
148+
'type' => Conditional::CONDITION_CELLIS,
149+
'operator' => Conditional::OPERATOR_EQUAL,
150+
'conditions' => [
151+
'"PHP"',
152+
],
153+
],
154+
],
155+
],
156+
];
157+
}
158+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheetTests\Reader\Xls;
4+
5+
use PhpOffice\PhpSpreadsheet\Reader\Xls;
6+
use PhpOffice\PhpSpreadsheet\Style\Conditional;
7+
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
8+
use PHPUnit\Framework\TestCase;
9+
10+
class ConditionalFormattingExpressionTest extends TestCase
11+
{
12+
/**
13+
* @var Worksheet
14+
*/
15+
protected $sheet;
16+
17+
protected function setUp(): void
18+
{
19+
$filename = 'tests/data/Reader/XLS/CF_Expression_Comparisons.xls';
20+
$reader = new Xls();
21+
$spreadsheet = $reader->load($filename);
22+
$this->sheet = $spreadsheet->getActiveSheet();
23+
}
24+
25+
/**
26+
* @dataProvider conditionalFormattingProvider
27+
*/
28+
public function testReadConditionalFormatting(string $expectedRange, array $expectedRule): void
29+
{
30+
$hasConditionalStyles = $this->sheet->conditionalStylesExists($expectedRange);
31+
self::assertTrue($hasConditionalStyles);
32+
33+
$conditionalStyles = $this->sheet->getConditionalStyles($expectedRange);
34+
35+
foreach ($conditionalStyles as $index => $conditionalStyle) {
36+
self::assertSame($expectedRule[$index]['type'], $conditionalStyle->getConditionType());
37+
self::assertSame($expectedRule[$index]['operator'], $conditionalStyle->getOperatorType());
38+
self::assertSame($expectedRule[$index]['conditions'], $conditionalStyle->getConditions());
39+
}
40+
}
41+
42+
public function conditionalFormattingProvider(): array
43+
{
44+
return [
45+
[
46+
'A3:D8',
47+
[
48+
[
49+
'type' => Conditional::CONDITION_EXPRESSION,
50+
'operator' => Conditional::OPERATOR_NONE,
51+
'conditions' => [
52+
'$C1="USA"',
53+
],
54+
],
55+
],
56+
],
57+
[
58+
'A13:D18',
59+
[
60+
[
61+
'type' => Conditional::CONDITION_EXPRESSION,
62+
'operator' => Conditional::OPERATOR_NONE,
63+
'conditions' => [
64+
'AND($C1="USA",$D1="Q4")',
65+
],
66+
],
67+
],
68+
],
69+
];
70+
}
71+
}

0 commit comments

Comments
 (0)