Passed
Pull Request — master (#4501)
by Owen
11:46
created

Styles::getFontCharsets()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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