Failed Conditions
Push — develop ( bf2dbb...481fc4 )
by Adrien
31:39
created

Xml::parseStyleNumberFormat()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 10
nc 5
nop 2
dl 0
loc 16
rs 9.2
c 0
b 0
f 0
ccs 10
cts 10
cp 1
crap 4
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\RichText\RichText;
9
use PhpOffice\PhpSpreadsheet\Settings;
10
use PhpOffice\PhpSpreadsheet\Shared\Date;
11
use PhpOffice\PhpSpreadsheet\Shared\File;
12
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
13
use PhpOffice\PhpSpreadsheet\Spreadsheet;
14
use PhpOffice\PhpSpreadsheet\Style\Alignment;
15
use PhpOffice\PhpSpreadsheet\Style\Border;
16
use PhpOffice\PhpSpreadsheet\Style\Font;
17
use SimpleXMLElement;
18
19
/**
20
 * Reader for SpreadsheetML, the XML schema for Microsoft Office Excel 2003.
21
 */
22
class Xml extends BaseReader
23
{
24
    /**
25
     * Formats.
26
     *
27
     * @var array
28
     */
29
    protected $styles = [];
30
31
    /**
32
     * Character set used in the file.
33
     *
34
     * @var string
35
     */
36
    protected $charSet = 'UTF-8';
37
38
    /**
39
     * Create a new Excel2003XML Reader instance.
40
     */
41 7
    public function __construct()
42
    {
43 7
        $this->readFilter = new DefaultReadFilter();
44 7
    }
45
46
    /**
47
     * Can the current IReader read the file?
48
     *
49
     * @param string $pFilename
50
     *
51
     * @throws Exception
52
     *
53
     * @return bool
54
     */
55 5
    public function canRead($pFilename)
56
    {
57
        //    Office                    xmlns:o="urn:schemas-microsoft-com:office:office"
58
        //    Excel                    xmlns:x="urn:schemas-microsoft-com:office:excel"
59
        //    XML Spreadsheet            xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
60
        //    Spreadsheet component    xmlns:c="urn:schemas-microsoft-com:office:component:spreadsheet"
61
        //    XML schema                 xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
62
        //    XML data type            xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
63
        //    MS-persist recordset    xmlns:rs="urn:schemas-microsoft-com:rowset"
64
        //    Rowset                    xmlns:z="#RowsetSchema"
65
        //
66
67
        $signature = [
68 5
            '<?xml version="1.0"',
69
            '<?mso-application progid="Excel.Sheet"?>',
70
        ];
71
72
        // Open file
73 5
        $this->openFile($pFilename);
74 5
        $fileHandle = $this->fileHandle;
75
76
        // Read sample data (first 2 KB will do)
77 5
        $data = fread($fileHandle, 2048);
1 ignored issue
show
Bug introduced by
It seems like $fileHandle can also be of type false; however, parameter $handle of fread() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

77
        $data = fread(/** @scrutinizer ignore-type */ $fileHandle, 2048);
Loading history...
78 5
        fclose($fileHandle);
1 ignored issue
show
Bug introduced by
It seems like $fileHandle can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

78
        fclose(/** @scrutinizer ignore-type */ $fileHandle);
Loading history...
79 5
        $data = strtr($data, "'", '"'); // fix headers with single quote
80
81 5
        $valid = true;
82 5
        foreach ($signature as $match) {
83
            // every part of the signature must be present
84 5
            if (strpos($data, $match) === false) {
85
                $valid = false;
86
87 5
                break;
88
            }
89
        }
90
91
        //    Retrieve charset encoding
92 5
        if (preg_match('/<?xml.*encoding=[\'"](.*?)[\'"].*?>/um', $data, $matches)) {
93 5
            $this->charSet = strtoupper($matches[1]);
94
        }
95
96 5
        return $valid;
97
    }
98
99
    /**
100
     * Check if the file is a valid SimpleXML.
101
     *
102
     * @param string $pFilename
103
     *
104
     * @throws Exception
105
     *
106
     * @return false|\SimpleXMLElement
107
     */
108 4
    public function trySimpleXMLLoadString($pFilename)
109
    {
110
        try {
111 4
            $xml = simplexml_load_string(
112 4
                $this->securityScan(file_get_contents($pFilename)),
113 4
                'SimpleXMLElement',
114 4
                Settings::getLibXmlLoaderOptions()
115
            );
116 1
        } catch (\Exception $e) {
117 1
            throw new Exception('Cannot load invalid XML file: ' . $pFilename, 0, $e);
118
        }
119
120 3
        return $xml;
121
    }
122
123
    /**
124
     * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
125
     *
126
     * @param string $pFilename
127
     *
128
     * @throws Exception
129
     */
130
    public function listWorksheetNames($pFilename)
131
    {
132
        File::assertFile($pFilename);
133
        if (!$this->canRead($pFilename)) {
134
            throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
135
        }
136
137
        $worksheetNames = [];
138
139
        $xml = $this->trySimpleXMLLoadString($pFilename);
140
141
        $namespaces = $xml->getNamespaces(true);
142
143
        $xml_ss = $xml->children($namespaces['ss']);
144
        foreach ($xml_ss->Worksheet as $worksheet) {
145
            $worksheet_ss = $worksheet->attributes($namespaces['ss']);
146
            $worksheetNames[] = self::convertStringEncoding((string) $worksheet_ss['Name'], $this->charSet);
147
        }
148
149
        return $worksheetNames;
150
    }
151
152
    /**
153
     * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
154
     *
155
     * @param string $pFilename
156
     *
157
     * @throws Exception
158
     */
159
    public function listWorksheetInfo($pFilename)
160
    {
161
        File::assertFile($pFilename);
162
163
        $worksheetInfo = [];
164
165
        $xml = $this->trySimpleXMLLoadString($pFilename);
166
167
        $namespaces = $xml->getNamespaces(true);
168
169
        $worksheetID = 1;
170
        $xml_ss = $xml->children($namespaces['ss']);
171
        foreach ($xml_ss->Worksheet as $worksheet) {
172
            $worksheet_ss = $worksheet->attributes($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
            if (isset($worksheet_ss['Name'])) {
182
                $tmpInfo['worksheetName'] = (string) $worksheet_ss['Name'];
183
            } else {
184
                $tmpInfo['worksheetName'] = "Worksheet_{$worksheetID}";
185
            }
186
187
            if (isset($worksheet->Table->Row)) {
188
                $rowIndex = 0;
189
190
                foreach ($worksheet->Table->Row as $rowData) {
191
                    $columnIndex = 0;
192
                    $rowHasData = false;
193
194
                    foreach ($rowData->Cell as $cell) {
195
                        if (isset($cell->Data)) {
196
                            $tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex);
197
                            $rowHasData = true;
198
                        }
199
200
                        ++$columnIndex;
201
                    }
202
203
                    ++$rowIndex;
204
205
                    if ($rowHasData) {
206
                        $tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex);
207
                    }
208
                }
209
            }
210
211
            $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
212
            $tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1;
213
214
            $worksheetInfo[] = $tmpInfo;
215
            ++$worksheetID;
216
        }
217
218
        return $worksheetInfo;
219
    }
220
221
    /**
222
     * Loads Spreadsheet from file.
223
     *
224
     * @param string $pFilename
225
     *
226
     * @throws Exception
227
     *
228
     * @return Spreadsheet
229
     */
230 3
    public function load($pFilename)
231
    {
232
        // Create new Spreadsheet
233 3
        $spreadsheet = new Spreadsheet();
234 3
        $spreadsheet->removeSheetByIndex(0);
235
236
        // Load into this instance
237 3
        return $this->loadIntoExisting($pFilename, $spreadsheet);
238
    }
239
240 2
    private static function identifyFixedStyleValue($styleList, &$styleAttributeValue)
241
    {
242 2
        $styleAttributeValue = strtolower($styleAttributeValue);
243 2
        foreach ($styleList as $style) {
244 2
            if ($styleAttributeValue == strtolower($style)) {
245 2
                $styleAttributeValue = $style;
246
247 2
                return true;
248
            }
249
        }
250
251
        return false;
252
    }
253
254
    /**
255
     * pixel units to excel width units(units of 1/256th of a character width).
256
     *
257
     * @param float $pxs
258
     *
259
     * @return float
260
     */
261
    protected static function pixel2WidthUnits($pxs)
262
    {
263
        $UNIT_OFFSET_MAP = [0, 36, 73, 109, 146, 182, 219];
264
265
        $widthUnits = 256 * ($pxs / 7);
266
        $widthUnits += $UNIT_OFFSET_MAP[($pxs % 7)];
267
268
        return $widthUnits;
269
    }
270
271
    /**
272
     * excel width units(units of 1/256th of a character width) to pixel units.
273
     *
274
     * @param float $widthUnits
275
     *
276
     * @return float
277
     */
278
    protected static function widthUnits2Pixel($widthUnits)
279
    {
280
        $pixels = ($widthUnits / 256) * 7;
281
        $offsetWidthUnits = $widthUnits % 256;
282
        $pixels += round($offsetWidthUnits / (256 / 7));
283
284
        return $pixels;
285
    }
286
287
    protected static function hex2str($hex)
288
    {
289
        return chr(hexdec($hex[1]));
1 ignored issue
show
Bug introduced by
It seems like hexdec($hex[1]) can also be of type double; however, parameter $ascii of chr() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

289
        return chr(/** @scrutinizer ignore-type */ hexdec($hex[1]));
Loading history...
290
    }
291
292
    /**
293
     * Loads from file into Spreadsheet instance.
294
     *
295
     * @param string $pFilename
296
     * @param Spreadsheet $spreadsheet
297
     *
298
     * @throws Exception
299
     *
300
     * @return Spreadsheet
301
     */
302 3
    public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
303
    {
304 3
        File::assertFile($pFilename);
305 3
        if (!$this->canRead($pFilename)) {
306
            throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
307
        }
308
309 3
        $xml = $this->trySimpleXMLLoadString($pFilename);
310
311 3
        $namespaces = $xml->getNamespaces(true);
312
313 3
        $docProps = $spreadsheet->getProperties();
314 3
        if (isset($xml->DocumentProperties[0])) {
315
            foreach ($xml->DocumentProperties[0] as $propertyName => $propertyValue) {
316
                switch ($propertyName) {
317
                    case 'Title':
318
                        $docProps->setTitle(self::convertStringEncoding($propertyValue, $this->charSet));
319
320
                        break;
321
                    case 'Subject':
322
                        $docProps->setSubject(self::convertStringEncoding($propertyValue, $this->charSet));
323
324
                        break;
325
                    case 'Author':
326
                        $docProps->setCreator(self::convertStringEncoding($propertyValue, $this->charSet));
327
328
                        break;
329
                    case 'Created':
330
                        $creationDate = strtotime($propertyValue);
331
                        $docProps->setCreated($creationDate);
332
333
                        break;
334
                    case 'LastAuthor':
335
                        $docProps->setLastModifiedBy(self::convertStringEncoding($propertyValue, $this->charSet));
336
337
                        break;
338
                    case 'LastSaved':
339
                        $lastSaveDate = strtotime($propertyValue);
340
                        $docProps->setModified($lastSaveDate);
341
342
                        break;
343
                    case 'Company':
344
                        $docProps->setCompany(self::convertStringEncoding($propertyValue, $this->charSet));
345
346
                        break;
347
                    case 'Category':
348
                        $docProps->setCategory(self::convertStringEncoding($propertyValue, $this->charSet));
349
350
                        break;
351
                    case 'Manager':
352
                        $docProps->setManager(self::convertStringEncoding($propertyValue, $this->charSet));
353
354
                        break;
355
                    case 'Keywords':
356
                        $docProps->setKeywords(self::convertStringEncoding($propertyValue, $this->charSet));
357
358
                        break;
359
                    case 'Description':
360
                        $docProps->setDescription(self::convertStringEncoding($propertyValue, $this->charSet));
361
362
                        break;
363
                }
364
            }
365
        }
366 3
        if (isset($xml->CustomDocumentProperties)) {
367
            foreach ($xml->CustomDocumentProperties[0] as $propertyName => $propertyValue) {
368
                $propertyAttributes = $propertyValue->attributes($namespaces['dt']);
369
                $propertyName = preg_replace_callback('/_x([0-9a-z]{4})_/', ['self', 'hex2str'], $propertyName);
370
                $propertyType = Properties::PROPERTY_TYPE_UNKNOWN;
371
                switch ((string) $propertyAttributes) {
372
                    case 'string':
373
                        $propertyType = Properties::PROPERTY_TYPE_STRING;
374
                        $propertyValue = trim($propertyValue);
375
376
                        break;
377
                    case 'boolean':
378
                        $propertyType = Properties::PROPERTY_TYPE_BOOLEAN;
379
                        $propertyValue = (bool) $propertyValue;
380
381
                        break;
382
                    case 'integer':
383
                        $propertyType = Properties::PROPERTY_TYPE_INTEGER;
384
                        $propertyValue = (int) $propertyValue;
385
386
                        break;
387
                    case 'float':
388
                        $propertyType = Properties::PROPERTY_TYPE_FLOAT;
389
                        $propertyValue = (float) $propertyValue;
390
391
                        break;
392
                    case 'dateTime.tz':
393
                        $propertyType = Properties::PROPERTY_TYPE_DATE;
394
                        $propertyValue = strtotime(trim($propertyValue));
395
396
                        break;
397
                }
398
                $docProps->setCustomProperty($propertyName, $propertyValue, $propertyType);
399
            }
400
        }
401
402 3
        $this->parseStyles($xml, $namespaces);
403
404 3
        $worksheetID = 0;
405 3
        $xml_ss = $xml->children($namespaces['ss']);
406
407 3
        foreach ($xml_ss->Worksheet as $worksheet) {
408 3
            $worksheet_ss = $worksheet->attributes($namespaces['ss']);
409
410 3
            if ((isset($this->loadSheetsOnly)) && (isset($worksheet_ss['Name'])) &&
411 3
                (!in_array($worksheet_ss['Name'], $this->loadSheetsOnly))) {
412
                continue;
413
            }
414
415
            // Create new Worksheet
416 3
            $spreadsheet->createSheet();
417 3
            $spreadsheet->setActiveSheetIndex($worksheetID);
418 3
            if (isset($worksheet_ss['Name'])) {
419 3
                $worksheetName = self::convertStringEncoding((string) $worksheet_ss['Name'], $this->charSet);
420
                //    Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
421
                //        formula cells... during the load, all formulae should be correct, and we're simply bringing
422
                //        the worksheet name in line with the formula, not the reverse
423 3
                $spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
424
            }
425
426 3
            $columnID = 'A';
427 3
            if (isset($worksheet->Table->Column)) {
428 3
                foreach ($worksheet->Table->Column as $columnData) {
429 3
                    $columnData_ss = $columnData->attributes($namespaces['ss']);
430 3
                    if (isset($columnData_ss['Index'])) {
431 3
                        $columnID = Coordinate::stringFromColumnIndex((int) $columnData_ss['Index']);
432
                    }
433 3
                    if (isset($columnData_ss['Width'])) {
434 3
                        $columnWidth = $columnData_ss['Width'];
435 3
                        $spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setWidth($columnWidth / 5.4);
436
                    }
437 3
                    ++$columnID;
438
                }
439
            }
440
441 3
            $rowID = 1;
442 3
            if (isset($worksheet->Table->Row)) {
443 3
                $additionalMergedCells = 0;
444 3
                foreach ($worksheet->Table->Row as $rowData) {
445 3
                    $rowHasData = false;
446 3
                    $row_ss = $rowData->attributes($namespaces['ss']);
447 3
                    if (isset($row_ss['Index'])) {
448 2
                        $rowID = (int) $row_ss['Index'];
449
                    }
450
451 3
                    $columnID = 'A';
452 3
                    foreach ($rowData->Cell as $cell) {
453 3
                        $cell_ss = $cell->attributes($namespaces['ss']);
454 3
                        if (isset($cell_ss['Index'])) {
455 2
                            $columnID = Coordinate::stringFromColumnIndex((int) $cell_ss['Index']);
456
                        }
457 3
                        $cellRange = $columnID . $rowID;
458
459 3
                        if ($this->getReadFilter() !== null) {
460 3
                            if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $worksheetName does not seem to be defined for all execution paths leading up to this point.
Loading history...
461
                                ++$columnID;
462
463
                                continue;
464
                            }
465
                        }
466
467 3
                        if (isset($cell_ss['HRef'])) {
468 2
                            $spreadsheet->getActiveSheet()->getCell($cellRange)->getHyperlink()->setUrl($cell_ss['HRef']);
469
                        }
470
471 3
                        if ((isset($cell_ss['MergeAcross'])) || (isset($cell_ss['MergeDown']))) {
472 2
                            $columnTo = $columnID;
473 2
                            if (isset($cell_ss['MergeAcross'])) {
474 2
                                $additionalMergedCells += (int) $cell_ss['MergeAcross'];
475 2
                                $columnTo = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($columnID) + $cell_ss['MergeAcross']);
476
                            }
477 2
                            $rowTo = $rowID;
478 2
                            if (isset($cell_ss['MergeDown'])) {
479 2
                                $rowTo = $rowTo + $cell_ss['MergeDown'];
480
                            }
481 2
                            $cellRange .= ':' . $columnTo . $rowTo;
482 2
                            $spreadsheet->getActiveSheet()->mergeCells($cellRange);
483
                        }
484
485 3
                        $cellIsSet = $hasCalculatedValue = false;
486 3
                        $cellDataFormula = '';
487 3
                        if (isset($cell_ss['Formula'])) {
488 2
                            $cellDataFormula = $cell_ss['Formula'];
489 2
                            $hasCalculatedValue = true;
490
                        }
491 3
                        if (isset($cell->Data)) {
492 3
                            $cellValue = $cellData = $cell->Data;
493 3
                            $type = DataType::TYPE_NULL;
494 3
                            $cellData_ss = $cellData->attributes($namespaces['ss']);
495 3
                            if (isset($cellData_ss['Type'])) {
496 3
                                $cellDataType = $cellData_ss['Type'];
497
                                switch ($cellDataType) {
498
                                    /*
499
                                    const TYPE_STRING        = 's';
500
                                    const TYPE_FORMULA        = 'f';
501
                                    const TYPE_NUMERIC        = 'n';
502
                                    const TYPE_BOOL            = 'b';
503
                                    const TYPE_NULL            = 'null';
504
                                    const TYPE_INLINE        = 'inlineStr';
505
                                    const TYPE_ERROR        = 'e';
506
                                    */
507 3
                                    case 'String':
508 3
                                        $cellValue = self::convertStringEncoding($cellValue, $this->charSet);
509 3
                                        $type = DataType::TYPE_STRING;
510
511 3
                                        break;
512 3
                                    case 'Number':
513 3
                                        $type = DataType::TYPE_NUMERIC;
514 3
                                        $cellValue = (float) $cellValue;
515 3
                                        if (floor($cellValue) == $cellValue) {
516 3
                                            $cellValue = (int) $cellValue;
517
                                        }
518
519 3
                                        break;
520 2
                                    case 'Boolean':
521 2
                                        $type = DataType::TYPE_BOOL;
522 2
                                        $cellValue = ($cellValue != 0);
523
524 2
                                        break;
525 2
                                    case 'DateTime':
526 2
                                        $type = DataType::TYPE_NUMERIC;
527 2
                                        $cellValue = Date::PHPToExcel(strtotime($cellValue));
528
529 2
                                        break;
530
                                    case 'Error':
531
                                        $type = DataType::TYPE_ERROR;
532
533
                                        break;
534
                                }
535
                            }
536
537 3
                            if ($hasCalculatedValue) {
538 2
                                $type = DataType::TYPE_FORMULA;
539 2
                                $columnNumber = Coordinate::columnIndexFromString($columnID);
540 2
                                if (substr($cellDataFormula, 0, 3) == 'of:') {
541 2
                                    $cellDataFormula = substr($cellDataFormula, 3);
542 2
                                    $temp = explode('"', $cellDataFormula);
543 2
                                    $key = false;
544 2
                                    foreach ($temp as &$value) {
545
                                        //    Only replace in alternate array entries (i.e. non-quoted blocks)
546 2
                                        if ($key = !$key) {
547 2
                                            $value = str_replace(['[.', '.', ']'], '', $value);
548
                                        }
549
                                    }
550
                                } else {
551
                                    //    Convert R1C1 style references to A1 style references (but only when not quoted)
552
                                    $temp = explode('"', $cellDataFormula);
553
                                    $key = false;
554
                                    foreach ($temp as &$value) {
555
                                        //    Only replace in alternate array entries (i.e. non-quoted blocks)
556
                                        if ($key = !$key) {
557
                                            preg_match_all('/(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))/', $value, $cellReferences, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
558
                                            //    Reverse the matches array, otherwise all our offsets will become incorrect if we modify our way
559
                                            //        through the formula from left to right. Reversing means that we work right to left.through
560
                                            //        the formula
561
                                            $cellReferences = array_reverse($cellReferences);
562
                                            //    Loop through each R1C1 style reference in turn, converting it to its A1 style equivalent,
563
                                            //        then modify the formula to use that new reference
564
                                            foreach ($cellReferences as $cellReference) {
565
                                                $rowReference = $cellReference[2][0];
566
                                                //    Empty R reference is the current row
567
                                                if ($rowReference == '') {
568
                                                    $rowReference = $rowID;
569
                                                }
570
                                                //    Bracketed R references are relative to the current row
571
                                                if ($rowReference[0] == '[') {
572
                                                    $rowReference = $rowID + trim($rowReference, '[]');
573
                                                }
574
                                                $columnReference = $cellReference[4][0];
575
                                                //    Empty C reference is the current column
576
                                                if ($columnReference == '') {
577
                                                    $columnReference = $columnNumber;
578
                                                }
579
                                                //    Bracketed C references are relative to the current column
580
                                                if ($columnReference[0] == '[') {
581
                                                    $columnReference = $columnNumber + trim($columnReference, '[]');
582
                                                }
583
                                                $A1CellReference = Coordinate::stringFromColumnIndex($columnReference) . $rowReference;
584
                                                $value = substr_replace($value, $A1CellReference, $cellReference[0][1], strlen($cellReference[0][0]));
585
                                            }
586
                                        }
587
                                    }
588
                                }
589 2
                                unset($value);
590
                                //    Then rebuild the formula string
591 2
                                $cellDataFormula = implode('"', $temp);
592
                            }
593
594 3
                            $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValueExplicit((($hasCalculatedValue) ? $cellDataFormula : $cellValue), $type);
595 3
                            if ($hasCalculatedValue) {
596 2
                                $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setCalculatedValue($cellValue);
597
                            }
598 3
                            $cellIsSet = $rowHasData = true;
599
                        }
600
601 3
                        if (isset($cell->Comment)) {
602 3
                            $commentAttributes = $cell->Comment->attributes($namespaces['ss']);
603 3
                            $author = 'unknown';
604 3
                            if (isset($commentAttributes->Author)) {
605
                                $author = (string) $commentAttributes->Author;
606
                            }
607 3
                            $node = $cell->Comment->Data->asXML();
608 3
                            $annotation = strip_tags($node);
609 3
                            $spreadsheet->getActiveSheet()->getComment($columnID . $rowID)->setAuthor(self::convertStringEncoding($author, $this->charSet))->setText($this->parseRichText($annotation));
610
                        }
611
612 3
                        if (($cellIsSet) && (isset($cell_ss['StyleID']))) {
613 2
                            $style = (string) $cell_ss['StyleID'];
614 2
                            if ((isset($this->styles[$style])) && (!empty($this->styles[$style]))) {
615 2
                                if (!$spreadsheet->getActiveSheet()->cellExists($columnID . $rowID)) {
616
                                    $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValue(null);
617
                                }
618 2
                                $spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($this->styles[$style]);
619
                            }
620
                        }
621 3
                        ++$columnID;
622 3
                        while ($additionalMergedCells > 0) {
623 2
                            ++$columnID;
624 2
                            --$additionalMergedCells;
625
                        }
626
                    }
627
628 3
                    if ($rowHasData) {
629 3
                        if (isset($row_ss['Height'])) {
630 3
                            $rowHeight = $row_ss['Height'];
631 3
                            $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setRowHeight($rowHeight);
632
                        }
633
                    }
634
635 3
                    ++$rowID;
636
                }
637
            }
638 3
            ++$worksheetID;
639
        }
640
641
        // Return
642 3
        return $spreadsheet;
643
    }
644
645 3
    protected static function convertStringEncoding($string, $charset)
646
    {
647 3
        if ($charset != 'UTF-8') {
648
            return StringHelper::convertEncoding($string, 'UTF-8', $charset);
649
        }
650
651 3
        return $string;
652
    }
653
654 3
    protected function parseRichText($is)
655
    {
656 3
        $value = new RichText();
657
658 3
        $value->createText(self::convertStringEncoding($is, $this->charSet));
659
660 3
        return $value;
661
    }
662
663
    /**
664
     * @param SimpleXMLElement $xml
665
     * @param array $namespaces
666
     */
667 3
    private function parseStyles(SimpleXMLElement $xml, array $namespaces)
668
    {
669 3
        if (!isset($xml->Styles)) {
670 1
            return;
671
        }
672
673 2
        foreach ($xml->Styles[0] as $style) {
674 2
            $style_ss = $style->attributes($namespaces['ss']);
675 2
            $styleID = (string) $style_ss['ID'];
676 2
            $this->styles[$styleID] = (isset($this->styles['Default'])) ? $this->styles['Default'] : [];
677 2
            foreach ($style as $styleType => $styleData) {
678 2
                $styleAttributes = $styleData->attributes($namespaces['ss']);
679
                switch ($styleType) {
680 2
                    case 'Alignment':
681 2
                        $this->parseStyleAlignment($styleID, $styleAttributes);
682
683 2
                        break;
684 2
                    case 'Borders':
685 2
                        $this->parseStyleBorders($styleID, $styleData, $namespaces);
686
687 2
                        break;
688 2
                    case 'Font':
689 2
                        $this->parseStyleFont($styleID, $styleAttributes);
690
691 2
                        break;
692 2
                    case 'Interior':
693 2
                        $this->parseStyleInterior($styleID, $styleAttributes);
694
695 2
                        break;
696 2
                    case 'NumberFormat':
697 2
                        $this->parseStyleNumberFormat($styleID, $styleAttributes);
698
699 2
                        break;
700
                }
701
            }
702
        }
703 2
    }
704
705
    /**
706
     * @param string $styleID
707
     * @param SimpleXMLElement $styleAttributes
708
     */
709 2
    private function parseStyleAlignment($styleID, SimpleXMLElement $styleAttributes)
710
    {
711
        $verticalAlignmentStyles = [
712 2
            Alignment::VERTICAL_BOTTOM,
713 1
            Alignment::VERTICAL_TOP,
714 1
            Alignment::VERTICAL_CENTER,
715 1
            Alignment::VERTICAL_JUSTIFY,
716
        ];
717
        $horizontalAlignmentStyles = [
718 2
            Alignment::HORIZONTAL_GENERAL,
719 1
            Alignment::HORIZONTAL_LEFT,
720 1
            Alignment::HORIZONTAL_RIGHT,
721 1
            Alignment::HORIZONTAL_CENTER,
722 1
            Alignment::HORIZONTAL_CENTER_CONTINUOUS,
723 1
            Alignment::HORIZONTAL_JUSTIFY,
724
        ];
725
726 2
        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
727 2
            $styleAttributeValue = (string) $styleAttributeValue;
728
            switch ($styleAttributeKey) {
729 2
                case 'Vertical':
730 2
                    if (self::identifyFixedStyleValue($verticalAlignmentStyles, $styleAttributeValue)) {
731 2
                        $this->styles[$styleID]['alignment']['vertical'] = $styleAttributeValue;
732
                    }
733
734 2
                    break;
735 2
                case 'Horizontal':
736 2
                    if (self::identifyFixedStyleValue($horizontalAlignmentStyles, $styleAttributeValue)) {
737 2
                        $this->styles[$styleID]['alignment']['horizontal'] = $styleAttributeValue;
738
                    }
739
740 2
                    break;
741 2
                case 'WrapText':
742 2
                    $this->styles[$styleID]['alignment']['wrapText'] = true;
743
744 2
                    break;
745
            }
746
        }
747 2
    }
748
749
    /**
750
     * @param $styleID
751
     * @param SimpleXMLElement $styleData
752
     * @param array $namespaces
753
     */
754 2
    private function parseStyleBorders($styleID, SimpleXMLElement $styleData, array $namespaces)
755
    {
756 2
        foreach ($styleData->Border as $borderStyle) {
757 2
            $borderAttributes = $borderStyle->attributes($namespaces['ss']);
758 2
            $thisBorder = [];
759 2
            foreach ($borderAttributes as $borderStyleKey => $borderStyleValue) {
760
                switch ($borderStyleKey) {
761 2
                    case 'LineStyle':
762 2
                        $thisBorder['borderStyle'] = Border::BORDER_MEDIUM;
763
764 2
                        break;
765 2
                    case 'Weight':
766 2
                        break;
767 2
                    case 'Position':
768 2
                        $borderPosition = strtolower($borderStyleValue);
769
770 2
                        break;
771 2
                    case 'Color':
772 2
                        $borderColour = substr($borderStyleValue, 1);
773 2
                        $thisBorder['color']['rgb'] = $borderColour;
774
775 2
                        break;
776
                }
777
            }
778 2
            if (!empty($thisBorder)) {
0 ignored issues
show
introduced by
The condition ! empty($thisBorder) can never be false.
Loading history...
779 2
                if (($borderPosition == 'left') || ($borderPosition == 'right') || ($borderPosition == 'top') || ($borderPosition == 'bottom')) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $borderPosition does not seem to be defined for all execution paths leading up to this point.
Loading history...
780 2
                    $this->styles[$styleID]['borders'][$borderPosition] = $thisBorder;
781
                }
782
            }
783
        }
784 2
    }
785
786
    /**
787
     * @param $styleID
788
     * @param SimpleXMLElement $styleAttributes
789
     */
790 2
    private function parseStyleFont($styleID, SimpleXMLElement $styleAttributes)
791
    {
792
        $underlineStyles = [
793 2
            Font::UNDERLINE_NONE,
794 1
            Font::UNDERLINE_DOUBLE,
795 1
            Font::UNDERLINE_DOUBLEACCOUNTING,
796 1
            Font::UNDERLINE_SINGLE,
797 1
            Font::UNDERLINE_SINGLEACCOUNTING,
798
        ];
799
800 2
        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
801 2
            $styleAttributeValue = (string) $styleAttributeValue;
802
            switch ($styleAttributeKey) {
803 2
                case 'FontName':
804 2
                    $this->styles[$styleID]['font']['name'] = $styleAttributeValue;
805
806 2
                    break;
807 2
                case 'Size':
808 2
                    $this->styles[$styleID]['font']['size'] = $styleAttributeValue;
809
810 2
                    break;
811 2
                case 'Color':
812 2
                    $this->styles[$styleID]['font']['color']['rgb'] = substr($styleAttributeValue, 1);
813
814 2
                    break;
815 2
                case 'Bold':
816 2
                    $this->styles[$styleID]['font']['bold'] = true;
817
818 2
                    break;
819 2
                case 'Italic':
820 2
                    $this->styles[$styleID]['font']['italic'] = true;
821
822 2
                    break;
823 2
                case 'Underline':
824 2
                    if (self::identifyFixedStyleValue($underlineStyles, $styleAttributeValue)) {
825 2
                        $this->styles[$styleID]['font']['underline'] = $styleAttributeValue;
826
                    }
827
828 2
                    break;
829
            }
830
        }
831 2
    }
832
833
    /**
834
     * @param $styleID
835
     * @param SimpleXMLElement $styleAttributes
836
     */
837 2
    private function parseStyleInterior($styleID, SimpleXMLElement $styleAttributes)
838
    {
839 2
        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
840
            switch ($styleAttributeKey) {
841 2
                case 'Color':
842 2
                    $this->styles[$styleID]['fill']['color']['rgb'] = substr($styleAttributeValue, 1);
843
844 2
                    break;
845 2
                case 'Pattern':
846 2
                    $this->styles[$styleID]['fill']['fillType'] = strtolower($styleAttributeValue);
847
848 2
                    break;
849
            }
850
        }
851 2
    }
852
853
    /**
854
     * @param $styleID
855
     * @param SimpleXMLElement $styleAttributes
856
     */
857 2
    private function parseStyleNumberFormat($styleID, SimpleXMLElement $styleAttributes)
858
    {
859 2
        $fromFormats = ['\-', '\ '];
860 2
        $toFormats = ['-', ' '];
861
862 2
        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
863 2
            $styleAttributeValue = str_replace($fromFormats, $toFormats, $styleAttributeValue);
864
            switch ($styleAttributeValue) {
865 2
                case 'Short Date':
866 2
                    $styleAttributeValue = 'dd/mm/yyyy';
867
868 2
                    break;
869
            }
870
871 2
            if ($styleAttributeValue > '') {
872 2
                $this->styles[$styleID]['numberFormat']['formatCode'] = $styleAttributeValue;
873
            }
874
        }
875 2
    }
876
}
877