Passed
Push — master ( e65bc8...3bb10f )
by
unknown
17:18 queued 09:44
created

Styles::readColorTheme()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4

Importance

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