Failed Conditions
Push — master ( 8e3417...f52ae2 )
by
unknown
18:26 queued 07:14
created

Styles::readColor()   B

Complexity

Conditions 8
Paths 9

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 8

Importance

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