Failed Conditions
Pull Request — master (#4412)
by
unknown
22:45 queued 07:48
created

Styles::getAttribute()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.0175

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 2
dl 0
loc 13
ccs 7
cts 8
cp 0.875
crap 3.0175
rs 10
c 0
b 0
f 0
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 670
    public function setNamespace(string $namespace): void
37
    {
38 670
        $this->namespace = $namespace;
39
    }
40
41 670
    public function setWorkbookPalette(array $palette): void
42
    {
43 670
        $this->workbookPalette = $palette;
44
    }
45
46 672
    private function getStyleAttributes(SimpleXMLElement $value): SimpleXMLElement
47
    {
48 672
        $attr = $value->attributes('');
49 672
        if ($attr === null || count($attr) === 0) {
50 668
            $attr = $value->attributes($this->namespace);
51
        }
52
53 672
        return Xlsx::testSimpleXml($attr);
54
    }
55
56 670
    public function setStyleXml(SimpleXMLElement $styleXml): void
57
    {
58 670
        $this->styleXml = $styleXml;
59
    }
60
61 650
    public function setTheme(Theme $theme): void
62
    {
63 650
        $this->theme = $theme;
64
    }
65
66 670
    public function setStyleBaseData(?Theme $theme = null, array $styles = [], array $cellStyles = []): void
67
    {
68 670
        $this->theme = $theme;
69 670
        $this->styles = $styles;
70 670
        $this->cellStyles = $cellStyles;
71
    }
72
73 668
    public function readFontStyle(Font $fontStyle, SimpleXMLElement $fontStyleXml): void
74
    {
75 668
        if (isset($fontStyleXml->name)) {
76 667
            $attr = $this->getStyleAttributes($fontStyleXml->name);
77 667
            if (isset($attr['val'])) {
78 667
                $fontStyle->setName((string) $attr['val']);
79
            }
80
        }
81 668
        if (isset($fontStyleXml->sz)) {
82 667
            $attr = $this->getStyleAttributes($fontStyleXml->sz);
83 667
            if (isset($attr['val'])) {
84 667
                $fontStyle->setSize((float) $attr['val']);
85
            }
86
        }
87 668
        if (isset($fontStyleXml->b)) {
88 509
            $attr = $this->getStyleAttributes($fontStyleXml->b);
89 509
            $fontStyle->setBold(!isset($attr['val']) || self::boolean((string) $attr['val']));
90
        }
91 668
        if (isset($fontStyleXml->i)) {
92 471
            $attr = $this->getStyleAttributes($fontStyleXml->i);
93 471
            $fontStyle->setItalic(!isset($attr['val']) || self::boolean((string) $attr['val']));
94
        }
95 668
        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 668
        $fontStyle->getColor()->setARGB($this->readColor($fontStyleXml->color));
100
101 668
        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 668
        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 668
        if (isset($fontStyleXml->scheme)) {
121 392
            $attr = $this->getStyleAttributes($fontStyleXml->scheme);
122 392
            $fontStyle->setScheme((string) $attr['val']);
123
        }
124
    }
125
126 235
    private function readNumberFormat(NumberFormat $numfmtStyle, SimpleXMLElement $numfmtStyleXml): void
127
    {
128 235
        if ((string) $numfmtStyleXml['formatCode'] !== '') {
129
            $numfmtStyle->setFormatCode(self::formatGeneral((string) $numfmtStyleXml['formatCode']));
130
131
            return;
132
        }
133 235
        $numfmt = $this->getStyleAttributes($numfmtStyleXml);
134 235
        if (isset($numfmt['formatCode'])) {
135 197
            $numfmtStyle->setFormatCode(self::formatGeneral((string) $numfmt['formatCode']));
136
        }
137
    }
138
139 668
    public function readFillStyle(Fill $fillStyle, SimpleXMLElement $fillStyleXml): void
140
    {
141 668
        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 668
        } elseif ($fillStyleXml->patternFill) {
153 667
            $defaultFillStyle = ($fillStyle->getFillType() !== null) ? Fill::FILL_NONE : '';
154 667
            $fgFound = false;
155 667
            $bgFound = false;
156 667
            if ($fillStyleXml->patternFill->fgColor) {
157 262
                $fillStyle->getStartColor()->setARGB($this->readColor($fillStyleXml->patternFill->fgColor, true));
158 262
                if ($fillStyle->getFillType() !== null) {
159 82
                    $defaultFillStyle = Fill::FILL_SOLID;
160
                }
161 262
                $fgFound = true;
162
            }
163 667
            if ($fillStyleXml->patternFill->bgColor) {
164 273
                $fillStyle->getEndColor()->setARGB($this->readColor($fillStyleXml->patternFill->bgColor, true));
165 273
                if ($fillStyle->getFillType() !== null) {
166 78
                    $defaultFillStyle = Fill::FILL_SOLID;
167
                }
168 273
                $bgFound = true;
169
            }
170
171 667
            $type = '';
172 667
            if ((string) $fillStyleXml->patternFill['patternType'] !== '') {
173
                $type = (string) $fillStyleXml->patternFill['patternType'];
174
            } else {
175 667
                $attr = $this->getStyleAttributes($fillStyleXml->patternFill);
176 667
                $type = (string) $attr['patternType'];
177
            }
178 667
            $patternType = ($type === '') ? $defaultFillStyle : $type;
179
180 667
            $fillStyle->setFillType($patternType);
181
            if (
182 667
                !$fgFound // no foreground color specified
183 667
                && !in_array($patternType, [Fill::FILL_NONE, Fill::FILL_SOLID], true) // these patterns aren't relevant
184 667
                && $fillStyle->getStartColor()->getARGB() // not conditional
185
            ) {
186 11
                $fillStyle->getStartColor()
187 11
                    ->setARGB('', true);
188
            }
189
            if (
190 667
                !$bgFound // no background color specified
191 667
                && !in_array($patternType, [Fill::FILL_NONE, Fill::FILL_SOLID], true) // these patterns aren't relevant
192 667
                && $fillStyle->getEndColor()->getARGB() // not conditional
193
            ) {
194 11
                $fillStyle->getEndColor()
195 11
                    ->setARGB('', true);
196
            }
197
        }
198
    }
199
200 668
    public function readBorderStyle(Borders $borderStyle, SimpleXMLElement $borderStyleXml): void
201
    {
202 668
        $diagonalUp = $this->getAttribute($borderStyleXml, 'diagonalUp');
203 668
        $diagonalUp = self::boolean($diagonalUp);
204 668
        $diagonalDown = $this->getAttribute($borderStyleXml, 'diagonalDown');
205 668
        $diagonalDown = self::boolean($diagonalDown);
206 668
        if ($diagonalUp === false) {
207 668
            if ($diagonalDown === false) {
208 668
                $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 668
        if (isset($borderStyleXml->left)) {
219 651
            $this->readBorder($borderStyle->getLeft(), $borderStyleXml->left);
220
        }
221 668
        if (isset($borderStyleXml->right)) {
222 650
            $this->readBorder($borderStyle->getRight(), $borderStyleXml->right);
223
        }
224 668
        if (isset($borderStyleXml->top)) {
225 652
            $this->readBorder($borderStyle->getTop(), $borderStyleXml->top);
226
        }
227 668
        if (isset($borderStyleXml->bottom)) {
228 651
            $this->readBorder($borderStyle->getBottom(), $borderStyleXml->bottom);
229
        }
230 668
        if (isset($borderStyleXml->diagonal)) {
231 648
            $this->readBorder($borderStyle->getDiagonal(), $borderStyleXml->diagonal);
232
        }
233
    }
234
235 668
    private function getAttribute(SimpleXMLElement $xml, string $attribute): string
236
    {
237 668
        $style = '';
238 668
        if ((string) $xml[$attribute] !== '') {
239
            $style = (string) $xml[$attribute];
240
        } else {
241 668
            $attr = $this->getStyleAttributes($xml);
242 668
            if (isset($attr[$attribute])) {
243 347
                $style = (string) $attr[$attribute];
244
            }
245
        }
246
247 668
        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 668
    public function readAlignmentStyle(Alignment $alignment, SimpleXMLElement $alignmentXml): void
264
    {
265 668
        $horizontal = (string) $this->getAttribute($alignmentXml, 'horizontal');
266 668
        if ($horizontal !== '') {
267 313
            $alignment->setHorizontal($horizontal);
268
        }
269 668
        $justifyLastLine = (string) $this->getAttribute($alignmentXml, 'justifyLastLine');
270 668
        if ($justifyLastLine !== '') {
271 20
            $alignment->setJustifyLastLine(
272 20
                self::boolean($justifyLastLine)
273 20
            );
274
        }
275 668
        $vertical = (string) $this->getAttribute($alignmentXml, 'vertical');
276 668
        if ($vertical !== '') {
277 298
            $alignment->setVertical($vertical);
278
        }
279
280 668
        $textRotation = (int) $this->getAttribute($alignmentXml, 'textRotation');
281 668
        if ($textRotation > 90) {
282 2
            $textRotation = 90 - $textRotation;
283
        }
284 668
        $alignment->setTextRotation($textRotation);
285
286 668
        $wrapText = $this->getAttribute($alignmentXml, 'wrapText');
287 668
        $alignment->setWrapText(self::boolean((string) $wrapText));
288 668
        $shrinkToFit = $this->getAttribute($alignmentXml, 'shrinkToFit');
289 668
        $alignment->setShrinkToFit(self::boolean((string) $shrinkToFit));
290 668
        $indent = (int) $this->getAttribute($alignmentXml, 'indent');
291 668
        $alignment->setIndent(max($indent, 0));
292 668
        $readingOrder = (int) $this->getAttribute($alignmentXml, 'readingOrder');
293 668
        $alignment->setReadOrder(max($readingOrder, 0));
294
    }
295
296 668
    private static function formatGeneral(string $formatString): string
297
    {
298 668
        if ($formatString === 'GENERAL') {
299 1
            $formatString = NumberFormat::FORMAT_GENERAL;
300
        }
301
302 668
        return $formatString;
303
    }
304
305
    /**
306
     * Read style.
307
     */
308 668
    public function readStyle(Style $docStyle, SimpleXMLElement|stdClass $style): void
309
    {
310 668
        if ($style instanceof SimpleXMLElement) {
311 235
            $this->readNumberFormat($docStyle->getNumberFormat(), $style->numFmt);
312
        } else {
313 668
            $docStyle->getNumberFormat()->setFormatCode(self::formatGeneral((string) $style->numFmt));
314
        }
315
316 668
        if (isset($style->font)) {
317 668
            $this->readFontStyle($docStyle->getFont(), $style->font);
318
        }
319
320 668
        if (isset($style->fill)) {
321 668
            $this->readFillStyle($docStyle->getFill(), $style->fill);
322
        }
323
324 668
        if (isset($style->border)) {
325 668
            $this->readBorderStyle($docStyle->getBorders(), $style->border);
326
        }
327
328 668
        if (isset($style->alignment)) {
329 668
            $this->readAlignmentStyle($docStyle->getAlignment(), $style->alignment);
330
        }
331
332
        // protection
333 668
        if (isset($style->protection)) {
334 668
            $this->readProtectionLocked($docStyle, $style->protection);
335 668
            $this->readProtectionHidden($docStyle, $style->protection);
336
        }
337
338
        // top-level style settings
339 668
        if (isset($style->quotePrefix)) {
340 668
            $docStyle->setQuotePrefix((bool) $style->quotePrefix);
341
        }
342
    }
343
344
    /**
345
     * Read protection locked attribute.
346
     */
347 668
    public function readProtectionLocked(Style $docStyle, SimpleXMLElement $style): void
348
    {
349 668
        $locked = '';
350 668
        if ((string) $style['locked'] !== '') {
351
            $locked = (string) $style['locked'];
352
        } else {
353 668
            $attr = $this->getStyleAttributes($style);
354 668
            if (isset($attr['locked'])) {
355 40
                $locked = (string) $attr['locked'];
356
            }
357
        }
358 668
        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 668
    public function readProtectionHidden(Style $docStyle, SimpleXMLElement $style): void
371
    {
372 668
        $hidden = '';
373 668
        if ((string) $style['hidden'] !== '') {
374
            $hidden = (string) $style['hidden'];
375
        } else {
376 668
            $attr = $this->getStyleAttributes($style);
377 668
            if (isset($attr['hidden'])) {
378 22
                $hidden = (string) $attr['hidden'];
379
            }
380
        }
381 668
        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 672
    public function readColor(SimpleXMLElement $color, bool $background = false): string
391
    {
392 672
        $attr = $this->getStyleAttributes($color);
393 672
        if (isset($attr['rgb'])) {
394 556
            return (string) $attr['rgb'];
395
        }
396 434
        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 427
        if (isset($attr['theme'])) {
405 395
            if ($this->theme !== null) {
406 392
                $returnColour = $this->theme->getColourByIndex((int) $attr['theme']);
407 392
                if (isset($attr['tint'])) {
408 211
                    $tintAdjust = (float) $attr['tint'];
409 211
                    $returnColour = Color::changeBrightness($returnColour ?? '', $tintAdjust);
410
                }
411
412 392
                return 'FF' . $returnColour;
413
            }
414
        }
415
416 66
        return ($background) ? 'FFFFFFFF' : 'FF000000';
417
    }
418
419 670
    public function dxfs(bool $readDataOnly = false): array
420
    {
421 670
        $dxfs = [];
422 670
        if (!$readDataOnly && $this->styleXml) {
423
            //    Conditional Styles
424 668
            if ($this->styleXml->dxfs) {
425 647
                foreach ($this->styleXml->dxfs->dxf as $dxf) {
426 235
                    $style = new Style(false, true);
427 235
                    $this->readStyle($style, $dxf);
428 235
                    $dxfs[] = $style;
429
                }
430
            }
431
            //    Cell Styles
432 668
            if ($this->styleXml->cellStyles) {
433 664
                foreach ($this->styleXml->cellStyles->cellStyle as $cellStylex) {
434 664
                    $cellStyle = Xlsx::getAttributes($cellStylex);
435 664
                    if ((int) ($cellStyle['builtinId']) == 0) {
436 664
                        if (isset($this->cellStyles[(int) ($cellStyle['xfId'])])) {
437
                            // Set default style
438 664
                            $style = new Style();
439 664
                            $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 670
        return $dxfs;
449
    }
450
451
    // get TableStyles
452 670
    public function tableStyles(bool $readDataOnly = false): array
453
    {
454 670
        $tableStyles = [];
455 670
        if (!$readDataOnly && $this->styleXml) {
456
            //    Conditional Styles
457 668
            if ($this->styleXml->tableStyles) {
458 641
                foreach ($this->styleXml->tableStyles->tableStyle as $s) {
459 4
                    $attrs = Xlsx::getAttributes($s);
460 4
                    if (isset($attrs['name'][0])) {
461 4
                        $style = new TableDxfsStyle((string) ($attrs['name'][0]));
462 4
                        foreach ($s->tableStyleElement as $e) {
463 4
                            $a = Xlsx::getAttributes($e);
464 4
                            if (isset($a['dxfId'][0], $a['type'][0])) {
465 4
                                switch ($a['type'][0]) {
466 4
                                    case 'headerRow':
467 3
                                        $style->setHeaderRow((int) ($a['dxfId'][0]));
468
469 3
                                        break;
470 4
                                    case 'firstRowStripe':
471 4
                                        $style->setFirstRowStripe((int) ($a['dxfId'][0]));
472
473 4
                                        break;
474 4
                                    case 'secondRowStripe':
475 4
                                        $style->setSecondRowStripe((int) ($a['dxfId'][0]));
476
477 4
                                        break;
478
                                    default:
479
                                }
480
                            }
481
                        }
482 4
                        $tableStyles[] = $style;
483
                    }
484
                }
485
            }
486
        }
487
488 670
        return $tableStyles;
489
    }
490
491 670
    public function styles(): array
492
    {
493 670
        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