1<?php
2
3namespace PhpOffice\PhpSpreadsheet\Reader\Gnumeric;
4
5use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
6use PhpOffice\PhpSpreadsheet\Shared\Date;
7use PhpOffice\PhpSpreadsheet\Spreadsheet;
8use PhpOffice\PhpSpreadsheet\Style\Alignment;
9use PhpOffice\PhpSpreadsheet\Style\Border;
10use PhpOffice\PhpSpreadsheet\Style\Borders;
11use PhpOffice\PhpSpreadsheet\Style\Fill;
12use PhpOffice\PhpSpreadsheet\Style\Font;
13use SimpleXMLElement;
14
15class Styles
16{
17    /**
18     * @var Spreadsheet
19     */
20    private $spreadsheet;
21
22    /**
23     * @var bool
24     */
25    protected $readDataOnly = false;
26
27    /** @var array */
28    public static $mappings = [
29        'borderStyle' => [
30            '0' => Border::BORDER_NONE,
31            '1' => Border::BORDER_THIN,
32            '2' => Border::BORDER_MEDIUM,
33            '3' => Border::BORDER_SLANTDASHDOT,
34            '4' => Border::BORDER_DASHED,
35            '5' => Border::BORDER_THICK,
36            '6' => Border::BORDER_DOUBLE,
37            '7' => Border::BORDER_DOTTED,
38            '8' => Border::BORDER_MEDIUMDASHED,
39            '9' => Border::BORDER_DASHDOT,
40            '10' => Border::BORDER_MEDIUMDASHDOT,
41            '11' => Border::BORDER_DASHDOTDOT,
42            '12' => Border::BORDER_MEDIUMDASHDOTDOT,
43            '13' => Border::BORDER_MEDIUMDASHDOTDOT,
44        ],
45        'fillType' => [
46            '1' => Fill::FILL_SOLID,
47            '2' => Fill::FILL_PATTERN_DARKGRAY,
48            '3' => Fill::FILL_PATTERN_MEDIUMGRAY,
49            '4' => Fill::FILL_PATTERN_LIGHTGRAY,
50            '5' => Fill::FILL_PATTERN_GRAY125,
51            '6' => Fill::FILL_PATTERN_GRAY0625,
52            '7' => Fill::FILL_PATTERN_DARKHORIZONTAL, // horizontal stripe
53            '8' => Fill::FILL_PATTERN_DARKVERTICAL, // vertical stripe
54            '9' => Fill::FILL_PATTERN_DARKDOWN, // diagonal stripe
55            '10' => Fill::FILL_PATTERN_DARKUP, // reverse diagonal stripe
56            '11' => Fill::FILL_PATTERN_DARKGRID, // diagoanl crosshatch
57            '12' => Fill::FILL_PATTERN_DARKTRELLIS, // thick diagonal crosshatch
58            '13' => Fill::FILL_PATTERN_LIGHTHORIZONTAL,
59            '14' => Fill::FILL_PATTERN_LIGHTVERTICAL,
60            '15' => Fill::FILL_PATTERN_LIGHTUP,
61            '16' => Fill::FILL_PATTERN_LIGHTDOWN,
62            '17' => Fill::FILL_PATTERN_LIGHTGRID, // thin horizontal crosshatch
63            '18' => Fill::FILL_PATTERN_LIGHTTRELLIS, // thin diagonal crosshatch
64        ],
65        'horizontal' => [
66            '1' => Alignment::HORIZONTAL_GENERAL,
67            '2' => Alignment::HORIZONTAL_LEFT,
68            '4' => Alignment::HORIZONTAL_RIGHT,
69            '8' => Alignment::HORIZONTAL_CENTER,
70            '16' => Alignment::HORIZONTAL_CENTER_CONTINUOUS,
71            '32' => Alignment::HORIZONTAL_JUSTIFY,
72            '64' => Alignment::HORIZONTAL_CENTER_CONTINUOUS,
73        ],
74        'underline' => [
75            '1' => Font::UNDERLINE_SINGLE,
76            '2' => Font::UNDERLINE_DOUBLE,
77            '3' => Font::UNDERLINE_SINGLEACCOUNTING,
78            '4' => Font::UNDERLINE_DOUBLEACCOUNTING,
79        ],
80        'vertical' => [
81            '1' => Alignment::VERTICAL_TOP,
82            '2' => Alignment::VERTICAL_BOTTOM,
83            '4' => Alignment::VERTICAL_CENTER,
84            '8' => Alignment::VERTICAL_JUSTIFY,
85        ],
86    ];
87
88    public function __construct(Spreadsheet $spreadsheet, bool $readDataOnly)
89    {
90        $this->spreadsheet = $spreadsheet;
91        $this->readDataOnly = $readDataOnly;
92    }
93
94    public function read(SimpleXMLElement $sheet, int $maxRow, int $maxCol): void
95    {
96        if ($sheet->Styles->StyleRegion !== null) {
97            $this->readStyles($sheet->Styles->StyleRegion, $maxRow, $maxCol);
98        }
99    }
100
101    private function readStyles(SimpleXMLElement $styleRegion, int $maxRow, int $maxCol): void
102    {
103        foreach ($styleRegion as $style) {
104            if ($style === null) {
105                continue;
106            }
107
108            $styleAttributes = $style->attributes();
109            if ($styleAttributes !== null && ($styleAttributes['startRow'] <= $maxRow) && ($styleAttributes['startCol'] <= $maxCol)) {
110                $cellRange = $this->readStyleRange($styleAttributes, $maxCol, $maxRow);
111
112                $styleAttributes = $style->Style->attributes();
113
114                $styleArray = [];
115                // We still set the number format mask for date/time values, even if readDataOnly is true
116                //    so that we can identify whether a float is a float or a date value
117                $formatCode = (string) $styleAttributes['Format'];
118                if (Date::isDateTimeFormatCode($formatCode)) {
119                    $styleArray['numberFormat']['formatCode'] = $formatCode;
120                }
121                if ($this->readDataOnly === false && $styleAttributes !== null) {
122                    //    If readDataOnly is false, we set all formatting information
123                    $styleArray['numberFormat']['formatCode'] = $formatCode;
124                    $styleArray = $this->readStyle($styleArray, $styleAttributes, $style);
125                }
126                $this->spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($styleArray);
127            }
128        }
129    }
130
131    private function addBorderDiagonal(SimpleXMLElement $srssb, array &$styleArray): void
132    {
133        if (isset($srssb->Diagonal, $srssb->{'Rev-Diagonal'})) {
134            $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes());
135            $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_BOTH;
136        } elseif (isset($srssb->Diagonal)) {
137            $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes());
138            $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_UP;
139        } elseif (isset($srssb->{'Rev-Diagonal'})) {
140            $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->{'Rev-Diagonal'}->attributes());
141            $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_DOWN;
142        }
143    }
144
145    private function addBorderStyle(SimpleXMLElement $srssb, array &$styleArray, string $direction): void
146    {
147        $ucDirection = ucfirst($direction);
148        if (isset($srssb->$ucDirection)) {
149            $styleArray['borders'][$direction] = self::parseBorderAttributes($srssb->$ucDirection->attributes());
150        }
151    }
152
153    private function calcRotation(SimpleXMLElement $styleAttributes): int
154    {
155        $rotation = (int) $styleAttributes->Rotation;
156        if ($rotation >= 270 && $rotation <= 360) {
157            $rotation -= 360;
158        }
159        $rotation = (abs($rotation) > 90) ? 0 : $rotation;
160
161        return $rotation;
162    }
163
164    private static function addStyle(array &$styleArray, string $key, string $value): void
165    {
166        if (array_key_exists($value, self::$mappings[$key])) {
167            $styleArray[$key] = self::$mappings[$key][$value];
168        }
169    }
170
171    private static function addStyle2(array &$styleArray, string $key1, string $key, string $value): void
172    {
173        if (array_key_exists($value, self::$mappings[$key])) {
174            $styleArray[$key1][$key] = self::$mappings[$key][$value];
175        }
176    }
177
178    private static function parseBorderAttributes(?SimpleXMLElement $borderAttributes): array
179    {
180        $styleArray = [];
181        if ($borderAttributes !== null) {
182            if (isset($borderAttributes['Color'])) {
183                $styleArray['color']['rgb'] = self::parseGnumericColour($borderAttributes['Color']);
184            }
185
186            self::addStyle($styleArray, 'borderStyle', $borderAttributes['Style']);
187        }
188
189        return $styleArray;
190    }
191
192    private static function parseGnumericColour(string $gnmColour): string
193    {
194        [$gnmR, $gnmG, $gnmB] = explode(':', $gnmColour);
195        $gnmR = substr(str_pad($gnmR, 4, '0', STR_PAD_RIGHT), 0, 2);
196        $gnmG = substr(str_pad($gnmG, 4, '0', STR_PAD_RIGHT), 0, 2);
197        $gnmB = substr(str_pad($gnmB, 4, '0', STR_PAD_RIGHT), 0, 2);
198
199        return $gnmR . $gnmG . $gnmB;
200    }
201
202    private function addColors(array &$styleArray, SimpleXMLElement $styleAttributes): void
203    {
204        $RGB = self::parseGnumericColour($styleAttributes['Fore']);
205        $styleArray['font']['color']['rgb'] = $RGB;
206        $RGB = self::parseGnumericColour($styleAttributes['Back']);
207        $shade = (string) $styleAttributes['Shade'];
208        if (($RGB !== '000000') || ($shade !== '0')) {
209            $RGB2 = self::parseGnumericColour($styleAttributes['PatternColor']);
210            if ($shade === '1') {
211                $styleArray['fill']['startColor']['rgb'] = $RGB;
212                $styleArray['fill']['endColor']['rgb'] = $RGB2;
213            } else {
214                $styleArray['fill']['endColor']['rgb'] = $RGB;
215                $styleArray['fill']['startColor']['rgb'] = $RGB2;
216            }
217            self::addStyle2($styleArray, 'fill', 'fillType', $shade);
218        }
219    }
220
221    private function readStyleRange(SimpleXMLElement $styleAttributes, int $maxCol, int $maxRow): string
222    {
223        $startColumn = Coordinate::stringFromColumnIndex((int) $styleAttributes['startCol'] + 1);
224        $startRow = $styleAttributes['startRow'] + 1;
225
226        $endColumn = ($styleAttributes['endCol'] > $maxCol) ? $maxCol : (int) $styleAttributes['endCol'];
227        $endColumn = Coordinate::stringFromColumnIndex($endColumn + 1);
228
229        $endRow = 1 + (($styleAttributes['endRow'] > $maxRow) ? $maxRow : (int) $styleAttributes['endRow']);
230        $cellRange = $startColumn . $startRow . ':' . $endColumn . $endRow;
231
232        return $cellRange;
233    }
234
235    private function readStyle(array $styleArray, SimpleXMLElement $styleAttributes, SimpleXMLElement $style): array
236    {
237        self::addStyle2($styleArray, 'alignment', 'horizontal', $styleAttributes['HAlign']);
238        self::addStyle2($styleArray, 'alignment', 'vertical', $styleAttributes['VAlign']);
239        $styleArray['alignment']['wrapText'] = $styleAttributes['WrapText'] == '1';
240        $styleArray['alignment']['textRotation'] = $this->calcRotation($styleAttributes);
241        $styleArray['alignment']['shrinkToFit'] = $styleAttributes['ShrinkToFit'] == '1';
242        $styleArray['alignment']['indent'] = ((int) ($styleAttributes['Indent']) > 0) ? $styleAttributes['indent'] : 0;
243
244        $this->addColors($styleArray, $styleAttributes);
245
246        $fontAttributes = $style->Style->Font->attributes();
247        if ($fontAttributes !== null) {
248            $styleArray['font']['name'] = (string) $style->Style->Font;
249            $styleArray['font']['size'] = (int) ($fontAttributes['Unit']);
250            $styleArray['font']['bold'] = $fontAttributes['Bold'] == '1';
251            $styleArray['font']['italic'] = $fontAttributes['Italic'] == '1';
252            $styleArray['font']['strikethrough'] = $fontAttributes['StrikeThrough'] == '1';
253            self::addStyle2($styleArray, 'font', 'underline', $fontAttributes['Underline']);
254
255            switch ($fontAttributes['Script']) {
256                case '1':
257                    $styleArray['font']['superscript'] = true;
258
259                    break;
260                case '-1':
261                    $styleArray['font']['subscript'] = true;
262
263                    break;
264            }
265        }
266
267        if (isset($style->Style->StyleBorder)) {
268            $srssb = $style->Style->StyleBorder;
269            $this->addBorderStyle($srssb, $styleArray, 'top');
270            $this->addBorderStyle($srssb, $styleArray, 'bottom');
271            $this->addBorderStyle($srssb, $styleArray, 'left');
272            $this->addBorderStyle($srssb, $styleArray, 'right');
273            $this->addBorderDiagonal($srssb, $styleArray);
274        }
275        if (isset($style->Style->HyperLink)) {
276            //    TO DO
277            $hyperlink = $style->Style->HyperLink->attributes();
278        }
279
280        return $styleArray;
281    }
282}
283