Failed Conditions
Pull Request — master (#4118)
by Owen
13:53
created

ConditionalFormatting::readCFRule2()   C

Complexity

Conditions 13
Paths 226

Size

Total Lines 106
Code Lines 58

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 182

Importance

Changes 0
Metric Value
eloc 58
c 0
b 0
f 0
dl 0
loc 106
rs 5.4583
ccs 0
cts 0
cp 0
cc 13
nc 226
nop 2
crap 182

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 22
        0x06 => Conditional::OPERATOR_LESSTHAN,
33
        0x07 => Conditional::OPERATOR_GREATERTHANOREQUAL,
34 22
        0x08 => Conditional::OPERATOR_LESSTHANOREQUAL,
35 22
    ];
36
37
    public static function type(int $type): ?string
38
    {
39
        if (isset(self::$types[$type])) {
40
            return self::$types[$type];
41 22
        }
42
43 22
        return null;
44 22
    }
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