Failed Conditions
Pull Request — master (#4250)
by Owen
10:44
created

Styles::readBorder()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

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