Failed Conditions
Pull Request — master (#4501)
by Owen
18:19 queued 10:13
created

Styles::getFontCharsets()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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