Passed
Pull Request — master (#4373)
by Owen
23:56 queued 12:19
created

Styles::setStyleBaseData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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