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

Slk::loadIntoExisting()   F

Complexity

Conditions 66
Paths 723

Size

Total Lines 267
Code Lines 174

Duplication

Lines 100
Ratio 37.45 %

Code Coverage

Tests 160
CRAP Score 67.158

Importance

Changes 0
Metric Value
cc 66
eloc 174
nc 723
nop 2
dl 100
loc 267
ccs 160
cts 171
cp 0.9357
crap 67.158
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Reader;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
6
use PhpOffice\PhpSpreadsheet\Cell\Cell;
7
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
8
use PhpOffice\PhpSpreadsheet\Spreadsheet;
9
use PhpOffice\PhpSpreadsheet\Style\Border;
10
11
class Slk extends BaseReader implements IReader
12
{
13
    /**
14
     * Input encoding.
15
     *
16
     * @var string
17
     */
18
    private $inputEncoding = 'ANSI';
19
20
    /**
21
     * Sheet index to read.
22
     *
23
     * @var int
24
     */
25
    private $sheetIndex = 0;
26
27
    /**
28
     * Formats.
29
     *
30
     * @var array
31
     */
32
    private $formats = [];
33
34
    /**
35
     * Format Count.
36
     *
37
     * @var int
38
     */
39
    private $format = 0;
40
41
    /**
42
     * Create a new SYLK Reader instance.
43
     */
44 4
    public function __construct()
45
    {
46 4
        $this->readFilter = new DefaultReadFilter();
47 4
    }
48
49
    /**
50
     * Validate that the current file is a SYLK file.
51
     *
52
     * @param string $pFilename
53
     *
54
     * @throws Exception
55
     *
56
     * @return bool
57
     */
58 3
    public function canRead($pFilename)
59
    {
60
        // Check if file exists
61
        try {
62 3
            $this->openFile($pFilename);
63
        } catch (Exception $e) {
64
            return false;
65
        }
66
67
        // Read sample data (first 2 KB will do)
68 3
        $data = fread($this->fileHandle, 2048);
69
70
        // Count delimiters in file
71 3
        $delimiterCount = substr_count($data, ';');
72 3
        $hasDelimiter = $delimiterCount > 0;
73
74
        // Analyze first line looking for ID; signature
75 3
        $lines = explode("\n", $data);
76 3
        $hasId = substr($lines[0], 0, 4) === 'ID;P';
77
78 3
        fclose($this->fileHandle);
79
80 3
        return $hasDelimiter && $hasId;
81
    }
82
83
    /**
84
     * Set input encoding.
85
     *
86
     * @param string $pValue Input encoding, eg: 'ANSI'
87
     */
88
    public function setInputEncoding($pValue)
89
    {
90
        $this->inputEncoding = $pValue;
91
92
        return $this;
93
    }
94
95
    /**
96
     * Get input encoding.
97
     *
98
     * @return string
99
     */
100
    public function getInputEncoding()
101
    {
102
        return $this->inputEncoding;
103
    }
104
105
    /**
106
     * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
107
     *
108
     * @param string $pFilename
109
     *
110
     * @throws Exception
111
     */
112
    public function listWorksheetInfo($pFilename)
113
    {
114
        // Open file
115
        if (!$this->canRead($pFilename)) {
116
            throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
117
        }
118
        $this->openFile($pFilename);
119
        $fileHandle = $this->fileHandle;
120
        rewind($fileHandle);
121
122
        $worksheetInfo = [];
123
        $worksheetInfo[0]['worksheetName'] = 'Worksheet';
124
        $worksheetInfo[0]['lastColumnLetter'] = 'A';
125
        $worksheetInfo[0]['lastColumnIndex'] = 0;
126
        $worksheetInfo[0]['totalRows'] = 0;
127
        $worksheetInfo[0]['totalColumns'] = 0;
128
129
        // loop through one row (line) at a time in the file
130
        $rowIndex = 0;
131
        while (($rowData = fgets($fileHandle)) !== false) {
132
            $columnIndex = 0;
133
134
            // convert SYLK encoded $rowData to UTF-8
135
            $rowData = StringHelper::SYLKtoUTF8($rowData);
136
137
            // explode each row at semicolons while taking into account that literal semicolon (;)
138
            // is escaped like this (;;)
139
            $rowData = explode("\t", str_replace('¤', ';', str_replace(';', "\t", str_replace(';;', '¤', rtrim($rowData)))));
140
141
            $dataType = array_shift($rowData);
142
            if ($dataType == 'C') {
143
                //  Read cell value data
144
                foreach ($rowData as $rowDatum) {
145 View Code Duplication
                    switch ($rowDatum[0]) {
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...
146
                        case 'C':
147
                        case 'X':
148
                            $columnIndex = substr($rowDatum, 1) - 1;
149
150
                            break;
151
                        case 'R':
152
                        case 'Y':
153
                            $rowIndex = substr($rowDatum, 1);
154
155
                            break;
156
                    }
157
158
                    $worksheetInfo[0]['totalRows'] = max($worksheetInfo[0]['totalRows'], $rowIndex);
159
                    $worksheetInfo[0]['lastColumnIndex'] = max($worksheetInfo[0]['lastColumnIndex'], $columnIndex);
160
                }
161
            }
162
        }
163
164
        $worksheetInfo[0]['lastColumnLetter'] = Cell::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex']);
165
        $worksheetInfo[0]['totalColumns'] = $worksheetInfo[0]['lastColumnIndex'] + 1;
166
167
        // Close file
168
        fclose($fileHandle);
169
170
        return $worksheetInfo;
171
    }
172
173
    /**
174
     * Loads PhpSpreadsheet from file.
175
     *
176
     * @param string $pFilename
177
     *
178
     * @throws Exception
179
     *
180
     * @return Spreadsheet
181
     */
182 1
    public function load($pFilename)
183
    {
184
        // Create new Spreadsheet
185 1
        $spreadsheet = new Spreadsheet();
186
187
        // Load into this instance
188 1
        return $this->loadIntoExisting($pFilename, $spreadsheet);
189
    }
190
191
    /**
192
     * Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
193
     *
194
     * @param string $pFilename
195
     * @param Spreadsheet $spreadsheet
196
     *
197
     * @throws Exception
198
     *
199
     * @return Spreadsheet
200
     */
201 1
    public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
202
    {
203
        // Open file
204 1
        if (!$this->canRead($pFilename)) {
205
            throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
206
        }
207 1
        $this->openFile($pFilename);
208 1
        $fileHandle = $this->fileHandle;
209 1
        rewind($fileHandle);
210
211
        // Create new Worksheets
212 1
        while ($spreadsheet->getSheetCount() <= $this->sheetIndex) {
213
            $spreadsheet->createSheet();
214
        }
215 1
        $spreadsheet->setActiveSheetIndex($this->sheetIndex);
216
217 1
        $fromFormats = ['\-', '\ '];
218 1
        $toFormats = ['-', ' '];
219
220
        // Loop through file
221 1
        $column = $row = '';
222
223
        // loop through one row (line) at a time in the file
224 1
        while (($rowData = fgets($fileHandle)) !== false) {
225
            // convert SYLK encoded $rowData to UTF-8
226 1
            $rowData = StringHelper::SYLKtoUTF8($rowData);
227
228
            // explode each row at semicolons while taking into account that literal semicolon (;)
229
            // is escaped like this (;;)
230 1
            $rowData = explode("\t", str_replace('¤', ';', str_replace(';', "\t", str_replace(';;', '¤', rtrim($rowData)))));
231
232 1
            $dataType = array_shift($rowData);
233
            //    Read shared styles
234 1
            if ($dataType == 'P') {
235 1
                $formatArray = [];
236 1
                foreach ($rowData as $rowDatum) {
237 1
                    switch ($rowDatum[0]) {
238 1
                        case 'P':
239 1
                            $formatArray['numberFormat']['formatCode'] = str_replace($fromFormats, $toFormats, substr($rowDatum, 1));
240
241 1
                            break;
242 1
                        case 'E':
243 1
                        case 'F':
244 1
                            $formatArray['font']['name'] = substr($rowDatum, 1);
245
246 1
                            break;
247 1
                        case 'L':
248 1
                            $formatArray['font']['size'] = substr($rowDatum, 1);
249
250 1
                            break;
251 1 View Code Duplication
                        case 'S':
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...
252 1
                            $styleSettings = substr($rowDatum, 1);
253 1
                            for ($i = 0; $i < strlen($styleSettings); ++$i) {
254 1
                                switch ($styleSettings[$i]) {
255 1
                                    case 'I':
256 1
                                        $formatArray['font']['italic'] = true;
257
258 1
                                        break;
259 1
                                    case 'D':
260
                                        $formatArray['font']['bold'] = true;
261
262
                                        break;
263 1
                                    case 'T':
264
                                        $formatArray['borders']['top']['borderStyle'] = Border::BORDER_THIN;
265
266
                                        break;
267 1
                                    case 'B':
268 1
                                        $formatArray['borders']['bottom']['borderStyle'] = Border::BORDER_THIN;
269
270 1
                                        break;
271 1
                                    case 'L':
272
                                        $formatArray['borders']['left']['borderStyle'] = Border::BORDER_THIN;
273
274
                                        break;
275 1
                                    case 'R':
276
                                        $formatArray['borders']['right']['borderStyle'] = Border::BORDER_THIN;
277
278
                                        break;
279
                                }
280
                            }
281
282 1
                            break;
283
                    }
284
                }
285 1
                $this->formats['P' . $this->format++] = $formatArray;
286
                //    Read cell value data
287 1
            } elseif ($dataType == 'C') {
288 1
                $hasCalculatedValue = false;
289 1
                $cellData = $cellDataFormula = '';
290 1
                foreach ($rowData as $rowDatum) {
291 1
                    switch ($rowDatum[0]) {
292 1
                        case 'C':
293 1
                        case 'X':
294 1
                            $column = substr($rowDatum, 1);
295
296 1
                            break;
297 1
                        case 'R':
298 1
                        case 'Y':
299 1
                            $row = substr($rowDatum, 1);
300
301 1
                            break;
302 1
                        case 'K':
303 1
                            $cellData = substr($rowDatum, 1);
304
305 1
                            break;
306 1
                        case 'E':
307 1
                            $cellDataFormula = '=' . substr($rowDatum, 1);
308
                            //    Convert R1C1 style references to A1 style references (but only when not quoted)
309 1
                            $temp = explode('"', $cellDataFormula);
310 1
                            $key = false;
311 1 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...
312
                                //    Only count/replace in alternate array entries
313 1
                                if ($key = !$key) {
314 1
                                    preg_match_all('/(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))/', $value, $cellReferences, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
315
                                    //    Reverse the matches array, otherwise all our offsets will become incorrect if we modify our way
316
                                    //        through the formula from left to right. Reversing means that we work right to left.through
317
                                    //        the formula
318 1
                                    $cellReferences = array_reverse($cellReferences);
319
                                    //    Loop through each R1C1 style reference in turn, converting it to its A1 style equivalent,
320
                                    //        then modify the formula to use that new reference
321 1
                                    foreach ($cellReferences as $cellReference) {
322 1
                                        $rowReference = $cellReference[2][0];
323
                                        //    Empty R reference is the current row
324 1
                                        if ($rowReference == '') {
325 1
                                            $rowReference = $row;
326
                                        }
327
                                        //    Bracketed R references are relative to the current row
328 1
                                        if ($rowReference[0] == '[') {
329 1
                                            $rowReference = $row + trim($rowReference, '[]');
330
                                        }
331 1
                                        $columnReference = $cellReference[4][0];
332
                                        //    Empty C reference is the current column
333 1
                                        if ($columnReference == '') {
334
                                            $columnReference = $column;
335
                                        }
336
                                        //    Bracketed C references are relative to the current column
337 1
                                        if ($columnReference[0] == '[') {
338 1
                                            $columnReference = $column + trim($columnReference, '[]');
339
                                        }
340 1
                                        $A1CellReference = Cell::stringFromColumnIndex($columnReference - 1) . $rowReference;
341
342 1
                                        $value = substr_replace($value, $A1CellReference, $cellReference[0][1], strlen($cellReference[0][0]));
343
                                    }
344
                                }
345
                            }
346 1
                            unset($value);
347
                            //    Then rebuild the formula string
348 1
                            $cellDataFormula = implode('"', $temp);
349 1
                            $hasCalculatedValue = true;
350
351 1
                            break;
352
                    }
353
                }
354 1
                $columnLetter = Cell::stringFromColumnIndex($column - 1);
355 1
                $cellData = Calculation::unwrapResult($cellData);
356
357
                // Set cell value
358 1
                $spreadsheet->getActiveSheet()->getCell($columnLetter . $row)->setValue(($hasCalculatedValue) ? $cellDataFormula : $cellData);
359 1
                if ($hasCalculatedValue) {
360 1
                    $cellData = Calculation::unwrapResult($cellData);
361 1
                    $spreadsheet->getActiveSheet()->getCell($columnLetter . $row)->setCalculatedValue($cellData);
362
                }
363
                //    Read cell formatting
364 1
            } elseif ($dataType == 'F') {
365 1
                $formatStyle = $columnWidth = $styleSettings = '';
0 ignored issues
show
Unused Code introduced by
$styleSettings 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...
366 1
                $styleData = [];
367 1
                foreach ($rowData as $rowDatum) {
368 1
                    switch ($rowDatum[0]) {
369 1
                        case 'C':
370 1
                        case 'X':
371 1
                            $column = substr($rowDatum, 1);
372
373 1
                            break;
374 1
                        case 'R':
375 1
                        case 'Y':
376 1
                            $row = substr($rowDatum, 1);
377
378 1
                            break;
379 1
                        case 'P':
380 1
                            $formatStyle = $rowDatum;
381
382 1
                            break;
383 1
                        case 'W':
384 1
                            list($startCol, $endCol, $columnWidth) = explode(' ', substr($rowDatum, 1));
385
386 1
                            break;
387 1 View Code Duplication
                        case 'S':
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...
388 1
                            $styleSettings = substr($rowDatum, 1);
389 1
                            for ($i = 0; $i < strlen($styleSettings); ++$i) {
390 1
                                switch ($styleSettings[$i]) {
391 1
                                    case 'I':
392 1
                                        $styleData['font']['italic'] = true;
393
394 1
                                        break;
395 1
                                    case 'D':
396 1
                                        $styleData['font']['bold'] = true;
397
398 1
                                        break;
399 1
                                    case 'T':
400 1
                                        $styleData['borders']['top']['borderStyle'] = Border::BORDER_THIN;
401
402 1
                                        break;
403 1
                                    case 'B':
404 1
                                        $styleData['borders']['bottom']['borderStyle'] = Border::BORDER_THIN;
405
406 1
                                        break;
407 1
                                    case 'L':
408 1
                                        $styleData['borders']['left']['borderStyle'] = Border::BORDER_THIN;
409
410 1
                                        break;
411 1
                                    case 'R':
412 1
                                        $styleData['borders']['right']['borderStyle'] = Border::BORDER_THIN;
413
414 1
                                        break;
415
                                }
416
                            }
417
418 1
                            break;
419
                    }
420
                }
421 1
                if (($formatStyle > '') && ($column > '') && ($row > '')) {
422 1
                    $columnLetter = Cell::stringFromColumnIndex($column - 1);
423 1 View Code Duplication
                    if (isset($this->formats[$formatStyle])) {
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...
424 1
                        $spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($this->formats[$formatStyle]);
425
                    }
426
                }
427 1
                if ((!empty($styleData)) && ($column > '') && ($row > '')) {
428 1
                    $columnLetter = Cell::stringFromColumnIndex($column - 1);
429 1
                    $spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($styleData);
430
                }
431 1
                if ($columnWidth > '') {
432 1
                    if ($startCol == $endCol) {
433 1
                        $startCol = Cell::stringFromColumnIndex($startCol - 1);
0 ignored issues
show
Bug introduced by
The variable $startCol 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...
434 1
                        $spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth($columnWidth);
435
                    } else {
436 1
                        $startCol = Cell::stringFromColumnIndex($startCol - 1);
437 1
                        $endCol = Cell::stringFromColumnIndex($endCol - 1);
0 ignored issues
show
Bug introduced by
The variable $endCol 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...
438 1
                        $spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth($columnWidth);
439
                        do {
440 1
                            $spreadsheet->getActiveSheet()->getColumnDimension(++$startCol)->setWidth($columnWidth);
441 1
                        } while ($startCol != $endCol);
442
                    }
443
                }
444
            } else {
445 1
                foreach ($rowData as $rowDatum) {
446 1 View Code Duplication
                    switch ($rowDatum[0]) {
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...
447 1
                        case 'C':
448 1
                        case 'X':
449 1
                            $column = substr($rowDatum, 1);
450
451 1
                            break;
452 1
                        case 'R':
453 1
                        case 'Y':
454 1
                            $row = substr($rowDatum, 1);
455
456 1
                            break;
457
                    }
458
                }
459
            }
460
        }
461
462
        // Close file
463 1
        fclose($fileHandle);
464
465
        // Return
466 1
        return $spreadsheet;
467
    }
468
469
    /**
470
     * Get sheet index.
471
     *
472
     * @return int
473
     */
474
    public function getSheetIndex()
475
    {
476
        return $this->sheetIndex;
477
    }
478
479
    /**
480
     * Set sheet index.
481
     *
482
     * @param int $pValue Sheet index
483
     *
484
     * @return Slk
485
     */
486
    public function setSheetIndex($pValue)
487
    {
488
        $this->sheetIndex = $pValue;
489
490
        return $this;
491
    }
492
}
493