Completed
Push — develop ( febbe8...0477e6 )
by Adrien
25:31 queued 16:01
created

Xml::listWorksheetNames()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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