Passed
Pull Request — master (#4478)
by Owen
15:20
created

Styles   F

Complexity

Total Complexity 122

Size/Duplication

Total Lines 516
Duplicated Lines 0 %

Test Coverage

Coverage 97.74%

Importance

Changes 0
Metric Value
wmc 122
eloc 273
dl 0
loc 516
ccs 259
cts 265
cp 0.9774
rs 2
c 0
b 0
f 0

23 Methods

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

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