Completed
Push — develop ( d3e769...440bfe )
by Adrien
22:10
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 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 3
    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 3
                '<?xml version="1.0"',
68
                '<?mso-application progid="Excel.Sheet"?>',
69
            ];
70
71
        // Open file
72 3
        $this->openFile($pFilename);
73 3
        $fileHandle = $this->fileHandle;
74
75
        // Read sample data (first 2 KB will do)
76 3
        $data = fread($fileHandle, 2048);
77 3
        fclose($fileHandle);
78 3
        $data = strtr($data, "'", '"'); // fix headers with single quote
79
80 3
        $valid = true;
81 3
        foreach ($signature as $match) {
82
            // every part of the signature must be present
83 3
            if (strpos($data, $match) === false) {
84
                $valid = false;
85
86 3
                break;
87
            }
88
        }
89
90
        //    Retrieve charset encoding
91 3
        if (preg_match('/<?xml.*encoding=[\'"](.*?)[\'"].*?>/um', $data, $matches)) {
92 3
            $this->charSet = strtoupper($matches[1]);
93
        }
94
95 3
        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 View Code Duplication
    protected static function identifyFixedStyleValue($styleList, &$styleAttributeValue)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
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
        $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 2
        $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 2
        File::assertFile($pFilename);
334 2
        if (!$this->canRead($pFilename)) {
335
            throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
336
        }
337
338 2
        $xml = $this->trySimpleXMLLoadString($pFilename);
339
340 2
        $namespaces = $xml->getNamespaces(true);
341
342 2
        $docProps = $spreadsheet->getProperties();
343 2
        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 2
        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 2
        foreach ($xml->Styles[0] as $style) {
432 2
            $style_ss = $style->attributes($namespaces['ss']);
433 2
            $styleID = (string) $style_ss['ID'];
434 2
            $this->styles[$styleID] = (isset($this->styles['Default'])) ? $this->styles['Default'] : [];
435 2
            foreach ($style as $styleType => $styleData) {
436 2
                $styleAttributes = $styleData->attributes($namespaces['ss']);
437
                switch ($styleType) {
438 2
                    case 'Alignment':
439 2
                        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
440 2
                            $styleAttributeValue = (string) $styleAttributeValue;
441
                            switch ($styleAttributeKey) {
442 2
                                case 'Vertical':
443 2
                                    if (self::identifyFixedStyleValue($verticalAlignmentStyles, $styleAttributeValue)) {
444 2
                                        $this->styles[$styleID]['alignment']['vertical'] = $styleAttributeValue;
445
                                    }
446
447 2
                                    break;
448 2
                                case 'Horizontal':
449 2
                                    if (self::identifyFixedStyleValue($horizontalAlignmentStyles, $styleAttributeValue)) {
450 2
                                        $this->styles[$styleID]['alignment']['horizontal'] = $styleAttributeValue;
451
                                    }
452
453 2
                                    break;
454 2
                                case 'WrapText':
455 2
                                    $this->styles[$styleID]['alignment']['wrapText'] = true;
456
457 2
                                    break;
458
                            }
459
                        }
460
461 2
                        break;
462 2
                    case 'Borders':
463 2
                        foreach ($styleData->Border as $borderStyle) {
464 2
                            $borderAttributes = $borderStyle->attributes($namespaces['ss']);
465 2
                            $thisBorder = [];
466 2
                            foreach ($borderAttributes as $borderStyleKey => $borderStyleValue) {
467
                                switch ($borderStyleKey) {
468 2
                                    case 'LineStyle':
469 2
                                        $thisBorder['borderStyle'] = Border::BORDER_MEDIUM;
470
471 2
                                        break;
472 2
                                    case 'Weight':
473 2
                                        break;
474 2
                                    case 'Position':
475 2
                                        $borderPosition = strtolower($borderStyleValue);
476
477 2
                                        break;
478 2
                                    case 'Color':
479 2
                                        $borderColour = substr($borderStyleValue, 1);
480 2
                                        $thisBorder['color']['rgb'] = $borderColour;
481
482 2
                                        break;
483
                                }
484
                            }
485 2
                            if (!empty($thisBorder)) {
486 2
                                if (($borderPosition == 'left') || ($borderPosition == 'right') || ($borderPosition == 'top') || ($borderPosition == 'bottom')) {
487 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...
488
                                }
489
                            }
490
                        }
491
492 2
                        break;
493 2
                    case 'Font':
494 2
                        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
495 2
                            $styleAttributeValue = (string) $styleAttributeValue;
496
                            switch ($styleAttributeKey) {
497 2
                                case 'FontName':
498 2
                                    $this->styles[$styleID]['font']['name'] = $styleAttributeValue;
499
500 2
                                    break;
501 2
                                case 'Size':
502 2
                                    $this->styles[$styleID]['font']['size'] = $styleAttributeValue;
503
504 2
                                    break;
505 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...
506 2
                                    $this->styles[$styleID]['font']['color']['rgb'] = substr($styleAttributeValue, 1);
507
508 2
                                    break;
509 2
                                case 'Bold':
510 2
                                    $this->styles[$styleID]['font']['bold'] = true;
511
512 2
                                    break;
513 2
                                case 'Italic':
514 2
                                    $this->styles[$styleID]['font']['italic'] = true;
515
516 2
                                    break;
517 2
                                case 'Underline':
518 2
                                    if (self::identifyFixedStyleValue($underlineStyles, $styleAttributeValue)) {
519 2
                                        $this->styles[$styleID]['font']['underline'] = $styleAttributeValue;
520
                                    }
521
522 2
                                    break;
523
                            }
524
                        }
525
526 2
                        break;
527 2
                    case 'Interior':
528 2
                        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
529
                            switch ($styleAttributeKey) {
530 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...
531 2
                                    $this->styles[$styleID]['fill']['color']['rgb'] = substr($styleAttributeValue, 1);
532
533 2
                                    break;
534 2
                                case 'Pattern':
535 2
                                    $this->styles[$styleID]['fill']['fillType'] = strtolower($styleAttributeValue);
536
537 2
                                    break;
538
                            }
539
                        }
540
541 2
                        break;
542 2
                    case 'NumberFormat':
543 2
                        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
544 2
                            $styleAttributeValue = str_replace($fromFormats, $toFormats, $styleAttributeValue);
545
                            switch ($styleAttributeValue) {
546 2
                                case 'Short Date':
547 2
                                    $styleAttributeValue = 'dd/mm/yyyy';
548
549 2
                                    break;
550
                            }
551 2
                            if ($styleAttributeValue > '') {
552 2
                                $this->styles[$styleID]['numberFormat']['formatCode'] = $styleAttributeValue;
553
                            }
554
                        }
555
556 2
                        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 2
                        break;
562
                }
563
            }
564
        }
565
566 2
        $worksheetID = 0;
567 2
        $xml_ss = $xml->children($namespaces['ss']);
568
569 2
        foreach ($xml_ss->Worksheet as $worksheet) {
570 2
            $worksheet_ss = $worksheet->attributes($namespaces['ss']);
571
572 2
            if ((isset($this->loadSheetsOnly)) && (isset($worksheet_ss['Name'])) &&
573 2
                (!in_array($worksheet_ss['Name'], $this->loadSheetsOnly))) {
574
                continue;
575
            }
576
577
            // Create new Worksheet
578 2
            $spreadsheet->createSheet();
579 2
            $spreadsheet->setActiveSheetIndex($worksheetID);
580 2
            if (isset($worksheet_ss['Name'])) {
581 2
                $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 2
                $spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
586
            }
587
588 2
            $columnID = 'A';
589 2
            if (isset($worksheet->Table->Column)) {
590 2
                foreach ($worksheet->Table->Column as $columnData) {
591 2
                    $columnData_ss = $columnData->attributes($namespaces['ss']);
592 2
                    if (isset($columnData_ss['Index'])) {
593 2
                        $columnID = Cell::stringFromColumnIndex($columnData_ss['Index'] - 1);
594
                    }
595 2
                    if (isset($columnData_ss['Width'])) {
596 2
                        $columnWidth = $columnData_ss['Width'];
597 2
                        $spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setWidth($columnWidth / 5.4);
598
                    }
599 2
                    ++$columnID;
600
                }
601
            }
602
603 2
            $rowID = 1;
604 2
            if (isset($worksheet->Table->Row)) {
605 2
                $additionalMergedCells = 0;
606 2
                foreach ($worksheet->Table->Row as $rowData) {
607 2
                    $rowHasData = false;
608 2
                    $row_ss = $rowData->attributes($namespaces['ss']);
609 2
                    if (isset($row_ss['Index'])) {
610 2
                        $rowID = (int) $row_ss['Index'];
611
                    }
612
613 2
                    $columnID = 'A';
614 2
                    foreach ($rowData->Cell as $cell) {
615 2
                        $cell_ss = $cell->attributes($namespaces['ss']);
616 2
                        if (isset($cell_ss['Index'])) {
617 2
                            $columnID = Cell::stringFromColumnIndex($cell_ss['Index'] - 1);
618
                        }
619 2
                        $cellRange = $columnID . $rowID;
620
621 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...
622 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...
623
                                ++$columnID;
624
625
                                continue;
626
                            }
627
                        }
628
629 2
                        if (isset($cell_ss['HRef'])) {
630 2
                            $spreadsheet->getActiveSheet()->getCell($cellRange)->getHyperlink()->setUrl($cell_ss['HRef']);
631
                        }
632
633 2
                        if ((isset($cell_ss['MergeAcross'])) || (isset($cell_ss['MergeDown']))) {
634 2
                            $columnTo = $columnID;
635 2
                            if (isset($cell_ss['MergeAcross'])) {
636 2
                                $additionalMergedCells += (int) $cell_ss['MergeAcross'];
637 2
                                $columnTo = Cell::stringFromColumnIndex(Cell::columnIndexFromString($columnID) + $cell_ss['MergeAcross'] - 1);
638
                            }
639 2
                            $rowTo = $rowID;
640 2
                            if (isset($cell_ss['MergeDown'])) {
641 2
                                $rowTo = $rowTo + $cell_ss['MergeDown'];
642
                            }
643 2
                            $cellRange .= ':' . $columnTo . $rowTo;
644 2
                            $spreadsheet->getActiveSheet()->mergeCells($cellRange);
645
                        }
646
647 2
                        $cellIsSet = $hasCalculatedValue = false;
648 2
                        $cellDataFormula = '';
649 2
                        if (isset($cell_ss['Formula'])) {
650 2
                            $cellDataFormula = $cell_ss['Formula'];
651
                            // added this as a check for array formulas
652 2
                            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 2
                            $hasCalculatedValue = true;
656
                        }
657 2
                        if (isset($cell->Data)) {
658 2
                            $cellValue = $cellData = $cell->Data;
659 2
                            $type = Cell\DataType::TYPE_NULL;
660 2
                            $cellData_ss = $cellData->attributes($namespaces['ss']);
661 2
                            if (isset($cellData_ss['Type'])) {
662 2
                                $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 2
                                    case 'String':
674 2
                                        $cellValue = self::convertStringEncoding($cellValue, $this->charSet);
675 2
                                        $type = Cell\DataType::TYPE_STRING;
676
677 2
                                        break;
678 2
                                    case 'Number':
679 2
                                        $type = Cell\DataType::TYPE_NUMERIC;
680 2
                                        $cellValue = (float) $cellValue;
681 2
                                        if (floor($cellValue) == $cellValue) {
682 2
                                            $cellValue = (int) $cellValue;
683
                                        }
684
685 2
                                        break;
686 2
                                    case 'Boolean':
687 2
                                        $type = Cell\DataType::TYPE_BOOL;
688 2
                                        $cellValue = ($cellValue != 0);
689
690 2
                                        break;
691 2
                                    case 'DateTime':
692 2
                                        $type = Cell\DataType::TYPE_NUMERIC;
693 2
                                        $cellValue = Date::PHPToExcel(strtotime($cellValue));
694
695 2
                                        break;
696
                                    case 'Error':
697
                                        $type = Cell\DataType::TYPE_ERROR;
698
699
                                        break;
700
                                }
701
                            }
702
703 2
                            if ($hasCalculatedValue) {
704 2
                                $type = Cell\DataType::TYPE_FORMULA;
705 2
                                $columnNumber = Cell::columnIndexFromString($columnID);
706 2
                                if (substr($cellDataFormula, 0, 3) == 'of:') {
707 2
                                    $cellDataFormula = substr($cellDataFormula, 3);
708 2
                                    $temp = explode('"', $cellDataFormula);
709 2
                                    $key = false;
710 2
                                    foreach ($temp as &$value) {
711
                                        //    Only replace in alternate array entries (i.e. non-quoted blocks)
712 2
                                        if ($key = !$key) {
713 2
                                            $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 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...
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 2
                                unset($value);
756
                                //    Then rebuild the formula string
757 2
                                $cellDataFormula = implode('"', $temp);
758
                            }
759
760 2
                            $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValueExplicit((($hasCalculatedValue) ? $cellDataFormula : $cellValue), $type);
761 2
                            if ($hasCalculatedValue) {
762 2
                                $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setCalculatedValue($cellValue);
763
                            }
764 2
                            $cellIsSet = $rowHasData = true;
765
                        }
766
767 2
                        if (isset($cell->Comment)) {
768 2
                            $commentAttributes = $cell->Comment->attributes($namespaces['ss']);
769 2
                            $author = 'unknown';
770 2
                            if (isset($commentAttributes->Author)) {
771
                                $author = (string) $commentAttributes->Author;
772
                            }
773 2
                            $node = $cell->Comment->Data->asXML();
774 2
                            $annotation = strip_tags($node);
775 2
                            $spreadsheet->getActiveSheet()->getComment($columnID . $rowID)->setAuthor(self::convertStringEncoding($author, $this->charSet))->setText($this->parseRichText($annotation));
776
                        }
777
778 2
                        if (($cellIsSet) && (isset($cell_ss['StyleID']))) {
779 2
                            $style = (string) $cell_ss['StyleID'];
780 2
                            if ((isset($this->styles[$style])) && (!empty($this->styles[$style]))) {
781 2
                                if (!$spreadsheet->getActiveSheet()->cellExists($columnID . $rowID)) {
782
                                    $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValue(null);
783
                                }
784 2
                                $spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($this->styles[$style]);
785
                            }
786
                        }
787 2
                        ++$columnID;
788 2
                        while ($additionalMergedCells > 0) {
789 2
                            ++$columnID;
790 2
                            --$additionalMergedCells;
791
                        }
792
                    }
793
794 2
                    if ($rowHasData) {
795 2
                        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 2
                        if (isset($row_ss['Height'])) {
799 2
                            $rowHeight = $row_ss['Height'];
800 2
                            $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setRowHeight($rowHeight);
801
                        }
802
                    }
803
804 2
                    ++$rowID;
805
                }
806
            }
807 2
            ++$worksheetID;
808
        }
809
810
        // Return
811 2
        return $spreadsheet;
812
    }
813
814 2
    protected static function convertStringEncoding($string, $charset)
815
    {
816 2
        if ($charset != 'UTF-8') {
817
            return StringHelper::convertEncoding($string, 'UTF-8', $charset);
818
        }
819
820 2
        return $string;
821
    }
822
823 2
    protected function parseRichText($is)
824
    {
825 2
        $value = new RichText();
826
827 2
        $value->createText(self::convertStringEncoding($is, $this->charSet));
828
829 2
        return $value;
830
    }
831
}
832