PluginProbe
wpDataTables – WordPress Data Table, Dynamic Tables & Table Charts Plugin / 6.5.1.7
wpDataTables – WordPress Data Table, Dynamic Tables & Table Charts Plugin v6.5.1.7
6.5.1.7 6.5.1.6 6.5.1.5 6.5.1.4 6.5.1.3 6.5.1.2 6.5.1.1 6.5.0.9 6.5.0.8 6.5.0.7 6.5.0.6 trunk 3.4.2.40 3.4.2.41 3.4.2.42 3.4.2.43 3.4.2.44 3.4.2.45 3.4.2.46 3.4.2.47 3.4.2.48 3.4.2.49 3.4.2.50 6.3.2 6.3.3.1 All 47 releases
wpdatatables / lib / phpoffice / phpspreadsheet / src / PhpSpreadsheet / Reader / Xml.php

Xml.php in wpDataTables – WordPress Data Table, Dynamic Tables & Table Charts Plugin 6.5.1.7, at lib/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml.php

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