Passed
Pull Request — master (#4453)
by Owen
16:43 queued 04:10
created

Styles   F

Complexity

Total Complexity 117

Size/Duplication

Total Lines 498
Duplicated Lines 0 %

Test Coverage

Coverage 97.74%

Importance

Changes 0
Metric Value
wmc 117
eloc 263
c 0
b 0
f 0
dl 0
loc 498
ccs 259
cts 265
cp 0.9774
rs 2

22 Methods

Rating   Name   Duplication   Size   Complexity  
A setNamespace() 0 3 1
A setStyleBaseData() 0 5 1
A readProtectionLocked() 0 16 5
B readColor() 0 27 8
A setTheme() 0 3 1
B dxfs() 0 30 9
A readAlignmentStyle() 0 31 5
A getArrayItem() 0 3 2
A styles() 0 3 1
F readFillStyle() 0 57 17
A readNumberFormat() 0 10 3
B readBorderStyle() 0 32 9
A formatGeneral() 0 7 2
F readFontStyle() 0 50 18
A readProtectionHidden() 0 16 5
A getAttribute() 0 13 3
B tableStyles() 0 37 11
A readBorder() 0 10 3
B readStyle() 0 36 8
A setWorkbookPalette() 0 3 1
A getStyleAttributes() 0 8 3
A setStyleXml() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Styles 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 Styles, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
4
5
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
6
use PhpOffice\PhpSpreadsheet\Style\Alignment;
7
use PhpOffice\PhpSpreadsheet\Style\Border;
8
use PhpOffice\PhpSpreadsheet\Style\Borders;
9
use PhpOffice\PhpSpreadsheet\Style\Color;
10
use PhpOffice\PhpSpreadsheet\Style\Fill;
11
use PhpOffice\PhpSpreadsheet\Style\Font;
12
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
13
use PhpOffice\PhpSpreadsheet\Style\Protection;
14
use PhpOffice\PhpSpreadsheet\Style\Style;
15
use PhpOffice\PhpSpreadsheet\Worksheet\Table\TableDxfsStyle;
16
use SimpleXMLElement;
17
use stdClass;
18
19
class Styles extends BaseParserClass
20
{
21
    /**
22
     * Theme instance.
23
     */
24
    private ?Theme $theme = null;
25
26
    /** @var string[] */
27
    private array $workbookPalette = [];
28
29
    /** @var mixed[] */
30
    private array $styles = [];
31
32
    /** @var array<SimpleXMLElement|stdClass> */
33
    private array $cellStyles = [];
34
35
    private SimpleXMLElement $styleXml;
36
37
    private string $namespace = '';
38
39 675
    public function setNamespace(string $namespace): void
40
    {
41 675
        $this->namespace = $namespace;
42
    }
43
44
    /** @param string[] $palette */
45 675
    public function setWorkbookPalette(array $palette): void
46
    {
47 675
        $this->workbookPalette = $palette;
48
    }
49
50 677
    private function getStyleAttributes(SimpleXMLElement $value): SimpleXMLElement
51
    {
52 677
        $attr = $value->attributes('');
53 677
        if ($attr === null || count($attr) === 0) {
54 673
            $attr = $value->attributes($this->namespace);
55
        }
56
57 677
        return Xlsx::testSimpleXml($attr);
58
    }
59
60 675
    public function setStyleXml(SimpleXMLElement $styleXml): void
61
    {
62 675
        $this->styleXml = $styleXml;
63
    }
64
65 655
    public function setTheme(Theme $theme): void
66
    {
67 655
        $this->theme = $theme;
68
    }
69
70
    /**
71
     * @param mixed[] $styles
72
     * @param array<SimpleXMLElement|stdClass> $cellStyles
73
     */
74 675
    public function setStyleBaseData(?Theme $theme = null, array $styles = [], array $cellStyles = []): void
75
    {
76 675
        $this->theme = $theme;
77 675
        $this->styles = $styles;
78 675
        $this->cellStyles = $cellStyles;
79
    }
80
81 673
    public function readFontStyle(Font $fontStyle, SimpleXMLElement $fontStyleXml): void
82
    {
83 673
        if (isset($fontStyleXml->name)) {
84 672
            $attr = $this->getStyleAttributes($fontStyleXml->name);
85 672
            if (isset($attr['val'])) {
86 672
                $fontStyle->setName((string) $attr['val']);
87
            }
88
        }
89 673
        if (isset($fontStyleXml->sz)) {
90 672
            $attr = $this->getStyleAttributes($fontStyleXml->sz);
91 672
            if (isset($attr['val'])) {
92 672
                $fontStyle->setSize((float) $attr['val']);
93
            }
94
        }
95 673
        if (isset($fontStyleXml->b)) {
96 511
            $attr = $this->getStyleAttributes($fontStyleXml->b);
97 511
            $fontStyle->setBold(!isset($attr['val']) || self::boolean((string) $attr['val']));
98
        }
99 673
        if (isset($fontStyleXml->i)) {
100 473
            $attr = $this->getStyleAttributes($fontStyleXml->i);
101 473
            $fontStyle->setItalic(!isset($attr['val']) || self::boolean((string) $attr['val']));
102
        }
103 673
        if (isset($fontStyleXml->strike)) {
104 454
            $attr = $this->getStyleAttributes($fontStyleXml->strike);
105 454
            $fontStyle->setStrikethrough(!isset($attr['val']) || self::boolean((string) $attr['val']));
106
        }
107 673
        $fontStyle->getColor()->setARGB($this->readColor($fontStyleXml->color));
108
109 673
        if (isset($fontStyleXml->u)) {
110 466
            $attr = $this->getStyleAttributes($fontStyleXml->u);
111 466
            if (!isset($attr['val'])) {
112 203
                $fontStyle->setUnderline(Font::UNDERLINE_SINGLE);
113
            } else {
114 448
                $fontStyle->setUnderline((string) $attr['val']);
115
            }
116
        }
117 673
        if (isset($fontStyleXml->vertAlign)) {
118 24
            $attr = $this->getStyleAttributes($fontStyleXml->vertAlign);
119 24
            if (isset($attr['val'])) {
120 24
                $verticalAlign = strtolower((string) $attr['val']);
121 24
                if ($verticalAlign === 'superscript') {
122 2
                    $fontStyle->setSuperscript(true);
123 24
                } elseif ($verticalAlign === 'subscript') {
124 2
                    $fontStyle->setSubscript(true);
125
                }
126
            }
127
        }
128 673
        if (isset($fontStyleXml->scheme)) {
129 396
            $attr = $this->getStyleAttributes($fontStyleXml->scheme);
130 396
            $fontStyle->setScheme((string) $attr['val']);
131
        }
132
    }
133
134 239
    private function readNumberFormat(NumberFormat $numfmtStyle, SimpleXMLElement $numfmtStyleXml): void
135
    {
136 239
        if ((string) $numfmtStyleXml['formatCode'] !== '') {
137
            $numfmtStyle->setFormatCode(self::formatGeneral((string) $numfmtStyleXml['formatCode']));
138
139
            return;
140
        }
141 239
        $numfmt = $this->getStyleAttributes($numfmtStyleXml);
142 239
        if (isset($numfmt['formatCode'])) {
143 197
            $numfmtStyle->setFormatCode(self::formatGeneral((string) $numfmt['formatCode']));
144
        }
145
    }
146
147 673
    public function readFillStyle(Fill $fillStyle, SimpleXMLElement $fillStyleXml): void
148
    {
149 673
        if ($fillStyleXml->gradientFill) {
150
            /** @var SimpleXMLElement $gradientFill */
151 4
            $gradientFill = $fillStyleXml->gradientFill[0];
152 4
            $attr = $this->getStyleAttributes($gradientFill);
153 4
            if (!empty($attr['type'])) {
154 2
                $fillStyle->setFillType((string) $attr['type']);
155
            }
156 4
            $fillStyle->setRotation((float) ($attr['degree']));
157 4
            $gradientFill->registerXPathNamespace('sml', Namespaces::MAIN);
158 4
            $fillStyle->getStartColor()->setARGB($this->readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color)); //* @phpstan-ignore-line
159 4
            $fillStyle->getEndColor()->setARGB($this->readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color)); //* @phpstan-ignore-line
160 673
        } elseif ($fillStyleXml->patternFill) {
161 672
            $defaultFillStyle = ($fillStyle->getFillType() !== null) ? Fill::FILL_NONE : '';
162 672
            $fgFound = false;
163 672
            $bgFound = false;
164 672
            if ($fillStyleXml->patternFill->fgColor) {
165 266
                $fillStyle->getStartColor()->setARGB($this->readColor($fillStyleXml->patternFill->fgColor, true));
166 266
                if ($fillStyle->getFillType() !== null) {
167 82
                    $defaultFillStyle = Fill::FILL_SOLID;
168
                }
169 266
                $fgFound = true;
170
            }
171 672
            if ($fillStyleXml->patternFill->bgColor) {
172 277
                $fillStyle->getEndColor()->setARGB($this->readColor($fillStyleXml->patternFill->bgColor, true));
173 277
                if ($fillStyle->getFillType() !== null) {
174 78
                    $defaultFillStyle = Fill::FILL_SOLID;
175
                }
176 277
                $bgFound = true;
177
            }
178
179 672
            $type = '';
180 672
            if ((string) $fillStyleXml->patternFill['patternType'] !== '') {
181
                $type = (string) $fillStyleXml->patternFill['patternType'];
182
            } else {
183 672
                $attr = $this->getStyleAttributes($fillStyleXml->patternFill);
184 672
                $type = (string) $attr['patternType'];
185
            }
186 672
            $patternType = ($type === '') ? $defaultFillStyle : $type;
187
188 672
            $fillStyle->setFillType($patternType);
189
            if (
190 672
                !$fgFound // no foreground color specified
191 672
                && !in_array($patternType, [Fill::FILL_NONE, Fill::FILL_SOLID], true) // these patterns aren't relevant
192 672
                && $fillStyle->getStartColor()->getARGB() // not conditional
193
            ) {
194 11
                $fillStyle->getStartColor()
195 11
                    ->setARGB('', true);
196
            }
197
            if (
198 672
                !$bgFound // no background color specified
199 672
                && !in_array($patternType, [Fill::FILL_NONE, Fill::FILL_SOLID], true) // these patterns aren't relevant
200 672
                && $fillStyle->getEndColor()->getARGB() // not conditional
201
            ) {
202 11
                $fillStyle->getEndColor()
203 11
                    ->setARGB('', true);
204
            }
205
        }
206
    }
207
208 673
    public function readBorderStyle(Borders $borderStyle, SimpleXMLElement $borderStyleXml): void
209
    {
210 673
        $diagonalUp = $this->getAttribute($borderStyleXml, 'diagonalUp');
211 673
        $diagonalUp = self::boolean($diagonalUp);
212 673
        $diagonalDown = $this->getAttribute($borderStyleXml, 'diagonalDown');
213 673
        $diagonalDown = self::boolean($diagonalDown);
214 673
        if ($diagonalUp === false) {
215 673
            if ($diagonalDown === false) {
216 673
                $borderStyle->setDiagonalDirection(Borders::DIAGONAL_NONE);
217
            } else {
218 2
                $borderStyle->setDiagonalDirection(Borders::DIAGONAL_DOWN);
219
            }
220 3
        } elseif ($diagonalDown === false) {
221 2
            $borderStyle->setDiagonalDirection(Borders::DIAGONAL_UP);
222
        } else {
223 1
            $borderStyle->setDiagonalDirection(Borders::DIAGONAL_BOTH);
224
        }
225
226 673
        if (isset($borderStyleXml->left)) {
227 654
            $this->readBorder($borderStyle->getLeft(), $borderStyleXml->left);
228
        }
229 673
        if (isset($borderStyleXml->right)) {
230 653
            $this->readBorder($borderStyle->getRight(), $borderStyleXml->right);
231
        }
232 673
        if (isset($borderStyleXml->top)) {
233 655
            $this->readBorder($borderStyle->getTop(), $borderStyleXml->top);
234
        }
235 673
        if (isset($borderStyleXml->bottom)) {
236 654
            $this->readBorder($borderStyle->getBottom(), $borderStyleXml->bottom);
237
        }
238 673
        if (isset($borderStyleXml->diagonal)) {
239 651
            $this->readBorder($borderStyle->getDiagonal(), $borderStyleXml->diagonal);
240
        }
241
    }
242
243 673
    private function getAttribute(SimpleXMLElement $xml, string $attribute): string
244
    {
245 673
        $style = '';
246 673
        if ((string) $xml[$attribute] !== '') {
247
            $style = (string) $xml[$attribute];
248
        } else {
249 673
            $attr = $this->getStyleAttributes($xml);
250 673
            if (isset($attr[$attribute])) {
251 349
                $style = (string) $attr[$attribute];
252
            }
253
        }
254
255 673
        return $style;
256
    }
257
258 655
    private function readBorder(Border $border, SimpleXMLElement $borderXml): void
259
    {
260 655
        $style = $this->getAttribute($borderXml, 'style');
261 655
        if ($style !== '') {
262 81
            $border->setBorderStyle((string) $style);
263
        } else {
264 654
            $border->setBorderStyle(Border::BORDER_NONE);
265
        }
266 655
        if (isset($borderXml->color)) {
267 79
            $border->getColor()->setARGB($this->readColor($borderXml->color));
268
        }
269
    }
270
271 673
    public function readAlignmentStyle(Alignment $alignment, SimpleXMLElement $alignmentXml): void
272
    {
273 673
        $horizontal = (string) $this->getAttribute($alignmentXml, 'horizontal');
274 673
        if ($horizontal !== '') {
275 313
            $alignment->setHorizontal($horizontal);
276
        }
277 673
        $justifyLastLine = (string) $this->getAttribute($alignmentXml, 'justifyLastLine');
278 673
        if ($justifyLastLine !== '') {
279 20
            $alignment->setJustifyLastLine(
280 20
                self::boolean($justifyLastLine)
281 20
            );
282
        }
283 673
        $vertical = (string) $this->getAttribute($alignmentXml, 'vertical');
284 673
        if ($vertical !== '') {
285 300
            $alignment->setVertical($vertical);
286
        }
287
288 673
        $textRotation = (int) $this->getAttribute($alignmentXml, 'textRotation');
289 673
        if ($textRotation > 90) {
290 2
            $textRotation = 90 - $textRotation;
291
        }
292 673
        $alignment->setTextRotation($textRotation);
293
294 673
        $wrapText = $this->getAttribute($alignmentXml, 'wrapText');
295 673
        $alignment->setWrapText(self::boolean((string) $wrapText));
296 673
        $shrinkToFit = $this->getAttribute($alignmentXml, 'shrinkToFit');
297 673
        $alignment->setShrinkToFit(self::boolean((string) $shrinkToFit));
298 673
        $indent = (int) $this->getAttribute($alignmentXml, 'indent');
299 673
        $alignment->setIndent(max($indent, 0));
300 673
        $readingOrder = (int) $this->getAttribute($alignmentXml, 'readingOrder');
301 673
        $alignment->setReadOrder(max($readingOrder, 0));
302
    }
303
304 673
    private static function formatGeneral(string $formatString): string
305
    {
306 673
        if ($formatString === 'GENERAL') {
307 1
            $formatString = NumberFormat::FORMAT_GENERAL;
308
        }
309
310 673
        return $formatString;
311
    }
312
313
    /**
314
     * Read style.
315
     */
316 673
    public function readStyle(Style $docStyle, SimpleXMLElement|stdClass $style): void
317
    {
318 673
        if ($style instanceof SimpleXMLElement) {
319 239
            $this->readNumberFormat($docStyle->getNumberFormat(), $style->numFmt);
320
        } else {
321
            /** @var SimpleXMLElement */
322 673
            $temp = $style->numFmt;
323 673
            $docStyle->getNumberFormat()->setFormatCode(self::formatGeneral((string) $temp));
324
        }
325
326
        /** @var SimpleXMLElement $style */
327 673
        if (isset($style->font)) {
328 673
            $this->readFontStyle($docStyle->getFont(), $style->font);
329
        }
330
331 673
        if (isset($style->fill)) {
332 673
            $this->readFillStyle($docStyle->getFill(), $style->fill);
333
        }
334
335 673
        if (isset($style->border)) {
336 673
            $this->readBorderStyle($docStyle->getBorders(), $style->border);
337
        }
338
339 673
        if (isset($style->alignment)) {
340 673
            $this->readAlignmentStyle($docStyle->getAlignment(), $style->alignment);
341
        }
342
343
        // protection
344 673
        if (isset($style->protection)) {
345 673
            $this->readProtectionLocked($docStyle, $style->protection);
346 673
            $this->readProtectionHidden($docStyle, $style->protection);
347
        }
348
349
        // top-level style settings
350 673
        if (isset($style->quotePrefix)) {
351 673
            $docStyle->setQuotePrefix((bool) $style->quotePrefix);
352
        }
353
    }
354
355
    /**
356
     * Read protection locked attribute.
357
     */
358 673
    public function readProtectionLocked(Style $docStyle, SimpleXMLElement $style): void
359
    {
360 673
        $locked = '';
361 673
        if ((string) $style['locked'] !== '') {
362
            $locked = (string) $style['locked'];
363
        } else {
364 673
            $attr = $this->getStyleAttributes($style);
365 673
            if (isset($attr['locked'])) {
366 40
                $locked = (string) $attr['locked'];
367
            }
368
        }
369 673
        if ($locked !== '') {
370 40
            if (self::boolean($locked)) {
371 17
                $docStyle->getProtection()->setLocked(Protection::PROTECTION_PROTECTED);
372
            } else {
373 23
                $docStyle->getProtection()->setLocked(Protection::PROTECTION_UNPROTECTED);
374
            }
375
        }
376
    }
377
378
    /**
379
     * Read protection hidden attribute.
380
     */
381 673
    public function readProtectionHidden(Style $docStyle, SimpleXMLElement $style): void
382
    {
383 673
        $hidden = '';
384 673
        if ((string) $style['hidden'] !== '') {
385
            $hidden = (string) $style['hidden'];
386
        } else {
387 673
            $attr = $this->getStyleAttributes($style);
388 673
            if (isset($attr['hidden'])) {
389 22
                $hidden = (string) $attr['hidden'];
390
            }
391
        }
392 673
        if ($hidden !== '') {
393 22
            if (self::boolean((string) $hidden)) {
394 1
                $docStyle->getProtection()->setHidden(Protection::PROTECTION_PROTECTED);
395
            } else {
396 21
                $docStyle->getProtection()->setHidden(Protection::PROTECTION_UNPROTECTED);
397
            }
398
        }
399
    }
400
401 677
    public function readColor(SimpleXMLElement $color, bool $background = false): string
402
    {
403 677
        $attr = $this->getStyleAttributes($color);
404 677
        if (isset($attr['rgb'])) {
405 559
            return (string) $attr['rgb'];
406
        }
407 438
        if (isset($attr['indexed'])) {
408 101
            $indexedColor = (int) $attr['indexed'];
409 101
            if ($indexedColor >= count($this->workbookPalette)) {
410 98
                return Color::indexedColor($indexedColor - 7, $background)->getARGB() ?? '';
411
            }
412
413 4
            return Color::indexedColor($indexedColor, $background, $this->workbookPalette)->getARGB() ?? '';
414
        }
415 431
        if (isset($attr['theme'])) {
416 399
            if ($this->theme !== null) {
417 396
                $returnColour = $this->theme->getColourByIndex((int) $attr['theme']);
418 396
                if (isset($attr['tint'])) {
419 211
                    $tintAdjust = (float) $attr['tint'];
420 211
                    $returnColour = Color::changeBrightness($returnColour ?? '', $tintAdjust);
421
                }
422
423 396
                return 'FF' . $returnColour;
424
            }
425
        }
426
427 69
        return ($background) ? 'FFFFFFFF' : 'FF000000';
428
    }
429
430
    /** @return mixed[] */
431 675
    public function dxfs(bool $readDataOnly = false): array
432
    {
433 675
        $dxfs = [];
434 675
        if (!$readDataOnly && $this->styleXml) {
435
            //    Conditional Styles
436 673
            if ($this->styleXml->dxfs) {
437 652
                foreach ($this->styleXml->dxfs->dxf as $dxf) {
438 239
                    $style = new Style(false, true);
439 239
                    $this->readStyle($style, $dxf);
440 239
                    $dxfs[] = $style;
441
                }
442
            }
443
            //    Cell Styles
444 673
            if ($this->styleXml->cellStyles) {
445 669
                foreach ($this->styleXml->cellStyles->cellStyle as $cellStylex) {
446 669
                    $cellStyle = Xlsx::getAttributes($cellStylex);
447 669
                    if ((int) ($cellStyle['builtinId']) == 0) {
448 669
                        if (isset($this->cellStyles[(int) ($cellStyle['xfId'])])) {
449
                            // Set default style
450 669
                            $style = new Style();
451 669
                            $this->readStyle($style, $this->cellStyles[(int) ($cellStyle['xfId'])]);
452
453
                            // normal style, currently not using it for anything
454
                        }
455
                    }
456
                }
457
            }
458
        }
459
460 675
        return $dxfs;
461
    }
462
463
    /** @return TableDxfsStyle[] */
464 675
    public function tableStyles(bool $readDataOnly = false): array
465
    {
466 675
        $tableStyles = [];
467 675
        if (!$readDataOnly && $this->styleXml) {
468
            //    Conditional Styles
469 673
            if ($this->styleXml->tableStyles) {
470 646
                foreach ($this->styleXml->tableStyles->tableStyle as $s) {
471 6
                    $attrs = Xlsx::getAttributes($s);
472 6
                    if (isset($attrs['name'][0])) {
473 6
                        $style = new TableDxfsStyle((string) ($attrs['name'][0]));
474 6
                        foreach ($s->tableStyleElement as $e) {
475 6
                            $a = Xlsx::getAttributes($e);
476 6
                            if (isset($a['dxfId'][0], $a['type'][0])) {
477 6
                                switch ($a['type'][0]) {
478 6
                                    case 'headerRow':
479 5
                                        $style->setHeaderRow((int) ($a['dxfId'][0]));
480
481 5
                                        break;
482 6
                                    case 'firstRowStripe':
483 6
                                        $style->setFirstRowStripe((int) ($a['dxfId'][0]));
484
485 6
                                        break;
486 6
                                    case 'secondRowStripe':
487 6
                                        $style->setSecondRowStripe((int) ($a['dxfId'][0]));
488
489 6
                                        break;
490
                                    default:
491
                                }
492
                            }
493
                        }
494 6
                        $tableStyles[] = $style;
495
                    }
496
                }
497
            }
498
        }
499
500 675
        return $tableStyles;
501
    }
502
503
    /** @return mixed[] */
504 675
    public function styles(): array
505
    {
506 675
        return $this->styles;
507
    }
508
509
    /**
510
     * Get array item.
511
     *
512
     * @param false|mixed[] $array (usually array, in theory can be false)
513
     */
514 4
    private static function getArrayItem(mixed $array): ?SimpleXMLElement
515
    {
516 4
        return is_array($array) ? ($array[0] ?? null) : null; // @phpstan-ignore-line
517
    }
518
}
519