Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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