PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
tablepress / libraries / vendor / PhpSpreadsheet / Reader / Xml.php

Xml.php in TablePress – Tables in WordPress made easy 3.4, at libraries/vendor/PhpSpreadsheet/Reader/Xml.php

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