Completed
Push — develop ( 4fd8e7...d3e769 )
by Adrien
14:12 queued 07:04
created

Xml::load()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1.125

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 1
dl 0
loc 9
ccs 2
cts 4
cp 0.5
crap 1.125
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Reader;
4
5
use DateTimeZone;
6
use PhpOffice\PhpSpreadsheet\Cell;
7
use PhpOffice\PhpSpreadsheet\Document\Properties;
8
use PhpOffice\PhpSpreadsheet\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
18
/**
19
 * Reader for SpreadsheetML, the XML schema for Microsoft Office Excel 2003.
20
 */
21
class Xml extends BaseReader implements IReader
22
{
23
    /**
24
     * Formats.
25
     *
26
     * @var array
27
     */
28
    protected $styles = [];
29
30
    /**
31
     * Character set used in the file.
32
     *
33
     * @var string
34
     */
35
    protected $charSet = 'UTF-8';
36
37
    /**
38
     * Create a new Excel2003XML Reader instance.
39
     */
40 4
    public function __construct()
41
    {
42 4
        $this->readFilter = new DefaultReadFilter();
43 4
    }
44
45
    /**
46
     * Can the current IReader read the file?
47
     *
48
     * @param string $pFilename
49
     *
50
     * @throws Exception
51
     *
52
     * @return bool
53
     */
54 2
    public function canRead($pFilename)
55
    {
56
        //    Office                    xmlns:o="urn:schemas-microsoft-com:office:office"
57
        //    Excel                    xmlns:x="urn:schemas-microsoft-com:office:excel"
58
        //    XML Spreadsheet            xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
59
        //    Spreadsheet component    xmlns:c="urn:schemas-microsoft-com:office:component:spreadsheet"
60
        //    XML schema                 xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
61
        //    XML data type            xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
62
        //    MS-persist recordset    xmlns:rs="urn:schemas-microsoft-com:rowset"
63
        //    Rowset                    xmlns:z="#RowsetSchema"
64
        //
65
66
        $signature = [
67 2
                '<?xml version="1.0"',
68
                '<?mso-application progid="Excel.Sheet"?>',
69
            ];
70
71
        // Open file
72 2
        $this->openFile($pFilename);
73 2
        $fileHandle = $this->fileHandle;
74
75
        // Read sample data (first 2 KB will do)
76 2
        $data = fread($fileHandle, 2048);
77 2
        fclose($fileHandle);
78 2
        $data = strtr($data, "'", '"'); // fix headers with single quote
79
80 2
        $valid = true;
81 2
        foreach ($signature as $match) {
82
            // every part of the signature must be present
83 2
            if (strpos($data, $match) === false) {
84
                $valid = false;
85
86 2
                break;
87
            }
88
        }
89
90
        //    Retrieve charset encoding
91 2
        if (preg_match('/<?xml.*encoding=[\'"](.*?)[\'"].*?>/um', $data, $matches)) {
92 2
            $this->charSet = strtoupper($matches[1]);
93
        }
94
95 2
        return $valid;
96
    }
97
98
    /**
99
     * Check if the file is a valid SimpleXML.
100
     *
101
     * @param string $pFilename
102
     *
103
     * @throws Exception
104
     *
105
     * @return false|\SimpleXMLElement
106
     */
107 1
    public function trySimpleXMLLoadString($pFilename)
108
    {
109
        try {
110 1
            $xml = simplexml_load_string(
111 1
                $this->securityScan(file_get_contents($pFilename)),
112 1
                'SimpleXMLElement',
113 1
                Settings::getLibXmlLoaderOptions()
114
            );
115 1
        } catch (\Exception $e) {
116 1
            throw new Exception('Cannot load invalid XML file: ' . $pFilename, 0, $e);
117
        }
118
119
        return $xml;
120
    }
121
122
    /**
123
     * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
124
     *
125
     * @param string $pFilename
126
     *
127
     * @throws Exception
128
     */
129
    public function listWorksheetNames($pFilename)
130
    {
131
        File::assertFile($pFilename);
132
        if (!$this->canRead($pFilename)) {
133
            throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
134
        }
135
136
        $worksheetNames = [];
137
138
        $xml = $this->trySimpleXMLLoadString($pFilename);
139
140
        $namespaces = $xml->getNamespaces(true);
141
142
        $xml_ss = $xml->children($namespaces['ss']);
143
        foreach ($xml_ss->Worksheet as $worksheet) {
144
            $worksheet_ss = $worksheet->attributes($namespaces['ss']);
145
            $worksheetNames[] = self::convertStringEncoding((string) $worksheet_ss['Name'], $this->charSet);
146
        }
147
148
        return $worksheetNames;
149
    }
150
151
    /**
152
     * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
153
     *
154
     * @param string $pFilename
155
     *
156
     * @throws Exception
157
     */
158
    public function listWorksheetInfo($pFilename)
159
    {
160
        File::assertFile($pFilename);
161
162
        $worksheetInfo = [];
163
164
        $xml = $this->trySimpleXMLLoadString($pFilename);
165
166
        $namespaces = $xml->getNamespaces(true);
167
168
        $worksheetID = 1;
169
        $xml_ss = $xml->children($namespaces['ss']);
170
        foreach ($xml_ss->Worksheet as $worksheet) {
171
            $worksheet_ss = $worksheet->attributes($namespaces['ss']);
172
173
            $tmpInfo = [];
174
            $tmpInfo['worksheetName'] = '';
175
            $tmpInfo['lastColumnLetter'] = 'A';
176
            $tmpInfo['lastColumnIndex'] = 0;
177
            $tmpInfo['totalRows'] = 0;
178
            $tmpInfo['totalColumns'] = 0;
179
180
            if (isset($worksheet_ss['Name'])) {
181
                $tmpInfo['worksheetName'] = (string) $worksheet_ss['Name'];
182
            } else {
183
                $tmpInfo['worksheetName'] = "Worksheet_{$worksheetID}";
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'] = Cell::stringFromColumnIndex($tmpInfo['lastColumnIndex']);
211
            $tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1;
212
213
            $worksheetInfo[] = $tmpInfo;
214
            ++$worksheetID;
215
        }
216
217
        return $worksheetInfo;
218
    }
219
220
    /**
221
     * Loads Spreadsheet from file.
222
     *
223
     * @param string $pFilename
224
     *
225
     * @throws Exception
226
     *
227
     * @return Spreadsheet
228
     */
229 2
    public function load($pFilename)
230
    {
231
        // Create new Spreadsheet
232 2
        $spreadsheet = new Spreadsheet();
233
        $spreadsheet->removeSheetByIndex(0);
234
235
        // Load into this instance
236
        return $this->loadIntoExisting($pFilename, $spreadsheet);
237
    }
238
239
    protected static function identifyFixedStyleValue($styleList, &$styleAttributeValue)
240
    {
241
        $styleAttributeValue = strtolower($styleAttributeValue);
242
        foreach ($styleList as $style) {
243
            if ($styleAttributeValue == strtolower($style)) {
244
                $styleAttributeValue = $style;
245
246
                return true;
247
            }
248
        }
249
250
        return false;
251
    }
252
253
    /**
254
     * pixel units to excel width units(units of 1/256th of a character width).
255
     *
256
     * @param pxs
257
     * @param mixed $pxs
258
     *
259
     * @return
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 widthUnits
275
     * @param mixed $widthUnits
276
     *
277
     * @return
278
     */
279
    protected static function widthUnits2Pixel($widthUnits)
280
    {
281
        $pixels = ($widthUnits / 256) * 7;
282
        $offsetWidthUnits = $widthUnits % 256;
283
        $pixels += round($offsetWidthUnits / (256 / 7));
284
285
        return $pixels;
286
    }
287
288
    protected static function hex2str($hex)
289
    {
290
        return chr(hexdec($hex[1]));
291
    }
292
293
    /**
294
     * Loads from file into Spreadsheet instance.
295
     *
296
     * @param string $pFilename
297
     * @param Spreadsheet $spreadsheet
298
     *
299
     * @throws Exception
300
     *
301
     * @return Spreadsheet
302
     */
303
    public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
304
    {
305
        $fromFormats = ['\-', '\ '];
306
        $toFormats = ['-', ' '];
307
308
        $underlineStyles = [
309
            Font::UNDERLINE_NONE,
310
            Font::UNDERLINE_DOUBLE,
311
            Font::UNDERLINE_DOUBLEACCOUNTING,
312
            Font::UNDERLINE_SINGLE,
313
            Font::UNDERLINE_SINGLEACCOUNTING,
314
        ];
315
        $verticalAlignmentStyles = [
316
            Alignment::VERTICAL_BOTTOM,
317
            Alignment::VERTICAL_TOP,
318
            Alignment::VERTICAL_CENTER,
319
            Alignment::VERTICAL_JUSTIFY,
320
        ];
321
        $horizontalAlignmentStyles = [
322
            Alignment::HORIZONTAL_GENERAL,
323
            Alignment::HORIZONTAL_LEFT,
324
            Alignment::HORIZONTAL_RIGHT,
325
            Alignment::HORIZONTAL_CENTER,
326
            Alignment::HORIZONTAL_CENTER_CONTINUOUS,
327
            Alignment::HORIZONTAL_JUSTIFY,
328
        ];
329
330
        $timezoneObj = new DateTimeZone('Europe/London');
0 ignored issues
show
Unused Code introduced by
$timezoneObj is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
331
        $GMT = new DateTimeZone('UTC');
0 ignored issues
show
Unused Code introduced by
$GMT is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
332
333
        File::assertFile($pFilename);
334
        if (!$this->canRead($pFilename)) {
335
            throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
336
        }
337
338
        $xml = $this->trySimpleXMLLoadString($pFilename);
339
340
        $namespaces = $xml->getNamespaces(true);
341
342
        $docProps = $spreadsheet->getProperties();
343
        if (isset($xml->DocumentProperties[0])) {
344
            foreach ($xml->DocumentProperties[0] as $propertyName => $propertyValue) {
345
                switch ($propertyName) {
346
                    case 'Title':
347
                        $docProps->setTitle(self::convertStringEncoding($propertyValue, $this->charSet));
348
349
                        break;
350
                    case 'Subject':
351
                        $docProps->setSubject(self::convertStringEncoding($propertyValue, $this->charSet));
352
353
                        break;
354
                    case 'Author':
355
                        $docProps->setCreator(self::convertStringEncoding($propertyValue, $this->charSet));
356
357
                        break;
358
                    case 'Created':
359
                        $creationDate = strtotime($propertyValue);
360
                        $docProps->setCreated($creationDate);
0 ignored issues
show
Documentation introduced by
$creationDate is of type integer, but the function expects a object<PhpOffice\PhpSpre...heet\Document\datetime>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
361
362
                        break;
363
                    case 'LastAuthor':
364
                        $docProps->setLastModifiedBy(self::convertStringEncoding($propertyValue, $this->charSet));
365
366
                        break;
367
                    case 'LastSaved':
368
                        $lastSaveDate = strtotime($propertyValue);
369
                        $docProps->setModified($lastSaveDate);
0 ignored issues
show
Documentation introduced by
$lastSaveDate is of type integer, but the function expects a object<PhpOffice\PhpSpre...heet\Document\datetime>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
370
371
                        break;
372
                    case 'Company':
373
                        $docProps->setCompany(self::convertStringEncoding($propertyValue, $this->charSet));
374
375
                        break;
376
                    case 'Category':
377
                        $docProps->setCategory(self::convertStringEncoding($propertyValue, $this->charSet));
378
379
                        break;
380
                    case 'Manager':
381
                        $docProps->setManager(self::convertStringEncoding($propertyValue, $this->charSet));
382
383
                        break;
384
                    case 'Keywords':
385
                        $docProps->setKeywords(self::convertStringEncoding($propertyValue, $this->charSet));
386
387
                        break;
388
                    case 'Description':
389
                        $docProps->setDescription(self::convertStringEncoding($propertyValue, $this->charSet));
390
391
                        break;
392
                }
393
            }
394
        }
395
        if (isset($xml->CustomDocumentProperties)) {
396
            foreach ($xml->CustomDocumentProperties[0] as $propertyName => $propertyValue) {
397
                $propertyAttributes = $propertyValue->attributes($namespaces['dt']);
398
                $propertyName = preg_replace_callback('/_x([0-9a-z]{4})_/', ['self', 'hex2str'], $propertyName);
399
                $propertyType = Properties::PROPERTY_TYPE_UNKNOWN;
400
                switch ((string) $propertyAttributes) {
401
                    case 'string':
402
                        $propertyType = Properties::PROPERTY_TYPE_STRING;
403
                        $propertyValue = trim($propertyValue);
404
405
                        break;
406
                    case 'boolean':
407
                        $propertyType = Properties::PROPERTY_TYPE_BOOLEAN;
408
                        $propertyValue = (bool) $propertyValue;
409
410
                        break;
411
                    case 'integer':
412
                        $propertyType = Properties::PROPERTY_TYPE_INTEGER;
413
                        $propertyValue = (int) $propertyValue;
414
415
                        break;
416
                    case 'float':
417
                        $propertyType = Properties::PROPERTY_TYPE_FLOAT;
418
                        $propertyValue = (float) $propertyValue;
419
420
                        break;
421
                    case 'dateTime.tz':
422
                        $propertyType = Properties::PROPERTY_TYPE_DATE;
423
                        $propertyValue = strtotime(trim($propertyValue));
424
425
                        break;
426
                }
427
                $docProps->setCustomProperty($propertyName, $propertyValue, $propertyType);
428
            }
429
        }
430
431
        foreach ($xml->Styles[0] as $style) {
432
            $style_ss = $style->attributes($namespaces['ss']);
433
            $styleID = (string) $style_ss['ID'];
434
            $this->styles[$styleID] = (isset($this->styles['Default'])) ? $this->styles['Default'] : [];
435
            foreach ($style as $styleType => $styleData) {
436
                $styleAttributes = $styleData->attributes($namespaces['ss']);
437
                switch ($styleType) {
438
                    case 'Alignment':
439
                        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
440
                            $styleAttributeValue = (string) $styleAttributeValue;
441
                            switch ($styleAttributeKey) {
442
                                case 'Vertical':
443
                                    if (self::identifyFixedStyleValue($verticalAlignmentStyles, $styleAttributeValue)) {
444
                                        $this->styles[$styleID]['alignment']['vertical'] = $styleAttributeValue;
445
                                    }
446
447
                                    break;
448
                                case 'Horizontal':
449
                                    if (self::identifyFixedStyleValue($horizontalAlignmentStyles, $styleAttributeValue)) {
450
                                        $this->styles[$styleID]['alignment']['horizontal'] = $styleAttributeValue;
451
                                    }
452
453
                                    break;
454
                                case 'WrapText':
455
                                    $this->styles[$styleID]['alignment']['wrapText'] = true;
456
457
                                    break;
458
                            }
459
                        }
460
461
                        break;
462
                    case 'Borders':
463
                        foreach ($styleData->Border as $borderStyle) {
464
                            $borderAttributes = $borderStyle->attributes($namespaces['ss']);
465
                            $thisBorder = [];
466
                            foreach ($borderAttributes as $borderStyleKey => $borderStyleValue) {
467
                                switch ($borderStyleKey) {
468
                                    case 'LineStyle':
469
                                        $thisBorder['borderStyle'] = Border::BORDER_MEDIUM;
470
471
                                        break;
472
                                    case 'Weight':
473
                                        break;
474
                                    case 'Position':
475
                                        $borderPosition = strtolower($borderStyleValue);
476
477
                                        break;
478
                                    case 'Color':
479
                                        $borderColour = substr($borderStyleValue, 1);
480
                                        $thisBorder['color']['rgb'] = $borderColour;
481
482
                                        break;
483
                                }
484
                            }
485
                            if (!empty($thisBorder)) {
486
                                if (($borderPosition == 'left') || ($borderPosition == 'right') || ($borderPosition == 'top') || ($borderPosition == 'bottom')) {
487
                                    $this->styles[$styleID]['borders'][$borderPosition] = $thisBorder;
0 ignored issues
show
Bug introduced by
The variable $borderPosition does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
488
                                }
489
                            }
490
                        }
491
492
                        break;
493
                    case 'Font':
494
                        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
495
                            $styleAttributeValue = (string) $styleAttributeValue;
496
                            switch ($styleAttributeKey) {
497
                                case 'FontName':
498
                                    $this->styles[$styleID]['font']['name'] = $styleAttributeValue;
499
500
                                    break;
501
                                case 'Size':
502
                                    $this->styles[$styleID]['font']['size'] = $styleAttributeValue;
503
504
                                    break;
505 View Code Duplication
                                case 'Color':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
506
                                    $this->styles[$styleID]['font']['color']['rgb'] = substr($styleAttributeValue, 1);
507
508
                                    break;
509
                                case 'Bold':
510
                                    $this->styles[$styleID]['font']['bold'] = true;
511
512
                                    break;
513
                                case 'Italic':
514
                                    $this->styles[$styleID]['font']['italic'] = true;
515
516
                                    break;
517
                                case 'Underline':
518
                                    if (self::identifyFixedStyleValue($underlineStyles, $styleAttributeValue)) {
519
                                        $this->styles[$styleID]['font']['underline'] = $styleAttributeValue;
520
                                    }
521
522
                                    break;
523
                            }
524
                        }
525
526
                        break;
527
                    case 'Interior':
528
                        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
529
                            switch ($styleAttributeKey) {
530 View Code Duplication
                                case 'Color':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
531
                                    $this->styles[$styleID]['fill']['color']['rgb'] = substr($styleAttributeValue, 1);
532
533
                                    break;
534
                                case 'Pattern':
535
                                    $this->styles[$styleID]['fill']['fillType'] = strtolower($styleAttributeValue);
536
537
                                    break;
538
                            }
539
                        }
540
541
                        break;
542
                    case 'NumberFormat':
543
                        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
544
                            $styleAttributeValue = str_replace($fromFormats, $toFormats, $styleAttributeValue);
545
                            switch ($styleAttributeValue) {
546
                                case 'Short Date':
547
                                    $styleAttributeValue = 'dd/mm/yyyy';
548
549
                                    break;
550
                            }
551
                            if ($styleAttributeValue > '') {
552
                                $this->styles[$styleID]['numberFormat']['formatCode'] = $styleAttributeValue;
553
                            }
554
                        }
555
556
                        break;
557
                    case 'Protection':
558
                        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
0 ignored issues
show
Unused Code introduced by
This foreach statement is empty and can be removed.

This check looks for foreach loops that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

Consider removing the loop.

Loading history...
559
                        }
560
561
                        break;
562
                }
563
            }
564
        }
565
566
        $worksheetID = 0;
567
        $xml_ss = $xml->children($namespaces['ss']);
568
569
        foreach ($xml_ss->Worksheet as $worksheet) {
570
            $worksheet_ss = $worksheet->attributes($namespaces['ss']);
571
572
            if ((isset($this->loadSheetsOnly)) && (isset($worksheet_ss['Name'])) &&
573
                (!in_array($worksheet_ss['Name'], $this->loadSheetsOnly))) {
574
                continue;
575
            }
576
577
            // Create new Worksheet
578
            $spreadsheet->createSheet();
579
            $spreadsheet->setActiveSheetIndex($worksheetID);
580
            if (isset($worksheet_ss['Name'])) {
581
                $worksheetName = self::convertStringEncoding((string) $worksheet_ss['Name'], $this->charSet);
582
                //    Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
583
                //        formula cells... during the load, all formulae should be correct, and we're simply bringing
584
                //        the worksheet name in line with the formula, not the reverse
585
                $spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
586
            }
587
588
            $columnID = 'A';
589
            if (isset($worksheet->Table->Column)) {
590
                foreach ($worksheet->Table->Column as $columnData) {
591
                    $columnData_ss = $columnData->attributes($namespaces['ss']);
592
                    if (isset($columnData_ss['Index'])) {
593
                        $columnID = Cell::stringFromColumnIndex($columnData_ss['Index'] - 1);
594
                    }
595
                    if (isset($columnData_ss['Width'])) {
596
                        $columnWidth = $columnData_ss['Width'];
597
                        $spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setWidth($columnWidth / 5.4);
598
                    }
599
                    ++$columnID;
600
                }
601
            }
602
603
            $rowID = 1;
604
            if (isset($worksheet->Table->Row)) {
605
                $additionalMergedCells = 0;
606
                foreach ($worksheet->Table->Row as $rowData) {
607
                    $rowHasData = false;
608
                    $row_ss = $rowData->attributes($namespaces['ss']);
609
                    if (isset($row_ss['Index'])) {
610
                        $rowID = (int) $row_ss['Index'];
611
                    }
612
613
                    $columnID = 'A';
614
                    foreach ($rowData->Cell as $cell) {
615
                        $cell_ss = $cell->attributes($namespaces['ss']);
616
                        if (isset($cell_ss['Index'])) {
617
                            $columnID = Cell::stringFromColumnIndex($cell_ss['Index'] - 1);
618
                        }
619
                        $cellRange = $columnID . $rowID;
620
621
                        if ($this->getReadFilter() !== null) {
622
                            if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
0 ignored issues
show
Bug introduced by
The variable $worksheetName does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
623
                                ++$columnID;
624
625
                                continue;
626
                            }
627
                        }
628
629
                        if (isset($cell_ss['HRef'])) {
630
                            $spreadsheet->getActiveSheet()->getCell($cellRange)->getHyperlink()->setUrl($cell_ss['HRef']);
631
                        }
632
633
                        if ((isset($cell_ss['MergeAcross'])) || (isset($cell_ss['MergeDown']))) {
634
                            $columnTo = $columnID;
635
                            if (isset($cell_ss['MergeAcross'])) {
636
                                $additionalMergedCells += (int) $cell_ss['MergeAcross'];
637
                                $columnTo = Cell::stringFromColumnIndex(Cell::columnIndexFromString($columnID) + $cell_ss['MergeAcross'] - 1);
638
                            }
639
                            $rowTo = $rowID;
640
                            if (isset($cell_ss['MergeDown'])) {
641
                                $rowTo = $rowTo + $cell_ss['MergeDown'];
642
                            }
643
                            $cellRange .= ':' . $columnTo . $rowTo;
644
                            $spreadsheet->getActiveSheet()->mergeCells($cellRange);
645
                        }
646
647
                        $cellIsSet = $hasCalculatedValue = false;
648
                        $cellDataFormula = '';
649
                        if (isset($cell_ss['Formula'])) {
650
                            $cellDataFormula = $cell_ss['Formula'];
651
                            // added this as a check for array formulas
652
                            if (isset($cell_ss['ArrayRange'])) {
653
                                $cellDataCSEFormula = $cell_ss['ArrayRange'];
0 ignored issues
show
Unused Code introduced by
$cellDataCSEFormula is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
654
                            }
655
                            $hasCalculatedValue = true;
656
                        }
657
                        if (isset($cell->Data)) {
658
                            $cellValue = $cellData = $cell->Data;
659
                            $type = Cell\DataType::TYPE_NULL;
660
                            $cellData_ss = $cellData->attributes($namespaces['ss']);
661
                            if (isset($cellData_ss['Type'])) {
662
                                $cellDataType = $cellData_ss['Type'];
663
                                switch ($cellDataType) {
664
                                    /*
665
                                    const TYPE_STRING        = 's';
666
                                    const TYPE_FORMULA        = 'f';
667
                                    const TYPE_NUMERIC        = 'n';
668
                                    const TYPE_BOOL            = 'b';
669
                                    const TYPE_NULL            = 'null';
670
                                    const TYPE_INLINE        = 'inlineStr';
671
                                    const TYPE_ERROR        = 'e';
672
                                    */
673
                                    case 'String':
674
                                        $cellValue = self::convertStringEncoding($cellValue, $this->charSet);
675
                                        $type = Cell\DataType::TYPE_STRING;
676
677
                                        break;
678
                                    case 'Number':
679
                                        $type = Cell\DataType::TYPE_NUMERIC;
680
                                        $cellValue = (float) $cellValue;
681
                                        if (floor($cellValue) == $cellValue) {
682
                                            $cellValue = (int) $cellValue;
683
                                        }
684
685
                                        break;
686
                                    case 'Boolean':
687
                                        $type = Cell\DataType::TYPE_BOOL;
688
                                        $cellValue = ($cellValue != 0);
689
690
                                        break;
691
                                    case 'DateTime':
692
                                        $type = Cell\DataType::TYPE_NUMERIC;
693
                                        $cellValue = Date::PHPToExcel(strtotime($cellValue));
694
695
                                        break;
696
                                    case 'Error':
697
                                        $type = Cell\DataType::TYPE_ERROR;
698
699
                                        break;
700
                                }
701
                            }
702
703
                            if ($hasCalculatedValue) {
704
                                $type = Cell\DataType::TYPE_FORMULA;
705
                                $columnNumber = Cell::columnIndexFromString($columnID);
706
                                if (substr($cellDataFormula, 0, 3) == 'of:') {
707
                                    $cellDataFormula = substr($cellDataFormula, 3);
708
                                    $temp = explode('"', $cellDataFormula);
709
                                    $key = false;
710
                                    foreach ($temp as &$value) {
711
                                        //    Only replace in alternate array entries (i.e. non-quoted blocks)
712
                                        if ($key = !$key) {
713
                                            $value = str_replace(['[.', '.', ']'], '', $value);
714
                                        }
715
                                    }
716
                                } else {
717
                                    //    Convert R1C1 style references to A1 style references (but only when not quoted)
718
                                    $temp = explode('"', $cellDataFormula);
719
                                    $key = false;
720
                                    foreach ($temp as &$value) {
721
                                        //    Only replace in alternate array entries (i.e. non-quoted blocks)
722
                                        if ($key = !$key) {
723
                                            preg_match_all('/(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))/', $value, $cellReferences, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
724
                                            //    Reverse the matches array, otherwise all our offsets will become incorrect if we modify our way
725
                                            //        through the formula from left to right. Reversing means that we work right to left.through
726
                                            //        the formula
727
                                            $cellReferences = array_reverse($cellReferences);
728
                                            //    Loop through each R1C1 style reference in turn, converting it to its A1 style equivalent,
729
                                            //        then modify the formula to use that new reference
730
                                            foreach ($cellReferences as $cellReference) {
731
                                                $rowReference = $cellReference[2][0];
732
                                                //    Empty R reference is the current row
733
                                                if ($rowReference == '') {
734
                                                    $rowReference = $rowID;
735
                                                }
736
                                                //    Bracketed R references are relative to the current row
737
                                                if ($rowReference[0] == '[') {
738
                                                    $rowReference = $rowID + trim($rowReference, '[]');
739
                                                }
740
                                                $columnReference = $cellReference[4][0];
741
                                                //    Empty C reference is the current column
742
                                                if ($columnReference == '') {
743
                                                    $columnReference = $columnNumber;
744
                                                }
745
                                                //    Bracketed C references are relative to the current column
746
                                                if ($columnReference[0] == '[') {
747
                                                    $columnReference = $columnNumber + trim($columnReference, '[]');
748
                                                }
749
                                                $A1CellReference = Cell::stringFromColumnIndex($columnReference - 1) . $rowReference;
750
                                                $value = substr_replace($value, $A1CellReference, $cellReference[0][1], strlen($cellReference[0][0]));
751
                                            }
752
                                        }
753
                                    }
754
                                }
755
                                unset($value);
756
                                //    Then rebuild the formula string
757
                                $cellDataFormula = implode('"', $temp);
758
                            }
759
760
                            $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValueExplicit((($hasCalculatedValue) ? $cellDataFormula : $cellValue), $type);
761
                            if ($hasCalculatedValue) {
762
                                $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setCalculatedValue($cellValue);
763
                            }
764
                            $cellIsSet = $rowHasData = true;
765
                        }
766
767
                        if (isset($cell->Comment)) {
768
                            $commentAttributes = $cell->Comment->attributes($namespaces['ss']);
769
                            $author = 'unknown';
770
                            if (isset($commentAttributes->Author)) {
771
                                $author = (string) $commentAttributes->Author;
772
                            }
773
                            $node = $cell->Comment->Data->asXML();
774
                            $annotation = strip_tags($node);
775
                            $spreadsheet->getActiveSheet()->getComment($columnID . $rowID)->setAuthor(self::convertStringEncoding($author, $this->charSet))->setText($this->parseRichText($annotation));
776
                        }
777
778
                        if (($cellIsSet) && (isset($cell_ss['StyleID']))) {
779
                            $style = (string) $cell_ss['StyleID'];
780
                            if ((isset($this->styles[$style])) && (!empty($this->styles[$style]))) {
781
                                if (!$spreadsheet->getActiveSheet()->cellExists($columnID . $rowID)) {
782
                                    $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValue(null);
783
                                }
784
                                $spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($this->styles[$style]);
785
                            }
786
                        }
787
                        ++$columnID;
788
                        while ($additionalMergedCells > 0) {
789
                            ++$columnID;
790
                            --$additionalMergedCells;
791
                        }
792
                    }
793
794
                    if ($rowHasData) {
795
                        if (isset($row_ss['StyleID'])) {
796
                            $rowStyle = $row_ss['StyleID'];
0 ignored issues
show
Unused Code introduced by
$rowStyle is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
797
                        }
798
                        if (isset($row_ss['Height'])) {
799
                            $rowHeight = $row_ss['Height'];
800
                            $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setRowHeight($rowHeight);
801
                        }
802
                    }
803
804
                    ++$rowID;
805
                }
806
            }
807
            ++$worksheetID;
808
        }
809
810
        // Return
811
        return $spreadsheet;
812
    }
813
814
    protected static function convertStringEncoding($string, $charset)
815
    {
816
        if ($charset != 'UTF-8') {
817
            return StringHelper::convertEncoding($string, 'UTF-8', $charset);
818
        }
819
820
        return $string;
821
    }
822
823
    protected function parseRichText($is)
824
    {
825
        $value = new RichText();
826
827
        $value->createText(self::convertStringEncoding($is, $this->charSet));
828
829
        return $value;
830
    }
831
}
832