PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 3.7.1
Visualizer – Tables & Charts Manager with Built-in AI Generator v3.7.1
4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 3.10.3 All 149 releases
visualizer / vendor / phpoffice / phpspreadsheet / src / PhpSpreadsheet / Reader / Xml.php

Xml.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 3.7.1, at vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml.php

883 lines 34.2 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 PhpOffice\PhpSpreadsheet\Cell\Coordinate;
6 use PhpOffice\PhpSpreadsheet\Cell\DataType;
7 use PhpOffice\PhpSpreadsheet\Document\Properties;
8 use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
9 use PhpOffice\PhpSpreadsheet\RichText\RichText;
10 use PhpOffice\PhpSpreadsheet\Settings;
11 use PhpOffice\PhpSpreadsheet\Shared\Date;
12 use PhpOffice\PhpSpreadsheet\Shared\File;
13 use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
14 use PhpOffice\PhpSpreadsheet\Spreadsheet;
15 use PhpOffice\PhpSpreadsheet\Style\Alignment;
16 use PhpOffice\PhpSpreadsheet\Style\Border;
17 use PhpOffice\PhpSpreadsheet\Style\Font;
18 use SimpleXMLElement;
19
20 /**
21 * Reader for SpreadsheetML, the XML schema for Microsoft Office Excel 2003.
22 */
23 class Xml extends BaseReader
24 {
25 /**
26 * Formats.
27 *
28 * @var array
29 */
30 protected $styles = [];
31
32 /**
33 * Character set used in the file.
34 *
35 * @var string
36 */
37 protected $charSet = 'UTF-8';
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 /**
49 * Can the current IReader read the file?
50 *
51 * @param string $pFilename
52 *
53 * @throws Exception
54 *
55 * @return bool
56 */
57 public function canRead($pFilename)
58 {
59 // Office xmlns:o="urn:schemas-microsoft-com:office:office"
60 // Excel xmlns:x="urn:schemas-microsoft-com:office:excel"
61 // XML Spreadsheet xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
62 // Spreadsheet component xmlns:c="urn:schemas-microsoft-com:office:component:spreadsheet"
63 // XML schema xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
64 // XML data type xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
65 // MS-persist recordset xmlns:rs="urn:schemas-microsoft-com:rowset"
66 // Rowset xmlns:z="#RowsetSchema"
67 //
68
69 $signature = [
70 '<?xml version="1.0"',
71 '<?mso-application progid="Excel.Sheet"?>',
72 ];
73
74 // Open file
75 $this->openFile($pFilename);
76 $fileHandle = $this->fileHandle;
77
78 // Read sample data (first 2 KB will do)
79 $data = fread($fileHandle, 2048);
80 fclose($fileHandle);
81 $data = str_replace("'", '"', $data); // fix headers with single quote
82
83 $valid = true;
84 foreach ($signature as $match) {
85 // every part of the signature must be present
86 if (strpos($data, $match) === false) {
87 $valid = false;
88
89 break;
90 }
91 }
92
93 // Retrieve charset encoding
94 if (preg_match('/<?xml.*encoding=[\'"](.*?)[\'"].*?>/um', $data, $matches)) {
95 $this->charSet = strtoupper($matches[1]);
96 }
97
98 return $valid;
99 }
100
101 /**
102 * Check if the file is a valid SimpleXML.
103 *
104 * @param string $pFilename
105 *
106 * @throws Exception
107 *
108 * @return false|\SimpleXMLElement
109 */
110 public function trySimpleXMLLoadString($pFilename)
111 {
112 try {
113 $xml = simplexml_load_string(
114 $this->securityScanner->scan(file_get_contents($pFilename)),
115 'SimpleXMLElement',
116 Settings::getLibXmlLoaderOptions()
117 );
118 } catch (\Exception $e) {
119 throw new Exception('Cannot load invalid XML file: ' . $pFilename, 0, $e);
120 }
121
122 return $xml;
123 }
124
125 /**
126 * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
127 *
128 * @param string $pFilename
129 *
130 * @throws Exception
131 *
132 * @return array
133 */
134 public function listWorksheetNames($pFilename)
135 {
136 File::assertFile($pFilename);
137 if (!$this->canRead($pFilename)) {
138 throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
139 }
140
141 $worksheetNames = [];
142
143 $xml = $this->trySimpleXMLLoadString($pFilename);
144
145 $namespaces = $xml->getNamespaces(true);
146
147 $xml_ss = $xml->children($namespaces['ss']);
148 foreach ($xml_ss->Worksheet as $worksheet) {
149 $worksheet_ss = $worksheet->attributes($namespaces['ss']);
150 $worksheetNames[] = self::convertStringEncoding((string) $worksheet_ss['Name'], $this->charSet);
151 }
152
153 return $worksheetNames;
154 }
155
156 /**
157 * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
158 *
159 * @param string $pFilename
160 *
161 * @throws Exception
162 *
163 * @return array
164 */
165 public function listWorksheetInfo($pFilename)
166 {
167 File::assertFile($pFilename);
168
169 $worksheetInfo = [];
170
171 $xml = $this->trySimpleXMLLoadString($pFilename);
172
173 $namespaces = $xml->getNamespaces(true);
174
175 $worksheetID = 1;
176 $xml_ss = $xml->children($namespaces['ss']);
177 foreach ($xml_ss->Worksheet as $worksheet) {
178 $worksheet_ss = $worksheet->attributes($namespaces['ss']);
179
180 $tmpInfo = [];
181 $tmpInfo['worksheetName'] = '';
182 $tmpInfo['lastColumnLetter'] = 'A';
183 $tmpInfo['lastColumnIndex'] = 0;
184 $tmpInfo['totalRows'] = 0;
185 $tmpInfo['totalColumns'] = 0;
186
187 if (isset($worksheet_ss['Name'])) {
188 $tmpInfo['worksheetName'] = (string) $worksheet_ss['Name'];
189 } else {
190 $tmpInfo['worksheetName'] = "Worksheet_{$worksheetID}";
191 }
192
193 if (isset($worksheet->Table->Row)) {
194 $rowIndex = 0;
195
196 foreach ($worksheet->Table->Row as $rowData) {
197 $columnIndex = 0;
198 $rowHasData = false;
199
200 foreach ($rowData->Cell as $cell) {
201 if (isset($cell->Data)) {
202 $tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex);
203 $rowHasData = true;
204 }
205
206 ++$columnIndex;
207 }
208
209 ++$rowIndex;
210
211 if ($rowHasData) {
212 $tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex);
213 }
214 }
215 }
216
217 $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
218 $tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1;
219
220 $worksheetInfo[] = $tmpInfo;
221 ++$worksheetID;
222 }
223
224 return $worksheetInfo;
225 }
226
227 /**
228 * Loads Spreadsheet from file.
229 *
230 * @param string $pFilename
231 *
232 * @throws Exception
233 *
234 * @return Spreadsheet
235 */
236 public function load($pFilename)
237 {
238 // Create new Spreadsheet
239 $spreadsheet = new Spreadsheet();
240 $spreadsheet->removeSheetByIndex(0);
241
242 // Load into this instance
243 return $this->loadIntoExisting($pFilename, $spreadsheet);
244 }
245
246 private static function identifyFixedStyleValue($styleList, &$styleAttributeValue)
247 {
248 $styleAttributeValue = strtolower($styleAttributeValue);
249 foreach ($styleList as $style) {
250 if ($styleAttributeValue == strtolower($style)) {
251 $styleAttributeValue = $style;
252
253 return true;
254 }
255 }
256
257 return false;
258 }
259
260 /**
261 * pixel units to excel width units(units of 1/256th of a character width).
262 *
263 * @param float $pxs
264 *
265 * @return float
266 */
267 protected static function pixel2WidthUnits($pxs)
268 {
269 $UNIT_OFFSET_MAP = [0, 36, 73, 109, 146, 182, 219];
270
271 $widthUnits = 256 * ($pxs / 7);
272 $widthUnits += $UNIT_OFFSET_MAP[($pxs % 7)];
273
274 return $widthUnits;
275 }
276
277 /**
278 * excel width units(units of 1/256th of a character width) to pixel units.
279 *
280 * @param float $widthUnits
281 *
282 * @return float
283 */
284 protected static function widthUnits2Pixel($widthUnits)
285 {
286 $pixels = ($widthUnits / 256) * 7;
287 $offsetWidthUnits = $widthUnits % 256;
288 $pixels += round($offsetWidthUnits / (256 / 7));
289
290 return $pixels;
291 }
292
293 protected static function hex2str($hex)
294 {
295 return chr(hexdec($hex[1]));
296 }
297
298 /**
299 * Loads from file into Spreadsheet instance.
300 *
301 * @param string $pFilename
302 * @param Spreadsheet $spreadsheet
303 *
304 * @throws Exception
305 *
306 * @return Spreadsheet
307 */
308 public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
309 {
310 File::assertFile($pFilename);
311 if (!$this->canRead($pFilename)) {
312 throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
313 }
314
315 $xml = $this->trySimpleXMLLoadString($pFilename);
316
317 $namespaces = $xml->getNamespaces(true);
318
319 $docProps = $spreadsheet->getProperties();
320 if (isset($xml->DocumentProperties[0])) {
321 foreach ($xml->DocumentProperties[0] as $propertyName => $propertyValue) {
322 switch ($propertyName) {
323 case 'Title':
324 $docProps->setTitle(self::convertStringEncoding($propertyValue, $this->charSet));
325
326 break;
327 case 'Subject':
328 $docProps->setSubject(self::convertStringEncoding($propertyValue, $this->charSet));
329
330 break;
331 case 'Author':
332 $docProps->setCreator(self::convertStringEncoding($propertyValue, $this->charSet));
333
334 break;
335 case 'Created':
336 $creationDate = strtotime($propertyValue);
337 $docProps->setCreated($creationDate);
338
339 break;
340 case 'LastAuthor':
341 $docProps->setLastModifiedBy(self::convertStringEncoding($propertyValue, $this->charSet));
342
343 break;
344 case 'LastSaved':
345 $lastSaveDate = strtotime($propertyValue);
346 $docProps->setModified($lastSaveDate);
347
348 break;
349 case 'Company':
350 $docProps->setCompany(self::convertStringEncoding($propertyValue, $this->charSet));
351
352 break;
353 case 'Category':
354 $docProps->setCategory(self::convertStringEncoding($propertyValue, $this->charSet));
355
356 break;
357 case 'Manager':
358 $docProps->setManager(self::convertStringEncoding($propertyValue, $this->charSet));
359
360 break;
361 case 'Keywords':
362 $docProps->setKeywords(self::convertStringEncoding($propertyValue, $this->charSet));
363
364 break;
365 case 'Description':
366 $docProps->setDescription(self::convertStringEncoding($propertyValue, $this->charSet));
367
368 break;
369 }
370 }
371 }
372 if (isset($xml->CustomDocumentProperties)) {
373 foreach ($xml->CustomDocumentProperties[0] as $propertyName => $propertyValue) {
374 $propertyAttributes = $propertyValue->attributes($namespaces['dt']);
375 $propertyName = preg_replace_callback('/_x([0-9a-z]{4})_/', ['self', 'hex2str'], $propertyName);
376 $propertyType = Properties::PROPERTY_TYPE_UNKNOWN;
377 switch ((string) $propertyAttributes) {
378 case 'string':
379 $propertyType = Properties::PROPERTY_TYPE_STRING;
380 $propertyValue = trim($propertyValue);
381
382 break;
383 case 'boolean':
384 $propertyType = Properties::PROPERTY_TYPE_BOOLEAN;
385 $propertyValue = (bool) $propertyValue;
386
387 break;
388 case 'integer':
389 $propertyType = Properties::PROPERTY_TYPE_INTEGER;
390 $propertyValue = (int) $propertyValue;
391
392 break;
393 case 'float':
394 $propertyType = Properties::PROPERTY_TYPE_FLOAT;
395 $propertyValue = (float) $propertyValue;
396
397 break;
398 case 'dateTime.tz':
399 $propertyType = Properties::PROPERTY_TYPE_DATE;
400 $propertyValue = strtotime(trim($propertyValue));
401
402 break;
403 }
404 $docProps->setCustomProperty($propertyName, $propertyValue, $propertyType);
405 }
406 }
407
408 $this->parseStyles($xml, $namespaces);
409
410 $worksheetID = 0;
411 $xml_ss = $xml->children($namespaces['ss']);
412
413 foreach ($xml_ss->Worksheet as $worksheet) {
414 $worksheet_ss = $worksheet->attributes($namespaces['ss']);
415
416 if ((isset($this->loadSheetsOnly)) && (isset($worksheet_ss['Name'])) &&
417 (!in_array($worksheet_ss['Name'], $this->loadSheetsOnly))) {
418 continue;
419 }
420
421 // Create new Worksheet
422 $spreadsheet->createSheet();
423 $spreadsheet->setActiveSheetIndex($worksheetID);
424 if (isset($worksheet_ss['Name'])) {
425 $worksheetName = self::convertStringEncoding((string) $worksheet_ss['Name'], $this->charSet);
426 // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
427 // formula cells... during the load, all formulae should be correct, and we're simply bringing
428 // the worksheet name in line with the formula, not the reverse
429 $spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
430 }
431
432 $columnID = 'A';
433 if (isset($worksheet->Table->Column)) {
434 foreach ($worksheet->Table->Column as $columnData) {
435 $columnData_ss = $columnData->attributes($namespaces['ss']);
436 if (isset($columnData_ss['Index'])) {
437 $columnID = Coordinate::stringFromColumnIndex((int) $columnData_ss['Index']);
438 }
439 if (isset($columnData_ss['Width'])) {
440 $columnWidth = $columnData_ss['Width'];
441 $spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setWidth($columnWidth / 5.4);
442 }
443 ++$columnID;
444 }
445 }
446
447 $rowID = 1;
448 if (isset($worksheet->Table->Row)) {
449 $additionalMergedCells = 0;
450 foreach ($worksheet->Table->Row as $rowData) {
451 $rowHasData = false;
452 $row_ss = $rowData->attributes($namespaces['ss']);
453 if (isset($row_ss['Index'])) {
454 $rowID = (int) $row_ss['Index'];
455 }
456
457 $columnID = 'A';
458 foreach ($rowData->Cell as $cell) {
459 $cell_ss = $cell->attributes($namespaces['ss']);
460 if (isset($cell_ss['Index'])) {
461 $columnID = Coordinate::stringFromColumnIndex((int) $cell_ss['Index']);
462 }
463 $cellRange = $columnID . $rowID;
464
465 if ($this->getReadFilter() !== null) {
466 if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
467 ++$columnID;
468
469 continue;
470 }
471 }
472
473 if (isset($cell_ss['HRef'])) {
474 $spreadsheet->getActiveSheet()->getCell($cellRange)->getHyperlink()->setUrl($cell_ss['HRef']);
475 }
476
477 if ((isset($cell_ss['MergeAcross'])) || (isset($cell_ss['MergeDown']))) {
478 $columnTo = $columnID;
479 if (isset($cell_ss['MergeAcross'])) {
480 $additionalMergedCells += (int) $cell_ss['MergeAcross'];
481 $columnTo = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($columnID) + $cell_ss['MergeAcross']);
482 }
483 $rowTo = $rowID;
484 if (isset($cell_ss['MergeDown'])) {
485 $rowTo = $rowTo + $cell_ss['MergeDown'];
486 }
487 $cellRange .= ':' . $columnTo . $rowTo;
488 $spreadsheet->getActiveSheet()->mergeCells($cellRange);
489 }
490
491 $cellIsSet = $hasCalculatedValue = false;
492 $cellDataFormula = '';
493 if (isset($cell_ss['Formula'])) {
494 $cellDataFormula = $cell_ss['Formula'];
495 $hasCalculatedValue = true;
496 }
497 if (isset($cell->Data)) {
498 $cellValue = $cellData = $cell->Data;
499 $type = DataType::TYPE_NULL;
500 $cellData_ss = $cellData->attributes($namespaces['ss']);
501 if (isset($cellData_ss['Type'])) {
502 $cellDataType = $cellData_ss['Type'];
503 switch ($cellDataType) {
504 /*
505 const TYPE_STRING = 's';
506 const TYPE_FORMULA = 'f';
507 const TYPE_NUMERIC = 'n';
508 const TYPE_BOOL = 'b';
509 const TYPE_NULL = 'null';
510 const TYPE_INLINE = 'inlineStr';
511 const TYPE_ERROR = 'e';
512 */
513 case 'String':
514 $cellValue = self::convertStringEncoding($cellValue, $this->charSet);
515 $type = DataType::TYPE_STRING;
516
517 break;
518 case 'Number':
519 $type = DataType::TYPE_NUMERIC;
520 $cellValue = (float) $cellValue;
521 if (floor($cellValue) == $cellValue) {
522 $cellValue = (int) $cellValue;
523 }
524
525 break;
526 case 'Boolean':
527 $type = DataType::TYPE_BOOL;
528 $cellValue = ($cellValue != 0);
529
530 break;
531 case 'DateTime':
532 $type = DataType::TYPE_NUMERIC;
533 $cellValue = Date::PHPToExcel(strtotime($cellValue));
534
535 break;
536 case 'Error':
537 $type = DataType::TYPE_ERROR;
538
539 break;
540 }
541 }
542
543 if ($hasCalculatedValue) {
544 $type = DataType::TYPE_FORMULA;
545 $columnNumber = Coordinate::columnIndexFromString($columnID);
546 if (substr($cellDataFormula, 0, 3) == 'of:') {
547 $cellDataFormula = substr($cellDataFormula, 3);
548 $temp = explode('"', $cellDataFormula);
549 $key = false;
550 foreach ($temp as &$value) {
551 // Only replace in alternate array entries (i.e. non-quoted blocks)
552 if ($key = !$key) {
553 $value = str_replace(['[.', '.', ']'], '', $value);
554 }
555 }
556 } else {
557 // Convert R1C1 style references to A1 style references (but only when not quoted)
558 $temp = explode('"', $cellDataFormula);
559 $key = false;
560 foreach ($temp as &$value) {
561 // Only replace in alternate array entries (i.e. non-quoted blocks)
562 if ($key = !$key) {
563 preg_match_all('/(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))/', $value, $cellReferences, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
564 // Reverse the matches array, otherwise all our offsets will become incorrect if we modify our way
565 // through the formula from left to right. Reversing means that we work right to left.through
566 // the formula
567 $cellReferences = array_reverse($cellReferences);
568 // Loop through each R1C1 style reference in turn, converting it to its A1 style equivalent,
569 // then modify the formula to use that new reference
570 foreach ($cellReferences as $cellReference) {
571 $rowReference = $cellReference[2][0];
572 // Empty R reference is the current row
573 if ($rowReference == '') {
574 $rowReference = $rowID;
575 }
576 // Bracketed R references are relative to the current row
577 if ($rowReference[0] == '[') {
578 $rowReference = $rowID + trim($rowReference, '[]');
579 }
580 $columnReference = $cellReference[4][0];
581 // Empty C reference is the current column
582 if ($columnReference == '') {
583 $columnReference = $columnNumber;
584 }
585 // Bracketed C references are relative to the current column
586 if ($columnReference[0] == '[') {
587 $columnReference = $columnNumber + trim($columnReference, '[]');
588 }
589 $A1CellReference = Coordinate::stringFromColumnIndex($columnReference) . $rowReference;
590 $value = substr_replace($value, $A1CellReference, $cellReference[0][1], strlen($cellReference[0][0]));
591 }
592 }
593 }
594 }
595 unset($value);
596 // Then rebuild the formula string
597 $cellDataFormula = implode('"', $temp);
598 }
599
600 $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValueExplicit((($hasCalculatedValue) ? $cellDataFormula : $cellValue), $type);
601 if ($hasCalculatedValue) {
602 $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setCalculatedValue($cellValue);
603 }
604 $cellIsSet = $rowHasData = true;
605 }
606
607 if (isset($cell->Comment)) {
608 $commentAttributes = $cell->Comment->attributes($namespaces['ss']);
609 $author = 'unknown';
610 if (isset($commentAttributes->Author)) {
611 $author = (string) $commentAttributes->Author;
612 }
613 $node = $cell->Comment->Data->asXML();
614 $annotation = strip_tags($node);
615 $spreadsheet->getActiveSheet()->getComment($columnID . $rowID)->setAuthor(self::convertStringEncoding($author, $this->charSet))->setText($this->parseRichText($annotation));
616 }
617
618 if (($cellIsSet) && (isset($cell_ss['StyleID']))) {
619 $style = (string) $cell_ss['StyleID'];
620 if ((isset($this->styles[$style])) && (!empty($this->styles[$style]))) {
621 if (!$spreadsheet->getActiveSheet()->cellExists($columnID . $rowID)) {
622 $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValue(null);
623 }
624 $spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($this->styles[$style]);
625 }
626 }
627 ++$columnID;
628 while ($additionalMergedCells > 0) {
629 ++$columnID;
630 --$additionalMergedCells;
631 }
632 }
633
634 if ($rowHasData) {
635 if (isset($row_ss['Height'])) {
636 $rowHeight = $row_ss['Height'];
637 $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setRowHeight($rowHeight);
638 }
639 }
640
641 ++$rowID;
642 }
643 }
644 ++$worksheetID;
645 }
646
647 // Return
648 return $spreadsheet;
649 }
650
651 protected static function convertStringEncoding($string, $charset)
652 {
653 if ($charset != 'UTF-8') {
654 return StringHelper::convertEncoding($string, 'UTF-8', $charset);
655 }
656
657 return $string;
658 }
659
660 protected function parseRichText($is)
661 {
662 $value = new RichText();
663
664 $value->createText(self::convertStringEncoding($is, $this->charSet));
665
666 return $value;
667 }
668
669 /**
670 * @param SimpleXMLElement $xml
671 * @param array $namespaces
672 */
673 private function parseStyles(SimpleXMLElement $xml, array $namespaces)
674 {
675 if (!isset($xml->Styles)) {
676 return;
677 }
678
679 foreach ($xml->Styles[0] as $style) {
680 $style_ss = $style->attributes($namespaces['ss']);
681 $styleID = (string) $style_ss['ID'];
682 $this->styles[$styleID] = (isset($this->styles['Default'])) ? $this->styles['Default'] : [];
683 foreach ($style as $styleType => $styleData) {
684 $styleAttributes = $styleData->attributes($namespaces['ss']);
685 switch ($styleType) {
686 case 'Alignment':
687 $this->parseStyleAlignment($styleID, $styleAttributes);
688
689 break;
690 case 'Borders':
691 $this->parseStyleBorders($styleID, $styleData, $namespaces);
692
693 break;
694 case 'Font':
695 $this->parseStyleFont($styleID, $styleAttributes);
696
697 break;
698 case 'Interior':
699 $this->parseStyleInterior($styleID, $styleAttributes);
700
701 break;
702 case 'NumberFormat':
703 $this->parseStyleNumberFormat($styleID, $styleAttributes);
704
705 break;
706 }
707 }
708 }
709 }
710
711 /**
712 * @param string $styleID
713 * @param SimpleXMLElement $styleAttributes
714 */
715 private function parseStyleAlignment($styleID, SimpleXMLElement $styleAttributes)
716 {
717 $verticalAlignmentStyles = [
718 Alignment::VERTICAL_BOTTOM,
719 Alignment::VERTICAL_TOP,
720 Alignment::VERTICAL_CENTER,
721 Alignment::VERTICAL_JUSTIFY,
722 ];
723 $horizontalAlignmentStyles = [
724 Alignment::HORIZONTAL_GENERAL,
725 Alignment::HORIZONTAL_LEFT,
726 Alignment::HORIZONTAL_RIGHT,
727 Alignment::HORIZONTAL_CENTER,
728 Alignment::HORIZONTAL_CENTER_CONTINUOUS,
729 Alignment::HORIZONTAL_JUSTIFY,
730 ];
731
732 foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
733 $styleAttributeValue = (string) $styleAttributeValue;
734 switch ($styleAttributeKey) {
735 case 'Vertical':
736 if (self::identifyFixedStyleValue($verticalAlignmentStyles, $styleAttributeValue)) {
737 $this->styles[$styleID]['alignment']['vertical'] = $styleAttributeValue;
738 }
739
740 break;
741 case 'Horizontal':
742 if (self::identifyFixedStyleValue($horizontalAlignmentStyles, $styleAttributeValue)) {
743 $this->styles[$styleID]['alignment']['horizontal'] = $styleAttributeValue;
744 }
745
746 break;
747 case 'WrapText':
748 $this->styles[$styleID]['alignment']['wrapText'] = true;
749
750 break;
751 }
752 }
753 }
754
755 /**
756 * @param $styleID
757 * @param SimpleXMLElement $styleData
758 * @param array $namespaces
759 */
760 private function parseStyleBorders($styleID, SimpleXMLElement $styleData, array $namespaces)
761 {
762 foreach ($styleData->Border as $borderStyle) {
763 $borderAttributes = $borderStyle->attributes($namespaces['ss']);
764 $thisBorder = [];
765 foreach ($borderAttributes as $borderStyleKey => $borderStyleValue) {
766 switch ($borderStyleKey) {
767 case 'LineStyle':
768 $thisBorder['borderStyle'] = Border::BORDER_MEDIUM;
769
770 break;
771 case 'Weight':
772 break;
773 case 'Position':
774 $borderPosition = strtolower($borderStyleValue);
775
776 break;
777 case 'Color':
778 $borderColour = substr($borderStyleValue, 1);
779 $thisBorder['color']['rgb'] = $borderColour;
780
781 break;
782 }
783 }
784 if (!empty($thisBorder)) {
785 if (($borderPosition == 'left') || ($borderPosition == 'right') || ($borderPosition == 'top') || ($borderPosition == 'bottom')) {
786 $this->styles[$styleID]['borders'][$borderPosition] = $thisBorder;
787 }
788 }
789 }
790 }
791
792 /**
793 * @param $styleID
794 * @param SimpleXMLElement $styleAttributes
795 */
796 private function parseStyleFont($styleID, SimpleXMLElement $styleAttributes)
797 {
798 $underlineStyles = [
799 Font::UNDERLINE_NONE,
800 Font::UNDERLINE_DOUBLE,
801 Font::UNDERLINE_DOUBLEACCOUNTING,
802 Font::UNDERLINE_SINGLE,
803 Font::UNDERLINE_SINGLEACCOUNTING,
804 ];
805
806 foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
807 $styleAttributeValue = (string) $styleAttributeValue;
808 switch ($styleAttributeKey) {
809 case 'FontName':
810 $this->styles[$styleID]['font']['name'] = $styleAttributeValue;
811
812 break;
813 case 'Size':
814 $this->styles[$styleID]['font']['size'] = $styleAttributeValue;
815
816 break;
817 case 'Color':
818 $this->styles[$styleID]['font']['color']['rgb'] = substr($styleAttributeValue, 1);
819
820 break;
821 case 'Bold':
822 $this->styles[$styleID]['font']['bold'] = true;
823
824 break;
825 case 'Italic':
826 $this->styles[$styleID]['font']['italic'] = true;
827
828 break;
829 case 'Underline':
830 if (self::identifyFixedStyleValue($underlineStyles, $styleAttributeValue)) {
831 $this->styles[$styleID]['font']['underline'] = $styleAttributeValue;
832 }
833
834 break;
835 }
836 }
837 }
838
839 /**
840 * @param $styleID
841 * @param SimpleXMLElement $styleAttributes
842 */
843 private function parseStyleInterior($styleID, SimpleXMLElement $styleAttributes)
844 {
845 foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
846 switch ($styleAttributeKey) {
847 case 'Color':
848 $this->styles[$styleID]['fill']['color']['rgb'] = substr($styleAttributeValue, 1);
849
850 break;
851 case 'Pattern':
852 $this->styles[$styleID]['fill']['fillType'] = strtolower($styleAttributeValue);
853
854 break;
855 }
856 }
857 }
858
859 /**
860 * @param $styleID
861 * @param SimpleXMLElement $styleAttributes
862 */
863 private function parseStyleNumberFormat($styleID, SimpleXMLElement $styleAttributes)
864 {
865 $fromFormats = ['\-', '\ '];
866 $toFormats = ['-', ' '];
867
868 foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
869 $styleAttributeValue = str_replace($fromFormats, $toFormats, $styleAttributeValue);
870 switch ($styleAttributeValue) {
871 case 'Short Date':
872 $styleAttributeValue = 'dd/mm/yyyy';
873
874 break;
875 }
876
877 if ($styleAttributeValue > '') {
878 $this->styles[$styleID]['numberFormat']['formatCode'] = $styleAttributeValue;
879 }
880 }
881 }
882 }
883