Passed
Push — master ( fb74dc...f37b11 )
by
unknown
18:17 queued 06:47
created

Xml::getAttributes()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 2
dl 0
loc 5
rs 10
c 0
b 0
f 0
ccs 3
cts 4
cp 0.75
crap 2.0625
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Reader;
4
5
use DateTime;
6
use DateTimeZone;
7
use PhpOffice\PhpSpreadsheet\Cell\AddressHelper;
8
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
9
use PhpOffice\PhpSpreadsheet\Cell\DataType;
10
use PhpOffice\PhpSpreadsheet\DefinedName;
11
use PhpOffice\PhpSpreadsheet\Helper\Html as HelperHtml;
12
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
13
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
14
use PhpOffice\PhpSpreadsheet\Reader\Xml\PageSettings;
15
use PhpOffice\PhpSpreadsheet\Reader\Xml\Properties;
16
use PhpOffice\PhpSpreadsheet\Reader\Xml\Style;
17
use PhpOffice\PhpSpreadsheet\RichText\RichText;
18
use PhpOffice\PhpSpreadsheet\Shared\Date;
19
use PhpOffice\PhpSpreadsheet\Shared\File;
20
use PhpOffice\PhpSpreadsheet\Spreadsheet;
21
use PhpOffice\PhpSpreadsheet\Worksheet\SheetView;
22
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
23
use SimpleXMLElement;
24
use Throwable;
25
26
/**
27
 * Reader for SpreadsheetML, the XML schema for Microsoft Office Excel 2003.
28
 */
29
class Xml extends BaseReader
30
{
31
    public const NAMESPACES_SS = 'urn:schemas-microsoft-com:office:spreadsheet';
32
33
    /**
34
     * Formats.
35
     */
36
    protected array $styles = [];
37
38
    /**
39
     * Create a new Excel2003XML Reader instance.
40
     */
41 82
    public function __construct()
42
    {
43 82
        parent::__construct();
44 82
        $this->securityScanner = XmlScanner::getInstance($this);
45
        /** @var callable */
46 82
        $unentity = [self::class, 'unentity'];
47 82
        $this->securityScanner->setAdditionalCallback($unentity);
48
    }
49
50 66
    public static function unentity(string $contents): string
51
    {
52 66
        $contents = preg_replace('/&(amp|lt|gt|quot|apos);/', "\u{fffe}\u{feff}\$1;", trim($contents)) ?? $contents;
53 66
        $contents = html_entity_decode($contents, ENT_NOQUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8');
54 66
        $contents = str_replace("\u{fffe}\u{feff}", '&', $contents);
55
56 66
        return $contents;
57
    }
58
59
    private string $fileContents = '';
60
61
    private string $xmlFailMessage = '';
62
63 50
    public static function xmlMappings(): array
64
    {
65 50
        return array_merge(
66 50
            Style\Fill::FILL_MAPPINGS,
67 50
            Style\Border::BORDER_MAPPINGS
68 50
        );
69
    }
70
71
    /**
72
     * Can the current IReader read the file?
73
     */
74 51
    public function canRead(string $filename): bool
75
    {
76
        //    Office                    xmlns:o="urn:schemas-microsoft-com:office:office"
77
        //    Excel                    xmlns:x="urn:schemas-microsoft-com:office:excel"
78
        //    XML Spreadsheet            xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
79
        //    Spreadsheet component    xmlns:c="urn:schemas-microsoft-com:office:component:spreadsheet"
80
        //    XML schema                 xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
81
        //    XML data type            xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
82
        //    MS-persist recordset    xmlns:rs="urn:schemas-microsoft-com:rowset"
83
        //    Rowset                    xmlns:z="#RowsetSchema"
84
        //
85
86 51
        $signature = [
87 51
            '<?xml version="1.0"',
88 51
            'xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet',
89 51
        ];
90
91
        // Open file
92 51
        $data = (string) file_get_contents($filename);
93 51
        $data = $this->getSecurityScannerOrThrow()->scan($data);
94
95
        // Why?
96
        //$data = str_replace("'", '"', $data); // fix headers with single quote
97
98 51
        $valid = true;
99 51
        foreach ($signature as $match) {
100
            // every part of the signature must be present
101 51
            if (!str_contains($data, $match)) {
102 20
                $valid = false;
103
104 20
                break;
105
            }
106
        }
107
108 51
        $this->fileContents = $data;
109
110 51
        return $valid;
111
    }
112
113
    /** @return false|SimpleXMLElement */
114 46
    private function trySimpleXMLLoadStringPrivate(string $filename, string $fileOrString = 'file'): SimpleXMLElement|bool
115
    {
116 46
        $this->xmlFailMessage = "Cannot load invalid XML $fileOrString: " . $filename;
117 46
        $xml = false;
118
119
        try {
120 46
            $data = $this->fileContents;
121 46
            $continue = true;
122 46
            if ($data === '' && $fileOrString === 'file') {
123
                if ($filename === '') {
124
                    $this->xmlFailMessage = 'Cannot load empty path';
125
                    $continue = false;
126
                } else {
127
                    $datax = @file_get_contents($filename);
128
                    $data = $datax ?: '';
129
                    $continue = $datax !== false;
130
                }
131
            }
132 46
            if ($continue) {
133 46
                $xml = @simplexml_load_string(
134 46
                    $this->getSecurityScannerOrThrow()
135 46
                        ->scan($data)
136 46
                );
137
            }
138
        } catch (Throwable $e) {
139
            throw new Exception($this->xmlFailMessage, 0, $e);
140
        }
141 46
        $this->fileContents = '';
142
143 46
        return $xml;
144
    }
145
146
    /**
147
     * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
148
     */
149 4
    public function listWorksheetNames(string $filename): array
150
    {
151 4
        File::assertFile($filename);
152 4
        if (!$this->canRead($filename)) {
153 2
            throw new Exception($filename . ' is an Invalid Spreadsheet file.');
154
        }
155
156 2
        $worksheetNames = [];
157
158 2
        $xml = $this->trySimpleXMLLoadStringPrivate($filename);
159 2
        if ($xml === false) {
160 1
            throw new Exception("Problem reading {$filename}");
161
        }
162
163 1
        $xml_ss = $xml->children(self::NAMESPACES_SS);
164 1
        foreach ($xml_ss->Worksheet as $worksheet) {
165 1
            $worksheet_ss = self::getAttributes($worksheet, self::NAMESPACES_SS);
166 1
            $worksheetNames[] = (string) $worksheet_ss['Name'];
167
        }
168
169 1
        return $worksheetNames;
170
    }
171
172
    /**
173
     * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
174
     */
175 4
    public function listWorksheetInfo(string $filename): array
176
    {
177 4
        File::assertFile($filename);
178 4
        if (!$this->canRead($filename)) {
179 2
            throw new Exception($filename . ' is an Invalid Spreadsheet file.');
180
        }
181
182 2
        $worksheetInfo = [];
183
184 2
        $xml = $this->trySimpleXMLLoadStringPrivate($filename);
185 2
        if ($xml === false) {
186 1
            throw new Exception("Problem reading {$filename}");
187
        }
188
189 1
        $worksheetID = 1;
190 1
        $xml_ss = $xml->children(self::NAMESPACES_SS);
191 1
        foreach ($xml_ss->Worksheet as $worksheet) {
192 1
            $worksheet_ss = self::getAttributes($worksheet, self::NAMESPACES_SS);
193
194 1
            $tmpInfo = [];
195 1
            $tmpInfo['worksheetName'] = '';
196 1
            $tmpInfo['lastColumnLetter'] = 'A';
197 1
            $tmpInfo['lastColumnIndex'] = 0;
198 1
            $tmpInfo['totalRows'] = 0;
199 1
            $tmpInfo['totalColumns'] = 0;
200
201 1
            $tmpInfo['worksheetName'] = "Worksheet_{$worksheetID}";
202 1
            if (isset($worksheet_ss['Name'])) {
203 1
                $tmpInfo['worksheetName'] = (string) $worksheet_ss['Name'];
204
            }
205
206 1
            if (isset($worksheet->Table->Row)) {
207 1
                $rowIndex = 0;
208
209 1
                foreach ($worksheet->Table->Row as $rowData) {
210 1
                    $columnIndex = 0;
211 1
                    $rowHasData = false;
212
213 1
                    foreach ($rowData->Cell as $cell) {
214 1
                        if (isset($cell->Data)) {
215 1
                            $tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex);
216 1
                            $rowHasData = true;
217
                        }
218
219 1
                        ++$columnIndex;
220
                    }
221
222 1
                    ++$rowIndex;
223
224 1
                    if ($rowHasData) {
225 1
                        $tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex);
226
                    }
227
                }
228
            }
229
230 1
            $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
231 1
            $tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1;
232
233 1
            $worksheetInfo[] = $tmpInfo;
234 1
            ++$worksheetID;
235
        }
236
237 1
        return $worksheetInfo;
238
    }
239
240
    /**
241
     * Loads Spreadsheet from string.
242
     */
243 15
    public function loadSpreadsheetFromString(string $contents): Spreadsheet
244
    {
245
        // Create new Spreadsheet
246 15
        $spreadsheet = new Spreadsheet();
247 15
        $spreadsheet->setValueBinder($this->valueBinder);
248 15
        $spreadsheet->removeSheetByIndex(0);
249
250
        // Load into this instance
251 15
        return $this->loadIntoExisting($contents, $spreadsheet, true);
252
    }
253
254
    /**
255
     * Loads Spreadsheet from file.
256
     */
257 31
    protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
258
    {
259
        // Create new Spreadsheet
260 31
        $spreadsheet = new Spreadsheet();
261 31
        $spreadsheet->setValueBinder($this->valueBinder);
262 31
        $spreadsheet->removeSheetByIndex(0);
263
264
        // Load into this instance
265 31
        return $this->loadIntoExisting($filename, $spreadsheet);
266
    }
267
268
    /**
269
     * Loads from file or contents into Spreadsheet instance.
270
     *
271
     * @param string $filename file name if useContents is false else file contents
272
     */
273 46
    public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, bool $useContents = false): Spreadsheet
274
    {
275 46
        if ($useContents) {
276 15
            $this->fileContents = $filename;
277 15
            $fileOrString = 'string';
278
        } else {
279 31
            File::assertFile($filename);
280 30
            if (!$this->canRead($filename)) {
281 3
                throw new Exception($filename . ' is an Invalid Spreadsheet file.');
282
            }
283 27
            $fileOrString = 'file';
284
        }
285
286 42
        $xml = $this->trySimpleXMLLoadStringPrivate($filename, $fileOrString);
287 42
        if ($xml === false) {
288 3
            throw new Exception($this->xmlFailMessage);
289
        }
290
291 39
        $namespaces = $xml->getNamespaces(true);
292
293 39
        (new Properties($spreadsheet))->readProperties($xml, $namespaces);
294
295 39
        $this->styles = (new Style())->parseStyles($xml, $namespaces);
296 39
        if (isset($this->styles['Default'])) {
297 33
            $spreadsheet->getCellXfCollection()[0]->applyFromArray($this->styles['Default']);
298
        }
299
300 39
        $worksheetID = 0;
301 39
        $xml_ss = $xml->children(self::NAMESPACES_SS);
302
303
        /** @var null|SimpleXMLElement $worksheetx */
304 39
        foreach ($xml_ss->Worksheet as $worksheetx) {
305 39
            $worksheet = $worksheetx ?? new SimpleXMLElement('<xml></xml>');
306 39
            $worksheet_ss = self::getAttributes($worksheet, self::NAMESPACES_SS);
307
308
            if (
309 39
                isset($this->loadSheetsOnly, $worksheet_ss['Name'])
310 39
                && (!in_array($worksheet_ss['Name'], $this->loadSheetsOnly))
311
            ) {
312 2
                continue;
313
            }
314
315
            // Create new Worksheet
316 38
            $spreadsheet->createSheet();
317 38
            $spreadsheet->setActiveSheetIndex($worksheetID);
318 38
            $worksheetName = '';
319 38
            if (isset($worksheet_ss['Name'])) {
320 38
                $worksheetName = (string) $worksheet_ss['Name'];
321
                //    Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
322
                //        formula cells... during the load, all formulae should be correct, and we're simply bringing
323
                //        the worksheet name in line with the formula, not the reverse
324 38
                $spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
325
            }
326 38
            if (isset($worksheet_ss['Protected'])) {
327 2
                $protection = (string) $worksheet_ss['Protected'] === '1';
328 2
                $spreadsheet->getActiveSheet()->getProtection()->setSheet($protection);
329
            }
330
331
            // locally scoped defined names
332 38
            if (isset($worksheet->Names[0])) {
333 2
                foreach ($worksheet->Names[0] as $definedName) {
334 2
                    $definedName_ss = self::getAttributes($definedName, self::NAMESPACES_SS);
335 2
                    $name = (string) $definedName_ss['Name'];
336 2
                    $definedValue = (string) $definedName_ss['RefersTo'];
337 2
                    $convertedValue = AddressHelper::convertFormulaToA1($definedValue);
338 2
                    if ($convertedValue[0] === '=') {
339 2
                        $convertedValue = substr($convertedValue, 1);
340
                    }
341 2
                    $spreadsheet->addDefinedName(DefinedName::createInstance($name, $spreadsheet->getActiveSheet(), $convertedValue, true));
342
                }
343
            }
344
345 38
            $columnID = 'A';
346 38
            if (isset($worksheet->Table->Column)) {
347 17
                foreach ($worksheet->Table->Column as $columnData) {
348 17
                    $columnData_ss = self::getAttributes($columnData, self::NAMESPACES_SS);
349 17
                    $colspan = 0;
350 17
                    if (isset($columnData_ss['Span'])) {
351 12
                        $spanAttr = (string) $columnData_ss['Span'];
352 12
                        if (is_numeric($spanAttr)) {
353 12
                            $colspan = max(0, (int) $spanAttr);
354
                        }
355
                    }
356 17
                    if (isset($columnData_ss['Index'])) {
357 14
                        $columnID = Coordinate::stringFromColumnIndex((int) $columnData_ss['Index']);
358
                    }
359 17
                    $columnWidth = null;
360 17
                    if (isset($columnData_ss['Width'])) {
361 16
                        $columnWidth = $columnData_ss['Width'];
362
                    }
363 17
                    $columnVisible = null;
364 17
                    if (isset($columnData_ss['Hidden'])) {
365 11
                        $columnVisible = ((string) $columnData_ss['Hidden']) !== '1';
366
                    }
367 17
                    while ($colspan >= 0) {
368 17
                        if (isset($columnWidth)) {
369 16
                            $spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setWidth($columnWidth / 5.4);
370
                        }
371 17
                        if (isset($columnVisible)) {
372 11
                            $spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setVisible($columnVisible);
373
                        }
374 17
                        ++$columnID;
375 17
                        --$colspan;
376
                    }
377
                }
378
            }
379
380 38
            $rowID = 1;
381 38
            if (isset($worksheet->Table->Row)) {
382 37
                $additionalMergedCells = 0;
383 37
                foreach ($worksheet->Table->Row as $rowData) {
384 37
                    $rowHasData = false;
385 37
                    $row_ss = self::getAttributes($rowData, self::NAMESPACES_SS);
386 37
                    if (isset($row_ss['Index'])) {
387 6
                        $rowID = (int) $row_ss['Index'];
388
                    }
389 37
                    if (isset($row_ss['Hidden'])) {
390 10
                        $rowVisible = ((string) $row_ss['Hidden']) !== '1';
391 10
                        $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setVisible($rowVisible);
392
                    }
393
394 37
                    $columnID = 'A';
395 37
                    foreach ($rowData->Cell as $cell) {
396 37
                        $arrayRef = '';
397 37
                        $cell_ss = self::getAttributes($cell, self::NAMESPACES_SS);
398 37
                        if (isset($cell_ss['Index'])) {
399 18
                            $columnID = Coordinate::stringFromColumnIndex((int) $cell_ss['Index']);
400
                        }
401 37
                        $cellRange = $columnID . $rowID;
402 37
                        if (isset($cell_ss['ArrayRange'])) {
403 1
                            $arrayRange = (string) $cell_ss['ArrayRange'];
404 1
                            $arrayRef = AddressHelper::convertFormulaToA1($arrayRange, $rowID, Coordinate::columnIndexFromString($columnID));
405
                        }
406
407 37
                        if ($this->getReadFilter() !== null) {
408 37
                            if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
409 1
                                ++$columnID;
410
411 1
                                continue;
412
                            }
413
                        }
414
415 37
                        if (isset($cell_ss['HRef'])) {
416 13
                            $spreadsheet->getActiveSheet()->getCell($cellRange)->getHyperlink()->setUrl((string) $cell_ss['HRef']);
417
                        }
418
419 37
                        if ((isset($cell_ss['MergeAcross'])) || (isset($cell_ss['MergeDown']))) {
420 9
                            $columnTo = $columnID;
421 9
                            if (isset($cell_ss['MergeAcross'])) {
422 9
                                $additionalMergedCells += (int) $cell_ss['MergeAcross'];
423 9
                                $columnTo = Coordinate::stringFromColumnIndex((int) (Coordinate::columnIndexFromString($columnID) + $cell_ss['MergeAcross']));
424
                            }
425 9
                            $rowTo = $rowID;
426 9
                            if (isset($cell_ss['MergeDown'])) {
427 9
                                $rowTo = $rowTo + $cell_ss['MergeDown'];
428
                            }
429 9
                            $cellRange .= ':' . $columnTo . $rowTo;
430 9
                            $spreadsheet->getActiveSheet()->mergeCells($cellRange, Worksheet::MERGE_CELL_CONTENT_HIDE);
431
                        }
432
433 37
                        $hasCalculatedValue = false;
434 37
                        $cellDataFormula = '';
435 37
                        if (isset($cell_ss['Formula'])) {
436 19
                            $cellDataFormula = $cell_ss['Formula'];
437 19
                            $hasCalculatedValue = true;
438 19
                            if ($arrayRef !== '') {
439 1
                                $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setFormulaAttributes(['t' => 'array', 'ref' => $arrayRef]);
440
                            }
441
                        }
442 37
                        if (isset($cell->Data)) {
443 37
                            $cellData = $cell->Data;
444 37
                            $cellValue = (string) $cellData;
445 37
                            $type = DataType::TYPE_NULL;
446 37
                            $cellData_ss = self::getAttributes($cellData, self::NAMESPACES_SS);
447 37
                            if (isset($cellData_ss['Type'])) {
448 37
                                $cellDataType = $cellData_ss['Type'];
449
                                switch ($cellDataType) {
450
                                    /*
451
                                    const TYPE_STRING        = 's';
452
                                    const TYPE_FORMULA        = 'f';
453
                                    const TYPE_NUMERIC        = 'n';
454
                                    const TYPE_BOOL            = 'b';
455
                                    const TYPE_NULL            = 'null';
456
                                    const TYPE_INLINE        = 'inlineStr';
457
                                    const TYPE_ERROR        = 'e';
458
                                    */
459 37
                                    case 'String':
460 33
                                        $type = DataType::TYPE_STRING;
461 33
                                        $rich = $cellData->children('http://www.w3.org/TR/REC-html40');
462 33
                                        if ($rich) {
463
                                            // in case of HTML content we extract the payload
464
                                            // and convert it into a rich text object
465 2
                                            $content = $cellData->asXML() ?: '';
466 2
                                            $html = new HelperHtml();
467 2
                                            $cellValue = $html->toRichTextObject($content, true);
468
                                        }
469
470 33
                                        break;
471 17
                                    case 'Number':
472 17
                                        $type = DataType::TYPE_NUMERIC;
473 17
                                        $cellValue = (float) $cellValue;
474 17
                                        if (floor($cellValue) == $cellValue) {
475 17
                                            $cellValue = (int) $cellValue;
476
                                        }
477
478 17
                                        break;
479 11
                                    case 'Boolean':
480 9
                                        $type = DataType::TYPE_BOOL;
481 9
                                        $cellValue = ($cellValue != 0);
482
483 9
                                        break;
484 11
                                    case 'DateTime':
485 11
                                        $type = DataType::TYPE_NUMERIC;
486 11
                                        $dateTime = new DateTime($cellValue, new DateTimeZone('UTC'));
487 11
                                        $cellValue = Date::PHPToExcel($dateTime);
488
489 11
                                        break;
490 9
                                    case 'Error':
491 9
                                        $type = DataType::TYPE_ERROR;
492 9
                                        $hasCalculatedValue = false;
493
494 9
                                        break;
495
                                }
496
                            }
497
498 37
                            $originalType = $type;
499 37
                            if ($hasCalculatedValue) {
500 19
                                $type = DataType::TYPE_FORMULA;
501 19
                                $columnNumber = Coordinate::columnIndexFromString($columnID);
502 19
                                $cellDataFormula = AddressHelper::convertFormulaToA1($cellDataFormula, $rowID, $columnNumber);
503
                            }
504
505 37
                            $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValueExplicit((($hasCalculatedValue) ? $cellDataFormula : $cellValue), $type);
506 37
                            if ($hasCalculatedValue) {
507 19
                                $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setCalculatedValue($cellValue, $originalType === DataType::TYPE_NUMERIC);
508
                            }
509 37
                            $rowHasData = true;
510
                        }
511
512 37
                        if (isset($cell->Comment)) {
513 10
                            $this->parseCellComment($cell->Comment, $spreadsheet, $columnID, $rowID);
514
                        }
515
516 37
                        if (isset($cell_ss['StyleID'])) {
517 22
                            $style = (string) $cell_ss['StyleID'];
518 22
                            if ((isset($this->styles[$style])) && (!empty($this->styles[$style]))) {
519 22
                                $spreadsheet->getActiveSheet()->getStyle($cellRange)
520 22
                                    ->applyFromArray($this->styles[$style]);
521
                            }
522
                        }
523 37
                        ++$columnID;
524 37
                        while ($additionalMergedCells > 0) {
525 9
                            ++$columnID;
526 9
                            --$additionalMergedCells;
527
                        }
528
                    }
529
530 37
                    if ($rowHasData) {
531 37
                        if (isset($row_ss['Height'])) {
532 17
                            $rowHeight = $row_ss['Height'];
533 17
                            $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setRowHeight((float) $rowHeight);
534
                        }
535
                    }
536
537 37
                    ++$rowID;
538
                }
539
            }
540
541 38
            $dataValidations = new Xml\DataValidations();
542 38
            $dataValidations->loadDataValidations($worksheet, $spreadsheet);
543 38
            $xmlX = $worksheet->children(Namespaces::URN_EXCEL);
544 38
            if (isset($xmlX->WorksheetOptions)) {
545 33
                if (isset($xmlX->WorksheetOptions->ShowPageBreakZoom)) {
546 3
                    $spreadsheet->getActiveSheet()->getSheetView()->setView(SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW);
547
                }
548 33
                if (isset($xmlX->WorksheetOptions->Zoom)) {
549 3
                    $zoomScaleNormal = (int) $xmlX->WorksheetOptions->Zoom;
550 3
                    if ($zoomScaleNormal > 0) {
551 3
                        $spreadsheet->getActiveSheet()->getSheetView()->setZoomScaleNormal($zoomScaleNormal);
552 3
                        $spreadsheet->getActiveSheet()->getSheetView()->setZoomScale($zoomScaleNormal);
553
                    }
554
                }
555 33
                if (isset($xmlX->WorksheetOptions->PageBreakZoom)) {
556 3
                    $zoomScaleNormal = (int) $xmlX->WorksheetOptions->PageBreakZoom;
557 3
                    if ($zoomScaleNormal > 0) {
558 3
                        $spreadsheet->getActiveSheet()->getSheetView()->setZoomScaleSheetLayoutView($zoomScaleNormal);
559
                    }
560
                }
561 33
                if (isset($xmlX->WorksheetOptions->ShowPageBreakZoom)) {
562 3
                    $spreadsheet->getActiveSheet()->getSheetView()->setView(SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW);
563
                }
564 33
                if (isset($xmlX->WorksheetOptions->FreezePanes)) {
565 4
                    $freezeRow = $freezeColumn = 1;
566 4
                    if (isset($xmlX->WorksheetOptions->SplitHorizontal)) {
567 4
                        $freezeRow = (int) $xmlX->WorksheetOptions->SplitHorizontal + 1;
568
                    }
569 4
                    if (isset($xmlX->WorksheetOptions->SplitVertical)) {
570 4
                        $freezeColumn = (int) $xmlX->WorksheetOptions->SplitVertical + 1;
571
                    }
572 4
                    $leftTopRow = (string) $xmlX->WorksheetOptions->TopRowBottomPane;
573 4
                    $leftTopColumn = (string) $xmlX->WorksheetOptions->LeftColumnRightPane;
574 4
                    if (is_numeric($leftTopRow) && is_numeric($leftTopColumn)) {
575 4
                        $leftTopCoordinate = Coordinate::stringFromColumnIndex((int) $leftTopColumn + 1) . (string) ($leftTopRow + 1);
576 4
                        $spreadsheet->getActiveSheet()->freezePane(Coordinate::stringFromColumnIndex($freezeColumn) . (string) $freezeRow, $leftTopCoordinate, !isset($xmlX->WorksheetOptions->FrozenNoSplit));
577
                    } else {
578
                        $spreadsheet->getActiveSheet()->freezePane(Coordinate::stringFromColumnIndex($freezeColumn) . (string) $freezeRow, null, !isset($xmlX->WorksheetOptions->FrozenNoSplit));
579
                    }
580 32
                } elseif (isset($xmlX->WorksheetOptions->SplitVertical) || isset($xmlX->WorksheetOptions->SplitHorizontal)) {
581 1
                    if (isset($xmlX->WorksheetOptions->SplitHorizontal)) {
582 1
                        $ySplit = (int) $xmlX->WorksheetOptions->SplitHorizontal;
583 1
                        $spreadsheet->getActiveSheet()->setYSplit($ySplit);
584
                    }
585 1
                    if (isset($xmlX->WorksheetOptions->SplitVertical)) {
586 1
                        $xSplit = (int) $xmlX->WorksheetOptions->SplitVertical;
587 1
                        $spreadsheet->getActiveSheet()->setXSplit($xSplit);
588
                    }
589 1
                    if (isset($xmlX->WorksheetOptions->LeftColumnVisible) || isset($xmlX->WorksheetOptions->TopRowVisible)) {
590 1
                        $leftTopColumn = $leftTopRow = 1;
591 1
                        if (isset($xmlX->WorksheetOptions->LeftColumnVisible)) {
592 1
                            $leftTopColumn = 1 + (int) $xmlX->WorksheetOptions->LeftColumnVisible;
593
                        }
594 1
                        if (isset($xmlX->WorksheetOptions->TopRowVisible)) {
595 1
                            $leftTopRow = 1 + (int) $xmlX->WorksheetOptions->TopRowVisible;
596
                        }
597 1
                        $leftTopCoordinate = Coordinate::stringFromColumnIndex($leftTopColumn) . "$leftTopRow";
598 1
                        $spreadsheet->getActiveSheet()->setTopLeftCell($leftTopCoordinate);
599
                    }
600
601 1
                    $leftTopColumn = $leftTopRow = 1;
602 1
                    if (isset($xmlX->WorksheetOptions->LeftColumnRightPane)) {
603 1
                        $leftTopColumn = 1 + (int) $xmlX->WorksheetOptions->LeftColumnRightPane;
604
                    }
605 1
                    if (isset($xmlX->WorksheetOptions->TopRowBottomPane)) {
606 1
                        $leftTopRow = 1 + (int) $xmlX->WorksheetOptions->TopRowBottomPane;
607
                    }
608 1
                    $leftTopCoordinate = Coordinate::stringFromColumnIndex($leftTopColumn) . "$leftTopRow";
609 1
                    $spreadsheet->getActiveSheet()->setPaneTopLeftCell($leftTopCoordinate);
610
                }
611 33
                (new PageSettings($xmlX))->loadPageSettings($spreadsheet);
612 33
                if (isset($xmlX->WorksheetOptions->TopRowVisible, $xmlX->WorksheetOptions->LeftColumnVisible)) {
613 2
                    $leftTopRow = (string) $xmlX->WorksheetOptions->TopRowVisible;
614 2
                    $leftTopColumn = (string) $xmlX->WorksheetOptions->LeftColumnVisible;
615 2
                    if (is_numeric($leftTopRow) && is_numeric($leftTopColumn)) {
616 2
                        $leftTopCoordinate = Coordinate::stringFromColumnIndex((int) $leftTopColumn + 1) . (string) ($leftTopRow + 1);
617 2
                        $spreadsheet->getActiveSheet()->setTopLeftCell($leftTopCoordinate);
618
                    }
619
                }
620 33
                $rangeCalculated = false;
621 33
                if (isset($xmlX->WorksheetOptions->Panes->Pane->RangeSelection)) {
622 13
                    if (1 === preg_match('/^R(\d+)C(\d+):R(\d+)C(\d+)$/', (string) $xmlX->WorksheetOptions->Panes->Pane->RangeSelection, $selectionMatches)) {
623 13
                        $selectedCell = Coordinate::stringFromColumnIndex((int) $selectionMatches[2])
624 13
                            . $selectionMatches[1]
625 13
                            . ':'
626 13
                            . Coordinate::stringFromColumnIndex((int) $selectionMatches[4])
627 13
                            . $selectionMatches[3];
628 13
                        $spreadsheet->getActiveSheet()->setSelectedCells($selectedCell);
629 13
                        $rangeCalculated = true;
630
                    }
631
                }
632 33
                if (!$rangeCalculated) {
633 32
                    if (isset($xmlX->WorksheetOptions->Panes->Pane->ActiveRow)) {
634 24
                        $activeRow = (string) $xmlX->WorksheetOptions->Panes->Pane->ActiveRow;
635
                    } else {
636 10
                        $activeRow = 0;
637
                    }
638 32
                    if (isset($xmlX->WorksheetOptions->Panes->Pane->ActiveCol)) {
639 19
                        $activeColumn = (string) $xmlX->WorksheetOptions->Panes->Pane->ActiveCol;
640
                    } else {
641 15
                        $activeColumn = 0;
642
                    }
643 32
                    if (is_numeric($activeRow) && is_numeric($activeColumn)) {
644 32
                        $selectedCell = Coordinate::stringFromColumnIndex((int) $activeColumn + 1) . (string) ($activeRow + 1);
645 32
                        $spreadsheet->getActiveSheet()->setSelectedCells($selectedCell);
646
                    }
647
                }
648
            }
649 38
            if (isset($xmlX->PageBreaks)) {
650 3
                if (isset($xmlX->PageBreaks->ColBreaks)) {
651 3
                    foreach ($xmlX->PageBreaks->ColBreaks->ColBreak as $colBreak) {
652 3
                        $colBreak = (string) $colBreak->Column;
653 3
                        $spreadsheet->getActiveSheet()->setBreak([1 + (int) $colBreak, 1], Worksheet::BREAK_COLUMN);
654
                    }
655
                }
656 3
                if (isset($xmlX->PageBreaks->RowBreaks)) {
657 3
                    foreach ($xmlX->PageBreaks->RowBreaks->RowBreak as $rowBreak) {
658 3
                        $rowBreak = (string) $rowBreak->Row;
659 3
                        $spreadsheet->getActiveSheet()->setBreak([1, (int) $rowBreak], Worksheet::BREAK_ROW);
660
                    }
661
                }
662
            }
663 38
            ++$worksheetID;
664
        }
665
666
        // Globally scoped defined names
667 39
        $activeSheetIndex = 0;
668 39
        if (isset($xml->ExcelWorkbook->ActiveSheet)) {
669 2
            $activeSheetIndex = (int) (string) $xml->ExcelWorkbook->ActiveSheet;
670
        }
671 39
        $activeWorksheet = $spreadsheet->setActiveSheetIndex($activeSheetIndex);
672 38
        if (isset($xml->Names[0])) {
673 10
            foreach ($xml->Names[0] as $definedName) {
674 10
                $definedName_ss = self::getAttributes($definedName, self::NAMESPACES_SS);
675 10
                $name = (string) $definedName_ss['Name'];
676 10
                $definedValue = (string) $definedName_ss['RefersTo'];
677 10
                $convertedValue = AddressHelper::convertFormulaToA1($definedValue);
678 10
                if ($convertedValue[0] === '=') {
679 10
                    $convertedValue = substr($convertedValue, 1);
680
                }
681 10
                $spreadsheet->addDefinedName(DefinedName::createInstance($name, $activeWorksheet, $convertedValue));
682
            }
683
        }
684
685
        // Return
686 38
        return $spreadsheet;
687
    }
688
689 10
    protected function parseCellComment(
690
        SimpleXMLElement $comment,
691
        Spreadsheet $spreadsheet,
692
        string $columnID,
693
        int $rowID
694
    ): void {
695 10
        $commentAttributes = $comment->attributes(self::NAMESPACES_SS);
696 10
        $author = 'unknown';
697 10
        if (isset($commentAttributes->Author)) {
698 9
            $author = (string) $commentAttributes->Author;
699
        }
700
701 10
        $node = $comment->Data->asXML();
702 10
        $annotation = strip_tags((string) $node);
703 10
        $spreadsheet->getActiveSheet()->getComment($columnID . $rowID)
704 10
            ->setAuthor($author)
705 10
            ->setText($this->parseRichText($annotation));
706
    }
707
708 10
    protected function parseRichText(string $annotation): RichText
709
    {
710 10
        $value = new RichText();
711
712 10
        $value->createText($annotation);
713
714 10
        return $value;
715
    }
716
717 41
    private static function getAttributes(?SimpleXMLElement $simple, string $node): SimpleXMLElement
718
    {
719 41
        return ($simple === null)
720
            ? new SimpleXMLElement('<xml></xml>')
721 41
            : ($simple->attributes($node) ?? new SimpleXMLElement('<xml></xml>'));
722
    }
723
}
724