Failed Conditions
Pull Request — master (#4118)
by Owen
20:22
created

ConditionalFormatting::setCFRules()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
eloc 14
c 0
b 0
f 0
dl 0
loc 20
rs 9.7998
ccs 0
cts 0
cp 0
cc 4
nc 5
nop 8
crap 20

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
4
5
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
6
use PhpOffice\PhpSpreadsheet\Reader\Xls;
7
use PhpOffice\PhpSpreadsheet\Reader\Xls\Style\FillPattern;
8
use PhpOffice\PhpSpreadsheet\Style\Conditional;
9
use PhpOffice\PhpSpreadsheet\Style\Fill;
10
use PhpOffice\PhpSpreadsheet\Style\Style;
11
12
class ConditionalFormatting extends Xls
13
{
14
    /**
15
     * @var array<int, string>
16
     */
17
    private static array $types = [
18
        0x01 => Conditional::CONDITION_CELLIS,
19
        0x02 => Conditional::CONDITION_EXPRESSION,
20
    ];
21
22
    /**
23
     * @var array<int, string>
24
     */
25
    private static array $operators = [
26
        0x00 => Conditional::OPERATOR_NONE,
27
        0x01 => Conditional::OPERATOR_BETWEEN,
28
        0x02 => Conditional::OPERATOR_NOTBETWEEN,
29
        0x03 => Conditional::OPERATOR_EQUAL,
30
        0x04 => Conditional::OPERATOR_NOTEQUAL,
31
        0x05 => Conditional::OPERATOR_GREATERTHAN,
32 21
        0x06 => Conditional::OPERATOR_LESSTHAN,
33
        0x07 => Conditional::OPERATOR_GREATERTHANOREQUAL,
34 21
        0x08 => Conditional::OPERATOR_LESSTHANOREQUAL,
35 21
    ];
36
37
    public static function type(int $type): ?string
38
    {
39
        if (isset(self::$types[$type])) {
40
            return self::$types[$type];
41 21
        }
42
43 21
        return null;
44 21
    }
45
46
    public static function operator(int $operator): ?string
47
    {
48
        if (isset(self::$operators[$operator])) {
49
            return self::$operators[$operator];
50
        }
51
52
        return null;
53
    }
54
55
    /**
56
     * Parse conditional formatting blocks.
57
     *
58
     * @see https://www.openoffice.org/sc/excelfileformat.pdf Search for CFHEADER followed by CFRULE
59
     */
60
    protected function readCFHeader2(Xls $xls): array
61
    {
62
        $length = self::getUInt2d($xls->data, $xls->pos + 2);
63
        $recordData = $xls->readRecordData($xls->data, $xls->pos + 4, $length);
64
65
        // move stream pointer forward to next record
66
        $xls->pos += 4 + $length;
67
68
        if ($xls->readDataOnly) {
69
            return [];
70
        }
71
72
        // offset: 0; size: 2; Rule Count
73
//        $ruleCount = self::getUInt2d($recordData, 0);
74
75
        // offset: var; size: var; cell range address list with
76
        $cellRangeAddressList = ($xls->version == self::XLS_BIFF8)
77
            ? Biff8::readBIFF8CellRangeAddressList(substr($recordData, 12))
78
            : Biff5::readBIFF5CellRangeAddressList(substr($recordData, 12));
79
        $cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses'];
80
81
        return $cellRangeAddresses;
82
    }
83
84
    protected function readCFRule2(array $cellRangeAddresses, Xls $xls): void
85
    {
86
        $length = self::getUInt2d($xls->data, $xls->pos + 2);
87
        $recordData = $xls->readRecordData($xls->data, $xls->pos + 4, $length);
88
89
        // move stream pointer forward to next record
90
        $xls->pos += 4 + $length;
91
92
        if ($xls->readDataOnly) {
93
            return;
94
        }
95
96
        // offset: 0; size: 2; Options
97
        $cfRule = self::getUInt2d($recordData, 0);
98
99
        // bit: 8-15; mask: 0x00FF; type
100
        $type = (0x00FF & $cfRule) >> 0;
101
        $type = self::type($type);
102
103
        // bit: 0-7; mask: 0xFF00; type
104
        $operator = (0xFF00 & $cfRule) >> 8;
105
        $operator = self::operator($operator);
106
107
        if ($type === null || $operator === null) {
108
            return;
109
        }
110
111
        // offset: 2; size: 2; Size1
112
        $size1 = self::getUInt2d($recordData, 2);
113
114
        // offset: 4; size: 2; Size2
115
        $size2 = self::getUInt2d($recordData, 4);
116
117
        // offset: 6; size: 4; Options
118
        $options = self::getInt4d($recordData, 6);
119
120
        $style = new Style(false, true); // non-supervisor, conditional
121
        $noFormatSet = true;
122
        //$xls->getCFStyleOptions($options, $style);
123
124
        $hasFontRecord = (bool) ((0x04000000 & $options) >> 26);
125
        $hasAlignmentRecord = (bool) ((0x08000000 & $options) >> 27);
126
        $hasBorderRecord = (bool) ((0x10000000 & $options) >> 28);
127
        $hasFillRecord = (bool) ((0x20000000 & $options) >> 29);
128
        $hasProtectionRecord = (bool) ((0x40000000 & $options) >> 30);
129
        // note unexpected values for following 4
130
        $hasBorderLeft = !(bool) (0x00000400 & $options);
131
        $hasBorderRight = !(bool) (0x00000800 & $options);
132
        $hasBorderTop = !(bool) (0x00001000 & $options);
133
        $hasBorderBottom = !(bool) (0x00002000 & $options);
134
135
        $offset = 12;
136
137
        if ($hasFontRecord === true) {
138
            $fontStyle = substr($recordData, $offset, 118);
139
            $this->getCFFontStyle($fontStyle, $style, $xls);
140
            $offset += 118;
141
            $noFormatSet = false;
142
        }
143
144
        if ($hasAlignmentRecord === true) {
145
            //$alignmentStyle = substr($recordData, $offset, 8);
146
            //$this->getCFAlignmentStyle($alignmentStyle, $style, $xls);
147
            $offset += 8;
148
        }
149
150
        if ($hasBorderRecord === true) {
151
            $borderStyle = substr($recordData, $offset, 8);
152
            $this->getCFBorderStyle($borderStyle, $style, $hasBorderLeft, $hasBorderRight, $hasBorderTop, $hasBorderBottom, $xls);
153
            $offset += 8;
154
            $noFormatSet = false;
155
        }
156
157
        if ($hasFillRecord === true) {
158
            $fillStyle = substr($recordData, $offset, 4);
159
            $this->getCFFillStyle($fillStyle, $style, $xls);
160
            $offset += 4;
161
            $noFormatSet = false;
162
        }
163
164
        if ($hasProtectionRecord === true) {
165
            //$protectionStyle = substr($recordData, $offset, 4);
166
            //$this->getCFProtectionStyle($protectionStyle, $style, $xls);
167
            $offset += 2;
168
        }
169
170
        $formula1 = $formula2 = null;
171
        if ($size1 > 0) {
172
            $formula1 = $this->readCFFormula($recordData, $offset, $size1, $xls);
173
            if ($formula1 === null) {
174
                return;
175
            }
176
177
            $offset += $size1;
178
        }
179
180
        if ($size2 > 0) {
181
            $formula2 = $this->readCFFormula($recordData, $offset, $size2, $xls);
182
            if ($formula2 === null) {
183
                return;
184
            }
185
186
            $offset += $size2;
187
        }
188
189
        $this->setCFRules($cellRangeAddresses, $type, $operator, $formula1, $formula2, $style, $noFormatSet, $xls);
190
    }
191
192
    /*private function getCFStyleOptions(int $options, Style $style, Xls $xls): void
193
    {
194
    }*/
195
196
    private function getCFFontStyle(string $options, Style $style, Xls $xls): void
197
    {
198
        $fontSize = self::getInt4d($options, 64);
199
        if ($fontSize !== -1) {
200
            $style->getFont()->setSize($fontSize / 20); // Convert twips to points
201
        }
202
        $options68 = self::getInt4d($options, 68);
203
        $options88 = self::getInt4d($options, 88);
204
205
        if (($options88 & 2) === 0) {
206
            $bold = self::getUInt2d($options, 72); // 400 = normal, 700 = bold
207
            if ($bold !== 0) {
208
                $style->getFont()->setBold($bold >= 550);
209
            }
210
            if (($options68 & 2) !== 0) {
211
                $style->getFont()->setItalic(true);
212
            }
213
        }
214
        if (($options88 & 0x80) === 0) {
215
            if (($options68 & 0x80) !== 0) {
216
                $style->getFont()->setStrikethrough(true);
217
            }
218
        }
219
220
        $color = self::getInt4d($options, 80);
221
222
        if ($color !== -1) {
223
            $style->getFont()->getColor()->setRGB(Color::map($color, $xls->palette, $xls->version)['rgb']);
224
        }
225
    }
226
227
    /*private function getCFAlignmentStyle(string $options, Style $style, Xls $xls): void
228
    {
229
    }*/
230
231
    private function getCFBorderStyle(string $options, Style $style, bool $hasBorderLeft, bool $hasBorderRight, bool $hasBorderTop, bool $hasBorderBottom, Xls $xls): void
232
    {
233
        $valueArray = unpack('V', $options);
234
        $value = is_array($valueArray) ? $valueArray[1] : 0;
235
        $left = $value & 15;
236
        $right = ($value >> 4) & 15;
237
        $top = ($value >> 8) & 15;
238
        $bottom = ($value >> 12) & 15;
239
        $leftc = ($value >> 16) & 0x7F;
240
        $rightc = ($value >> 23) & 0x7F;
241
        $valueArray = unpack('V', substr($options, 4));
242
        $value = is_array($valueArray) ? $valueArray[1] : 0;
243
        $topc = $value & 0x7F;
244
        $bottomc = ($value & 0x3F80) >> 7;
245
        if ($hasBorderLeft) {
246
            $style->getBorders()->getLeft()
247
                ->setBorderStyle(self::BORDER_STYLE_MAP[$left]);
248
            $style->getBorders()->getLeft()->getColor()
249
                ->setRGB(Color::map($leftc, $xls->palette, $xls->version)['rgb']);
250
        }
251
        if ($hasBorderRight) {
252
            $style->getBorders()->getRight()
253
                ->setBorderStyle(self::BORDER_STYLE_MAP[$right]);
254
            $style->getBorders()->getRight()->getColor()
255
                ->setRGB(Color::map($rightc, $xls->palette, $xls->version)['rgb']);
256
        }
257
        if ($hasBorderTop) {
258
            $style->getBorders()->getTop()
259
                ->setBorderStyle(self::BORDER_STYLE_MAP[$top]);
260
            $style->getBorders()->getTop()->getColor()
261
                ->setRGB(Color::map($topc, $xls->palette, $xls->version)['rgb']);
262
        }
263
        if ($hasBorderBottom) {
264
            $style->getBorders()->getBottom()
265
                ->setBorderStyle(self::BORDER_STYLE_MAP[$bottom]);
266
            $style->getBorders()->getBottom()->getColor()
267
                ->setRGB(Color::map($bottomc, $xls->palette, $xls->version)['rgb']);
268
        }
269
    }
270
271
    private function getCFFillStyle(string $options, Style $style, Xls $xls): void
272
    {
273
        $fillPattern = self::getUInt2d($options, 0);
274
        // bit: 10-15; mask: 0xFC00; type
275
        $fillPattern = (0xFC00 & $fillPattern) >> 10;
276
        $fillPattern = FillPattern::lookup($fillPattern);
277
        $fillPattern = $fillPattern === Fill::FILL_NONE ? Fill::FILL_SOLID : $fillPattern;
278
279
        if ($fillPattern !== Fill::FILL_NONE) {
280
            $style->getFill()->setFillType($fillPattern);
281
282
            $fillColors = self::getUInt2d($options, 2);
283
284
            // bit: 0-6; mask: 0x007F; type
285
            $color1 = (0x007F & $fillColors) >> 0;
286
287
            // bit: 7-13; mask: 0x3F80; type
288
            $color2 = (0x3F80 & $fillColors) >> 7;
289
            if ($fillPattern === Fill::FILL_SOLID) {
290
                $style->getFill()->getStartColor()->setRGB(Color::map($color2, $xls->palette, $xls->version)['rgb']);
291
            } else {
292
                $style->getFill()->getStartColor()->setRGB(Color::map($color1, $xls->palette, $xls->version)['rgb']);
293
                $style->getFill()->getEndColor()->setRGB(Color::map($color2, $xls->palette, $xls->version)['rgb']);
294
            }
295
        }
296
    }
297
298
    /*private function getCFProtectionStyle(string $options, Style $style, Xls $xls): void
299
    {
300
    }*/
301
302
    private function readCFFormula(string $recordData, int $offset, int $size, Xls $xls): float|int|string|null
303
    {
304
        try {
305
            $formula = substr($recordData, $offset, $size);
306
            $formula = pack('v', $size) . $formula; // prepend the length
307
308
            $formula = $xls->getFormulaFromStructure($formula);
309
            if (is_numeric($formula)) {
310
                return (str_contains($formula, '.')) ? (float) $formula : (int) $formula;
311
            }
312
313
            return $formula;
314
        } catch (PhpSpreadsheetException) {
315
            return null;
316
        }
317
    }
318
319
    private function setCFRules(array $cellRanges, string $type, string $operator, null|float|int|string $formula1, null|float|int|string $formula2, Style $style, bool $noFormatSet, Xls $xls): void
320
    {
321
        foreach ($cellRanges as $cellRange) {
322
            $conditional = new Conditional();
323
            $conditional->setNoFormatSet($noFormatSet);
324
            $conditional->setConditionType($type);
325
            $conditional->setOperatorType($operator);
326
            $conditional->setStopIfTrue(true);
327
            if ($formula1 !== null) {
328
                $conditional->addCondition($formula1);
329
            }
330
            if ($formula2 !== null) {
331
                $conditional->addCondition($formula2);
332
            }
333
            $conditional->setStyle($style);
334
335
            $conditionalStyles = $xls->phpSheet->getStyle($cellRange)->getConditionalStyles();
336
            $conditionalStyles[] = $conditional;
337
338
            $xls->phpSheet->getStyle($cellRange)->setConditionalStyles($conditionalStyles);
339
        }
340
    }
341
}
342