Completed
Push — develop ( 782b4e...557e80 )
by Adrien
43:38
created

Xml::hex2str()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 0
cts 2
cp 0
crap 2
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Reader;
4
5
use PhpOffice\PhpSpreadsheet\Cell\Cell;
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
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 6
    public function __construct()
41
    {
42 6
        $this->readFilter = new DefaultReadFilter();
43 6
    }
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 4
    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 4
                '<?xml version="1.0"',
68
                '<?mso-application progid="Excel.Sheet"?>',
69
            ];
70
71
        // Open file
72 4
        $this->openFile($pFilename);
73 4
        $fileHandle = $this->fileHandle;
74
75
        // Read sample data (first 2 KB will do)
76 4
        $data = fread($fileHandle, 2048);
77 4
        fclose($fileHandle);
78 4
        $data = strtr($data, "'", '"'); // fix headers with single quote
79
80 4
        $valid = true;
81 4
        foreach ($signature as $match) {
82
            // every part of the signature must be present
83 4
            if (strpos($data, $match) === false) {
84
                $valid = false;
85
86 4
                break;
87
            }
88
        }
89
90
        //    Retrieve charset encoding
91 4
        if (preg_match('/<?xml.*encoding=[\'"](.*?)[\'"].*?>/um', $data, $matches)) {
92 4
            $this->charSet = strtoupper($matches[1]);
93
        }
94
95 4
        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 3
    public function trySimpleXMLLoadString($pFilename)
108
    {
109
        try {
110 3
            $xml = simplexml_load_string(
111 3
                $this->securityScan(file_get_contents($pFilename)),
112 3
                'SimpleXMLElement',
113 3
                Settings::getLibXmlLoaderOptions()
114
            );
115 1
        } catch (\Exception $e) {
116 1
            throw new Exception('Cannot load invalid XML file: ' . $pFilename, 0, $e);
117
        }
118
119 2
        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 2
        $spreadsheet->removeSheetByIndex(0);
234
235
        // Load into this instance
236 2
        return $this->loadIntoExisting($pFilename, $spreadsheet);
237
    }
238
239 2
    private static function identifyFixedStyleValue($styleList, &$styleAttributeValue)
240
    {
241 2
        $styleAttributeValue = strtolower($styleAttributeValue);
242 2
        foreach ($styleList as $style) {
243 2
            if ($styleAttributeValue == strtolower($style)) {
244 2
                $styleAttributeValue = $style;
245
246 2
                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 2
    public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
304
    {
305 2
        $fromFormats = ['\-', '\ '];
306 2
        $toFormats = ['-', ' '];
307
308
        $underlineStyles = [
309 2
            Font::UNDERLINE_NONE,
310 1
            Font::UNDERLINE_DOUBLE,
311 1
            Font::UNDERLINE_DOUBLEACCOUNTING,
312 1
            Font::UNDERLINE_SINGLE,
313 1
            Font::UNDERLINE_SINGLEACCOUNTING,
314
        ];
315
        $verticalAlignmentStyles = [
316 2
            Alignment::VERTICAL_BOTTOM,
317 1
            Alignment::VERTICAL_TOP,
318 1
            Alignment::VERTICAL_CENTER,
319 1
            Alignment::VERTICAL_JUSTIFY,
320
        ];
321
        $horizontalAlignmentStyles = [
322 2
            Alignment::HORIZONTAL_GENERAL,
323 1
            Alignment::HORIZONTAL_LEFT,
324 1
            Alignment::HORIZONTAL_RIGHT,
325 1
            Alignment::HORIZONTAL_CENTER,
326 1
            Alignment::HORIZONTAL_CENTER_CONTINUOUS,
327 1
            Alignment::HORIZONTAL_JUSTIFY,
328
        ];
329
330 2
        File::assertFile($pFilename);
331 2
        if (!$this->canRead($pFilename)) {
332
            throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
333
        }
334
335 2
        $xml = $this->trySimpleXMLLoadString($pFilename);
336
337 2
        $namespaces = $xml->getNamespaces(true);
338
339 2
        $docProps = $spreadsheet->getProperties();
340 2
        if (isset($xml->DocumentProperties[0])) {
341
            foreach ($xml->DocumentProperties[0] as $propertyName => $propertyValue) {
342
                switch ($propertyName) {
343
                    case 'Title':
344
                        $docProps->setTitle(self::convertStringEncoding($propertyValue, $this->charSet));
345
346
                        break;
347
                    case 'Subject':
348
                        $docProps->setSubject(self::convertStringEncoding($propertyValue, $this->charSet));
349
350
                        break;
351
                    case 'Author':
352
                        $docProps->setCreator(self::convertStringEncoding($propertyValue, $this->charSet));
353
354
                        break;
355
                    case 'Created':
356
                        $creationDate = strtotime($propertyValue);
357
                        $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...
358
359
                        break;
360
                    case 'LastAuthor':
361
                        $docProps->setLastModifiedBy(self::convertStringEncoding($propertyValue, $this->charSet));
362
363
                        break;
364
                    case 'LastSaved':
365
                        $lastSaveDate = strtotime($propertyValue);
366
                        $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...
367
368
                        break;
369
                    case 'Company':
370
                        $docProps->setCompany(self::convertStringEncoding($propertyValue, $this->charSet));
371
372
                        break;
373
                    case 'Category':
374
                        $docProps->setCategory(self::convertStringEncoding($propertyValue, $this->charSet));
375
376
                        break;
377
                    case 'Manager':
378
                        $docProps->setManager(self::convertStringEncoding($propertyValue, $this->charSet));
379
380
                        break;
381
                    case 'Keywords':
382
                        $docProps->setKeywords(self::convertStringEncoding($propertyValue, $this->charSet));
383
384
                        break;
385
                    case 'Description':
386
                        $docProps->setDescription(self::convertStringEncoding($propertyValue, $this->charSet));
387
388
                        break;
389
                }
390
            }
391
        }
392 2
        if (isset($xml->CustomDocumentProperties)) {
393
            foreach ($xml->CustomDocumentProperties[0] as $propertyName => $propertyValue) {
394
                $propertyAttributes = $propertyValue->attributes($namespaces['dt']);
395
                $propertyName = preg_replace_callback('/_x([0-9a-z]{4})_/', ['self', 'hex2str'], $propertyName);
396
                $propertyType = Properties::PROPERTY_TYPE_UNKNOWN;
397
                switch ((string) $propertyAttributes) {
398
                    case 'string':
399
                        $propertyType = Properties::PROPERTY_TYPE_STRING;
400
                        $propertyValue = trim($propertyValue);
401
402
                        break;
403
                    case 'boolean':
404
                        $propertyType = Properties::PROPERTY_TYPE_BOOLEAN;
405
                        $propertyValue = (bool) $propertyValue;
406
407
                        break;
408
                    case 'integer':
409
                        $propertyType = Properties::PROPERTY_TYPE_INTEGER;
410
                        $propertyValue = (int) $propertyValue;
411
412
                        break;
413
                    case 'float':
414
                        $propertyType = Properties::PROPERTY_TYPE_FLOAT;
415
                        $propertyValue = (float) $propertyValue;
416
417
                        break;
418
                    case 'dateTime.tz':
419
                        $propertyType = Properties::PROPERTY_TYPE_DATE;
420
                        $propertyValue = strtotime(trim($propertyValue));
421
422
                        break;
423
                }
424
                $docProps->setCustomProperty($propertyName, $propertyValue, $propertyType);
425
            }
426
        }
427
428 2
        foreach ($xml->Styles[0] as $style) {
429 2
            $style_ss = $style->attributes($namespaces['ss']);
430 2
            $styleID = (string) $style_ss['ID'];
431 2
            $this->styles[$styleID] = (isset($this->styles['Default'])) ? $this->styles['Default'] : [];
432 2
            foreach ($style as $styleType => $styleData) {
433 2
                $styleAttributes = $styleData->attributes($namespaces['ss']);
434
                switch ($styleType) {
435 2
                    case 'Alignment':
436 2
                        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
437 2
                            $styleAttributeValue = (string) $styleAttributeValue;
438
                            switch ($styleAttributeKey) {
439 2
                                case 'Vertical':
440 2
                                    if (self::identifyFixedStyleValue($verticalAlignmentStyles, $styleAttributeValue)) {
441 2
                                        $this->styles[$styleID]['alignment']['vertical'] = $styleAttributeValue;
442
                                    }
443
444 2
                                    break;
445 2
                                case 'Horizontal':
446 2
                                    if (self::identifyFixedStyleValue($horizontalAlignmentStyles, $styleAttributeValue)) {
447 2
                                        $this->styles[$styleID]['alignment']['horizontal'] = $styleAttributeValue;
448
                                    }
449
450 2
                                    break;
451 2
                                case 'WrapText':
452 2
                                    $this->styles[$styleID]['alignment']['wrapText'] = true;
453
454 2
                                    break;
455
                            }
456
                        }
457
458 2
                        break;
459 2
                    case 'Borders':
460 2
                        foreach ($styleData->Border as $borderStyle) {
461 2
                            $borderAttributes = $borderStyle->attributes($namespaces['ss']);
462 2
                            $thisBorder = [];
463 2
                            foreach ($borderAttributes as $borderStyleKey => $borderStyleValue) {
464
                                switch ($borderStyleKey) {
465 2
                                    case 'LineStyle':
466 2
                                        $thisBorder['borderStyle'] = Border::BORDER_MEDIUM;
467
468 2
                                        break;
469 2
                                    case 'Weight':
470 2
                                        break;
471 2
                                    case 'Position':
472 2
                                        $borderPosition = strtolower($borderStyleValue);
473
474 2
                                        break;
475 2
                                    case 'Color':
476 2
                                        $borderColour = substr($borderStyleValue, 1);
477 2
                                        $thisBorder['color']['rgb'] = $borderColour;
478
479 2
                                        break;
480
                                }
481
                            }
482 2
                            if (!empty($thisBorder)) {
483 2
                                if (($borderPosition == 'left') || ($borderPosition == 'right') || ($borderPosition == 'top') || ($borderPosition == 'bottom')) {
484 2
                                    $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...
485
                                }
486
                            }
487
                        }
488
489 2
                        break;
490 2
                    case 'Font':
491 2
                        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
492 2
                            $styleAttributeValue = (string) $styleAttributeValue;
493
                            switch ($styleAttributeKey) {
494 2
                                case 'FontName':
495 2
                                    $this->styles[$styleID]['font']['name'] = $styleAttributeValue;
496
497 2
                                    break;
498 2
                                case 'Size':
499 2
                                    $this->styles[$styleID]['font']['size'] = $styleAttributeValue;
500
501 2
                                    break;
502 2 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...
503 2
                                    $this->styles[$styleID]['font']['color']['rgb'] = substr($styleAttributeValue, 1);
504
505 2
                                    break;
506 2
                                case 'Bold':
507 2
                                    $this->styles[$styleID]['font']['bold'] = true;
508
509 2
                                    break;
510 2
                                case 'Italic':
511 2
                                    $this->styles[$styleID]['font']['italic'] = true;
512
513 2
                                    break;
514 2
                                case 'Underline':
515 2
                                    if (self::identifyFixedStyleValue($underlineStyles, $styleAttributeValue)) {
516 2
                                        $this->styles[$styleID]['font']['underline'] = $styleAttributeValue;
517
                                    }
518
519 2
                                    break;
520
                            }
521
                        }
522
523 2
                        break;
524 2
                    case 'Interior':
525 2
                        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
526
                            switch ($styleAttributeKey) {
527 2 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...
528 2
                                    $this->styles[$styleID]['fill']['color']['rgb'] = substr($styleAttributeValue, 1);
529
530 2
                                    break;
531 2
                                case 'Pattern':
532 2
                                    $this->styles[$styleID]['fill']['fillType'] = strtolower($styleAttributeValue);
533
534 2
                                    break;
535
                            }
536
                        }
537
538 2
                        break;
539 2
                    case 'NumberFormat':
540 2
                        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
541 2
                            $styleAttributeValue = str_replace($fromFormats, $toFormats, $styleAttributeValue);
542
                            switch ($styleAttributeValue) {
543 2
                                case 'Short Date':
544 2
                                    $styleAttributeValue = 'dd/mm/yyyy';
545
546 2
                                    break;
547
                            }
548 2
                            if ($styleAttributeValue > '') {
549 2
                                $this->styles[$styleID]['numberFormat']['formatCode'] = $styleAttributeValue;
550
                            }
551
                        }
552
553 2
                        break;
554
                    case 'Protection':
555
                        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...
556
                        }
557
558 2
                        break;
559
                }
560
            }
561
        }
562
563 2
        $worksheetID = 0;
564 2
        $xml_ss = $xml->children($namespaces['ss']);
565
566 2
        foreach ($xml_ss->Worksheet as $worksheet) {
567 2
            $worksheet_ss = $worksheet->attributes($namespaces['ss']);
568
569 2
            if ((isset($this->loadSheetsOnly)) && (isset($worksheet_ss['Name'])) &&
570 2
                (!in_array($worksheet_ss['Name'], $this->loadSheetsOnly))) {
571
                continue;
572
            }
573
574
            // Create new Worksheet
575 2
            $spreadsheet->createSheet();
576 2
            $spreadsheet->setActiveSheetIndex($worksheetID);
577 2
            if (isset($worksheet_ss['Name'])) {
578 2
                $worksheetName = self::convertStringEncoding((string) $worksheet_ss['Name'], $this->charSet);
579
                //    Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
580
                //        formula cells... during the load, all formulae should be correct, and we're simply bringing
581
                //        the worksheet name in line with the formula, not the reverse
582 2
                $spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
583
            }
584
585 2
            $columnID = 'A';
586 2
            if (isset($worksheet->Table->Column)) {
587 2
                foreach ($worksheet->Table->Column as $columnData) {
588 2
                    $columnData_ss = $columnData->attributes($namespaces['ss']);
589 2
                    if (isset($columnData_ss['Index'])) {
590 2
                        $columnID = Cell::stringFromColumnIndex($columnData_ss['Index'] - 1);
591
                    }
592 2
                    if (isset($columnData_ss['Width'])) {
593 2
                        $columnWidth = $columnData_ss['Width'];
594 2
                        $spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setWidth($columnWidth / 5.4);
595
                    }
596 2
                    ++$columnID;
597
                }
598
            }
599
600 2
            $rowID = 1;
601 2
            if (isset($worksheet->Table->Row)) {
602 2
                $additionalMergedCells = 0;
603 2
                foreach ($worksheet->Table->Row as $rowData) {
604 2
                    $rowHasData = false;
605 2
                    $row_ss = $rowData->attributes($namespaces['ss']);
606 2
                    if (isset($row_ss['Index'])) {
607 2
                        $rowID = (int) $row_ss['Index'];
608
                    }
609
610 2
                    $columnID = 'A';
611 2
                    foreach ($rowData->Cell as $cell) {
612 2
                        $cell_ss = $cell->attributes($namespaces['ss']);
613 2
                        if (isset($cell_ss['Index'])) {
614 2
                            $columnID = Cell::stringFromColumnIndex($cell_ss['Index'] - 1);
615
                        }
616 2
                        $cellRange = $columnID . $rowID;
617
618 2 View Code Duplication
                        if ($this->getReadFilter() !== null) {
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...
619 2
                            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...
620
                                ++$columnID;
621
622
                                continue;
623
                            }
624
                        }
625
626 2
                        if (isset($cell_ss['HRef'])) {
627 2
                            $spreadsheet->getActiveSheet()->getCell($cellRange)->getHyperlink()->setUrl($cell_ss['HRef']);
628
                        }
629
630 2
                        if ((isset($cell_ss['MergeAcross'])) || (isset($cell_ss['MergeDown']))) {
631 2
                            $columnTo = $columnID;
632 2
                            if (isset($cell_ss['MergeAcross'])) {
633 2
                                $additionalMergedCells += (int) $cell_ss['MergeAcross'];
634 2
                                $columnTo = Cell::stringFromColumnIndex(Cell::columnIndexFromString($columnID) + $cell_ss['MergeAcross'] - 1);
635
                            }
636 2
                            $rowTo = $rowID;
637 2
                            if (isset($cell_ss['MergeDown'])) {
638 2
                                $rowTo = $rowTo + $cell_ss['MergeDown'];
639
                            }
640 2
                            $cellRange .= ':' . $columnTo . $rowTo;
641 2
                            $spreadsheet->getActiveSheet()->mergeCells($cellRange);
642
                        }
643
644 2
                        $cellIsSet = $hasCalculatedValue = false;
645 2
                        $cellDataFormula = '';
646 2
                        if (isset($cell_ss['Formula'])) {
647 2
                            $cellDataFormula = $cell_ss['Formula'];
648 2
                            $hasCalculatedValue = true;
649
                        }
650 2
                        if (isset($cell->Data)) {
651 2
                            $cellValue = $cellData = $cell->Data;
652 2
                            $type = DataType::TYPE_NULL;
653 2
                            $cellData_ss = $cellData->attributes($namespaces['ss']);
654 2
                            if (isset($cellData_ss['Type'])) {
655 2
                                $cellDataType = $cellData_ss['Type'];
656
                                switch ($cellDataType) {
657
                                    /*
658
                                    const TYPE_STRING        = 's';
659
                                    const TYPE_FORMULA        = 'f';
660
                                    const TYPE_NUMERIC        = 'n';
661
                                    const TYPE_BOOL            = 'b';
662
                                    const TYPE_NULL            = 'null';
663
                                    const TYPE_INLINE        = 'inlineStr';
664
                                    const TYPE_ERROR        = 'e';
665
                                    */
666 2
                                    case 'String':
667 2
                                        $cellValue = self::convertStringEncoding($cellValue, $this->charSet);
668 2
                                        $type = DataType::TYPE_STRING;
669
670 2
                                        break;
671 2
                                    case 'Number':
672 2
                                        $type = DataType::TYPE_NUMERIC;
673 2
                                        $cellValue = (float) $cellValue;
674 2
                                        if (floor($cellValue) == $cellValue) {
675 2
                                            $cellValue = (int) $cellValue;
676
                                        }
677
678 2
                                        break;
679 2
                                    case 'Boolean':
680 2
                                        $type = DataType::TYPE_BOOL;
681 2
                                        $cellValue = ($cellValue != 0);
682
683 2
                                        break;
684 2
                                    case 'DateTime':
685 2
                                        $type = DataType::TYPE_NUMERIC;
686 2
                                        $cellValue = Date::PHPToExcel(strtotime($cellValue));
687
688 2
                                        break;
689
                                    case 'Error':
690
                                        $type = DataType::TYPE_ERROR;
691
692
                                        break;
693
                                }
694
                            }
695
696 2
                            if ($hasCalculatedValue) {
697 2
                                $type = DataType::TYPE_FORMULA;
698 2
                                $columnNumber = Cell::columnIndexFromString($columnID);
699 2
                                if (substr($cellDataFormula, 0, 3) == 'of:') {
700 2
                                    $cellDataFormula = substr($cellDataFormula, 3);
701 2
                                    $temp = explode('"', $cellDataFormula);
702 2
                                    $key = false;
703 2
                                    foreach ($temp as &$value) {
704
                                        //    Only replace in alternate array entries (i.e. non-quoted blocks)
705 2
                                        if ($key = !$key) {
706 2
                                            $value = str_replace(['[.', '.', ']'], '', $value);
707
                                        }
708
                                    }
709
                                } else {
710
                                    //    Convert R1C1 style references to A1 style references (but only when not quoted)
711
                                    $temp = explode('"', $cellDataFormula);
712
                                    $key = false;
713 View Code Duplication
                                    foreach ($temp as &$value) {
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...
714
                                        //    Only replace in alternate array entries (i.e. non-quoted blocks)
715
                                        if ($key = !$key) {
716
                                            preg_match_all('/(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))/', $value, $cellReferences, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
717
                                            //    Reverse the matches array, otherwise all our offsets will become incorrect if we modify our way
718
                                            //        through the formula from left to right. Reversing means that we work right to left.through
719
                                            //        the formula
720
                                            $cellReferences = array_reverse($cellReferences);
721
                                            //    Loop through each R1C1 style reference in turn, converting it to its A1 style equivalent,
722
                                            //        then modify the formula to use that new reference
723
                                            foreach ($cellReferences as $cellReference) {
724
                                                $rowReference = $cellReference[2][0];
725
                                                //    Empty R reference is the current row
726
                                                if ($rowReference == '') {
727
                                                    $rowReference = $rowID;
728
                                                }
729
                                                //    Bracketed R references are relative to the current row
730
                                                if ($rowReference[0] == '[') {
731
                                                    $rowReference = $rowID + trim($rowReference, '[]');
732
                                                }
733
                                                $columnReference = $cellReference[4][0];
734
                                                //    Empty C reference is the current column
735
                                                if ($columnReference == '') {
736
                                                    $columnReference = $columnNumber;
737
                                                }
738
                                                //    Bracketed C references are relative to the current column
739
                                                if ($columnReference[0] == '[') {
740
                                                    $columnReference = $columnNumber + trim($columnReference, '[]');
741
                                                }
742
                                                $A1CellReference = Cell::stringFromColumnIndex($columnReference - 1) . $rowReference;
743
                                                $value = substr_replace($value, $A1CellReference, $cellReference[0][1], strlen($cellReference[0][0]));
744
                                            }
745
                                        }
746
                                    }
747
                                }
748 2
                                unset($value);
749
                                //    Then rebuild the formula string
750 2
                                $cellDataFormula = implode('"', $temp);
751
                            }
752
753 2
                            $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValueExplicit((($hasCalculatedValue) ? $cellDataFormula : $cellValue), $type);
754 2
                            if ($hasCalculatedValue) {
755 2
                                $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setCalculatedValue($cellValue);
756
                            }
757 2
                            $cellIsSet = $rowHasData = true;
758
                        }
759
760 2
                        if (isset($cell->Comment)) {
761 2
                            $commentAttributes = $cell->Comment->attributes($namespaces['ss']);
762 2
                            $author = 'unknown';
763 2
                            if (isset($commentAttributes->Author)) {
764
                                $author = (string) $commentAttributes->Author;
765
                            }
766 2
                            $node = $cell->Comment->Data->asXML();
767 2
                            $annotation = strip_tags($node);
768 2
                            $spreadsheet->getActiveSheet()->getComment($columnID . $rowID)->setAuthor(self::convertStringEncoding($author, $this->charSet))->setText($this->parseRichText($annotation));
769
                        }
770
771 2
                        if (($cellIsSet) && (isset($cell_ss['StyleID']))) {
772 2
                            $style = (string) $cell_ss['StyleID'];
773 2
                            if ((isset($this->styles[$style])) && (!empty($this->styles[$style]))) {
774 2
                                if (!$spreadsheet->getActiveSheet()->cellExists($columnID . $rowID)) {
775
                                    $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValue(null);
776
                                }
777 2
                                $spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($this->styles[$style]);
778
                            }
779
                        }
780 2
                        ++$columnID;
781 2
                        while ($additionalMergedCells > 0) {
782 2
                            ++$columnID;
783 2
                            --$additionalMergedCells;
784
                        }
785
                    }
786
787 2
                    if ($rowHasData) {
788 2
                        if (isset($row_ss['Height'])) {
789 2
                            $rowHeight = $row_ss['Height'];
790 2
                            $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setRowHeight($rowHeight);
791
                        }
792
                    }
793
794 2
                    ++$rowID;
795
                }
796
            }
797 2
            ++$worksheetID;
798
        }
799
800
        // Return
801 2
        return $spreadsheet;
802
    }
803
804 2
    protected static function convertStringEncoding($string, $charset)
805
    {
806 2
        if ($charset != 'UTF-8') {
807
            return StringHelper::convertEncoding($string, 'UTF-8', $charset);
808
        }
809
810 2
        return $string;
811
    }
812
813 2
    protected function parseRichText($is)
814
    {
815 2
        $value = new RichText();
816
817 2
        $value->createText(self::convertStringEncoding($is, $this->charSet));
818
819 2
        return $value;
820
    }
821
}
822