1<?php
2
3namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
4
5use PhpOffice\PhpSpreadsheet\Style\Alignment;
6use PhpOffice\PhpSpreadsheet\Style\Border;
7use PhpOffice\PhpSpreadsheet\Style\Borders;
8use PhpOffice\PhpSpreadsheet\Style\Color;
9use PhpOffice\PhpSpreadsheet\Style\Fill;
10use PhpOffice\PhpSpreadsheet\Style\Font;
11use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
12use PhpOffice\PhpSpreadsheet\Style\Protection;
13use PhpOffice\PhpSpreadsheet\Style\Style;
14use SimpleXMLElement;
15
16class Styles extends BaseParserClass
17{
18    /**
19     * Theme instance.
20     *
21     * @var Theme
22     */
23    private static $theme = null;
24
25    private $styles = [];
26
27    private $cellStyles = [];
28
29    private $styleXml;
30
31    public function __construct(SimpleXMLElement $styleXml)
32    {
33        $this->styleXml = $styleXml;
34    }
35
36    public function setStyleBaseData(?Theme $theme = null, $styles = [], $cellStyles = []): void
37    {
38        self::$theme = $theme;
39        $this->styles = $styles;
40        $this->cellStyles = $cellStyles;
41    }
42
43    private static function readFontStyle(Font $fontStyle, SimpleXMLElement $fontStyleXml): void
44    {
45        $fontStyle->setName((string) $fontStyleXml->name['val']);
46        $fontStyle->setSize((float) $fontStyleXml->sz['val']);
47
48        if (isset($fontStyleXml->b)) {
49            $fontStyle->setBold(!isset($fontStyleXml->b['val']) || self::boolean((string) $fontStyleXml->b['val']));
50        }
51        if (isset($fontStyleXml->i)) {
52            $fontStyle->setItalic(!isset($fontStyleXml->i['val']) || self::boolean((string) $fontStyleXml->i['val']));
53        }
54        if (isset($fontStyleXml->strike)) {
55            $fontStyle->setStrikethrough(!isset($fontStyleXml->strike['val']) || self::boolean((string) $fontStyleXml->strike['val']));
56        }
57        $fontStyle->getColor()->setARGB(self::readColor($fontStyleXml->color));
58
59        if (isset($fontStyleXml->u) && !isset($fontStyleXml->u['val'])) {
60            $fontStyle->setUnderline(Font::UNDERLINE_SINGLE);
61        } elseif (isset($fontStyleXml->u, $fontStyleXml->u['val'])) {
62            $fontStyle->setUnderline((string) $fontStyleXml->u['val']);
63        }
64
65        if (isset($fontStyleXml->vertAlign, $fontStyleXml->vertAlign['val'])) {
66            $verticalAlign = strtolower((string) $fontStyleXml->vertAlign['val']);
67            if ($verticalAlign === 'superscript') {
68                $fontStyle->setSuperscript(true);
69            }
70            if ($verticalAlign === 'subscript') {
71                $fontStyle->setSubscript(true);
72            }
73        }
74    }
75
76    private static function readNumberFormat(NumberFormat $numfmtStyle, SimpleXMLElement $numfmtStyleXml): void
77    {
78        if ($numfmtStyleXml->count() === 0) {
79            return;
80        }
81        $numfmt = $numfmtStyleXml->attributes();
82        if ($numfmt->count() > 0 && isset($numfmt['formatCode'])) {
83            $numfmtStyle->setFormatCode((string) $numfmt['formatCode']);
84        }
85    }
86
87    private static function readFillStyle(Fill $fillStyle, SimpleXMLElement $fillStyleXml): void
88    {
89        if ($fillStyleXml->gradientFill) {
90            /** @var SimpleXMLElement $gradientFill */
91            $gradientFill = $fillStyleXml->gradientFill[0];
92            if (!empty($gradientFill['type'])) {
93                $fillStyle->setFillType((string) $gradientFill['type']);
94            }
95            $fillStyle->setRotation((float) ($gradientFill['degree']));
96            $gradientFill->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
97            $fillStyle->getStartColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color));
98            $fillStyle->getEndColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color));
99        } elseif ($fillStyleXml->patternFill) {
100            $patternType = (string) $fillStyleXml->patternFill['patternType'] != '' ? (string) $fillStyleXml->patternFill['patternType'] : 'solid';
101            $fillStyle->setFillType($patternType);
102            if ($fillStyleXml->patternFill->fgColor) {
103                $fillStyle->getStartColor()->setARGB(self::readColor($fillStyleXml->patternFill->fgColor, true));
104            } else {
105                $fillStyle->getStartColor()->setARGB('FF000000');
106            }
107            if ($fillStyleXml->patternFill->bgColor) {
108                $fillStyle->getEndColor()->setARGB(self::readColor($fillStyleXml->patternFill->bgColor, true));
109            }
110        }
111    }
112
113    private static function readBorderStyle(Borders $borderStyle, SimpleXMLElement $borderStyleXml): void
114    {
115        $diagonalUp = self::boolean((string) $borderStyleXml['diagonalUp']);
116        $diagonalDown = self::boolean((string) $borderStyleXml['diagonalDown']);
117        if (!$diagonalUp && !$diagonalDown) {
118            $borderStyle->setDiagonalDirection(Borders::DIAGONAL_NONE);
119        } elseif ($diagonalUp && !$diagonalDown) {
120            $borderStyle->setDiagonalDirection(Borders::DIAGONAL_UP);
121        } elseif (!$diagonalUp && $diagonalDown) {
122            $borderStyle->setDiagonalDirection(Borders::DIAGONAL_DOWN);
123        } else {
124            $borderStyle->setDiagonalDirection(Borders::DIAGONAL_BOTH);
125        }
126
127        self::readBorder($borderStyle->getLeft(), $borderStyleXml->left);
128        self::readBorder($borderStyle->getRight(), $borderStyleXml->right);
129        self::readBorder($borderStyle->getTop(), $borderStyleXml->top);
130        self::readBorder($borderStyle->getBottom(), $borderStyleXml->bottom);
131        self::readBorder($borderStyle->getDiagonal(), $borderStyleXml->diagonal);
132    }
133
134    private static function readBorder(Border $border, SimpleXMLElement $borderXml): void
135    {
136        if (isset($borderXml['style'])) {
137            $border->setBorderStyle((string) $borderXml['style']);
138        }
139        if (isset($borderXml->color)) {
140            $border->getColor()->setARGB(self::readColor($borderXml->color));
141        }
142    }
143
144    private static function readAlignmentStyle(Alignment $alignment, SimpleXMLElement $alignmentXml): void
145    {
146        $alignment->setHorizontal((string) $alignmentXml->alignment['horizontal']);
147        $alignment->setVertical((string) $alignmentXml->alignment['vertical']);
148
149        $textRotation = 0;
150        if ((int) $alignmentXml->alignment['textRotation'] <= 90) {
151            $textRotation = (int) $alignmentXml->alignment['textRotation'];
152        } elseif ((int) $alignmentXml->alignment['textRotation'] > 90) {
153            $textRotation = 90 - (int) $alignmentXml->alignment['textRotation'];
154        }
155
156        $alignment->setTextRotation((int) $textRotation);
157        $alignment->setWrapText(self::boolean((string) $alignmentXml->alignment['wrapText']));
158        $alignment->setShrinkToFit(self::boolean((string) $alignmentXml->alignment['shrinkToFit']));
159        $alignment->setIndent((int) ((string) $alignmentXml->alignment['indent']) > 0 ? (int) ((string) $alignmentXml->alignment['indent']) : 0);
160        $alignment->setReadOrder((int) ((string) $alignmentXml->alignment['readingOrder']) > 0 ? (int) ((string) $alignmentXml->alignment['readingOrder']) : 0);
161    }
162
163    private function readStyle(Style $docStyle, $style): void
164    {
165        if ($style->numFmt instanceof SimpleXMLElement) {
166            self::readNumberFormat($docStyle->getNumberFormat(), $style->numFmt);
167        } else {
168            $docStyle->getNumberFormat()->setFormatCode($style->numFmt);
169        }
170
171        if (isset($style->font)) {
172            self::readFontStyle($docStyle->getFont(), $style->font);
173        }
174
175        if (isset($style->fill)) {
176            self::readFillStyle($docStyle->getFill(), $style->fill);
177        }
178
179        if (isset($style->border)) {
180            self::readBorderStyle($docStyle->getBorders(), $style->border);
181        }
182
183        if (isset($style->alignment->alignment)) {
184            self::readAlignmentStyle($docStyle->getAlignment(), $style->alignment);
185        }
186
187        // protection
188        if (isset($style->protection)) {
189            $this->readProtectionLocked($docStyle, $style);
190            $this->readProtectionHidden($docStyle, $style);
191        }
192
193        // top-level style settings
194        if (isset($style->quotePrefix)) {
195            $docStyle->setQuotePrefix(true);
196        }
197    }
198
199    private function readProtectionLocked(Style $docStyle, $style): void
200    {
201        if (isset($style->protection['locked'])) {
202            if (self::boolean((string) $style->protection['locked'])) {
203                $docStyle->getProtection()->setLocked(Protection::PROTECTION_PROTECTED);
204            } else {
205                $docStyle->getProtection()->setLocked(Protection::PROTECTION_UNPROTECTED);
206            }
207        }
208    }
209
210    private function readProtectionHidden(Style $docStyle, $style): void
211    {
212        if (isset($style->protection['hidden'])) {
213            if (self::boolean((string) $style->protection['hidden'])) {
214                $docStyle->getProtection()->setHidden(Protection::PROTECTION_PROTECTED);
215            } else {
216                $docStyle->getProtection()->setHidden(Protection::PROTECTION_UNPROTECTED);
217            }
218        }
219    }
220
221    private static function readColor($color, $background = false)
222    {
223        if (isset($color['rgb'])) {
224            return (string) $color['rgb'];
225        } elseif (isset($color['indexed'])) {
226            return Color::indexedColor($color['indexed'] - 7, $background)->getARGB();
227        } elseif (isset($color['theme'])) {
228            if (self::$theme !== null) {
229                $returnColour = self::$theme->getColourByIndex((int) $color['theme']);
230                if (isset($color['tint'])) {
231                    $tintAdjust = (float) $color['tint'];
232                    $returnColour = Color::changeBrightness($returnColour, $tintAdjust);
233                }
234
235                return 'FF' . $returnColour;
236            }
237        }
238
239        return ($background) ? 'FFFFFFFF' : 'FF000000';
240    }
241
242    public function dxfs($readDataOnly = false)
243    {
244        $dxfs = [];
245        if (!$readDataOnly && $this->styleXml) {
246            //    Conditional Styles
247            if ($this->styleXml->dxfs) {
248                foreach ($this->styleXml->dxfs->dxf as $dxf) {
249                    $style = new Style(false, true);
250                    $this->readStyle($style, $dxf);
251                    $dxfs[] = $style;
252                }
253            }
254            //    Cell Styles
255            if ($this->styleXml->cellStyles) {
256                foreach ($this->styleXml->cellStyles->cellStyle as $cellStyle) {
257                    if ((int) ($cellStyle['builtinId']) == 0) {
258                        if (isset($this->cellStyles[(int) ($cellStyle['xfId'])])) {
259                            // Set default style
260                            $style = new Style();
261                            $this->readStyle($style, $this->cellStyles[(int) ($cellStyle['xfId'])]);
262
263                            // normal style, currently not using it for anything
264                        }
265                    }
266                }
267            }
268        }
269
270        return $dxfs;
271    }
272
273    public function styles()
274    {
275        return $this->styles;
276    }
277
278    private static function getArrayItem($array, $key = 0)
279    {
280        return $array[$key] ?? null;
281    }
282}
283