Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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