Passed
Pull Request — master (#4279)
by Owen
18:14 queued 05:25
created

ConditionalFormatting::setCFRules()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 4

Importance

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

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