Passed
Pull Request — master (#4143)
by Owen
14:43
created

Xml::trySimpleXMLLoadString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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