Passed
Push — master ( f9b1b7...3922d9 )
by
unknown
18:56 queued 11:14
created

ConditionalFormatting   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 331
Duplicated Lines 0 %

Test Coverage

Coverage 95.27%

Importance

Changes 0
Metric Value
wmc 45
eloc 176
c 0
b 0
f 0
dl 0
loc 331
ccs 161
cts 169
cp 0.9527
rs 8.8

9 Methods

Rating   Name   Duplication   Size   Complexity  
A type() 0 3 1
A operator() 0 3 1
A getCFFillStyle() 0 23 4
A readCFHeader2() 0 22 3
C readCFRule2() 0 106 13
A readCFFormula() 0 14 4
A setCFRules() 0 24 4
B getCFBorderStyle() 0 39 7
B getCFFontStyle() 0 30 8

How to fix   Complexity   

Complex Class

Complex classes like ConditionalFormatting often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ConditionalFormatting, and based on these observations, apply Extract Interface, too.

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