Completed
Push — develop ( d383bc...d9bd45 )
by Adrien
23:59
created

Slk::load()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 7
ccs 3
cts 3
cp 1
crap 1
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Reader;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
6
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
7
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
8
use PhpOffice\PhpSpreadsheet\Spreadsheet;
9
use PhpOffice\PhpSpreadsheet\Style\Border;
10
11
class Slk extends BaseReader
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);
1 ignored issue
show
Bug introduced by
It seems like $this->fileHandle can also be of type false; however, parameter $handle of fread() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

68
        $data = fread(/** @scrutinizer ignore-type */ $this->fileHandle, 2048);
Loading history...
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);
1 ignored issue
show
Bug introduced by
It seems like $this->fileHandle can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

78
        fclose(/** @scrutinizer ignore-type */ $this->fileHandle);
Loading history...
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);
1 ignored issue
show
Bug introduced by
It seems like $fileHandle can also be of type false; however, parameter $handle of rewind() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

120
        rewind(/** @scrutinizer ignore-type */ $fileHandle);
Loading history...
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) {
1 ignored issue
show
Bug introduced by
It seems like $fileHandle can also be of type false; however, parameter $handle of fgets() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

131
        while (($rowData = fgets(/** @scrutinizer ignore-type */ $fileHandle)) !== false) {
Loading history...
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'] = Coordinate::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex'] + 1);
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);
1 ignored issue
show
Bug introduced by
It seems like $fileHandle can also be of type false; however, parameter $handle of rewind() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

209
        rewind(/** @scrutinizer ignore-type */ $fileHandle);
Loading history...
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) {
1 ignored issue
show
Bug introduced by
It seems like $fileHandle can also be of type false; however, parameter $handle of fgets() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

224
        while (($rowData = fgets(/** @scrutinizer ignore-type */ $fileHandle)) !== false) {
Loading history...
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
                            $iMax = strlen($styleSettings);
254 1
                            for ($i = 0; $i < $iMax; ++$i) {
255 1
                                switch ($styleSettings[$i]) {
256 1
                                    case 'I':
257 1
                                        $formatArray['font']['italic'] = true;
258
259 1
                                        break;
260 1
                                    case 'D':
261
                                        $formatArray['font']['bold'] = true;
262
263
                                        break;
264 1
                                    case 'T':
265
                                        $formatArray['borders']['top']['borderStyle'] = Border::BORDER_THIN;
266
267
                                        break;
268 1
                                    case 'B':
269 1
                                        $formatArray['borders']['bottom']['borderStyle'] = Border::BORDER_THIN;
270
271 1
                                        break;
272 1
                                    case 'L':
273
                                        $formatArray['borders']['left']['borderStyle'] = Border::BORDER_THIN;
274
275
                                        break;
276 1
                                    case 'R':
277
                                        $formatArray['borders']['right']['borderStyle'] = Border::BORDER_THIN;
278
279
                                        break;
280
                                }
281
                            }
282
283 1
                            break;
284
                    }
285
                }
286 1
                $this->formats['P' . $this->format++] = $formatArray;
287
                //    Read cell value data
288 1
            } elseif ($dataType == 'C') {
289 1
                $hasCalculatedValue = false;
290 1
                $cellData = $cellDataFormula = '';
291 1
                foreach ($rowData as $rowDatum) {
292 1
                    switch ($rowDatum[0]) {
293 1
                        case 'C':
294 1
                        case 'X':
295 1
                            $column = substr($rowDatum, 1);
296
297 1
                            break;
298 1
                        case 'R':
299 1
                        case 'Y':
300 1
                            $row = substr($rowDatum, 1);
301
302 1
                            break;
303 1
                        case 'K':
304 1
                            $cellData = substr($rowDatum, 1);
305
306 1
                            break;
307 1
                        case 'E':
308 1
                            $cellDataFormula = '=' . substr($rowDatum, 1);
309
                            //    Convert R1C1 style references to A1 style references (but only when not quoted)
310 1
                            $temp = explode('"', $cellDataFormula);
311 1
                            $key = false;
312 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...
313
                                //    Only count/replace in alternate array entries
314 1
                                if ($key = !$key) {
315 1
                                    preg_match_all('/(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))/', $value, $cellReferences, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
316
                                    //    Reverse the matches array, otherwise all our offsets will become incorrect if we modify our way
317
                                    //        through the formula from left to right. Reversing means that we work right to left.through
318
                                    //        the formula
319 1
                                    $cellReferences = array_reverse($cellReferences);
320
                                    //    Loop through each R1C1 style reference in turn, converting it to its A1 style equivalent,
321
                                    //        then modify the formula to use that new reference
322 1
                                    foreach ($cellReferences as $cellReference) {
323 1
                                        $rowReference = $cellReference[2][0];
324
                                        //    Empty R reference is the current row
325 1
                                        if ($rowReference == '') {
326 1
                                            $rowReference = $row;
327
                                        }
328
                                        //    Bracketed R references are relative to the current row
329 1
                                        if ($rowReference[0] == '[') {
330 1
                                            $rowReference = $row + trim($rowReference, '[]');
331
                                        }
332 1
                                        $columnReference = $cellReference[4][0];
333
                                        //    Empty C reference is the current column
334 1
                                        if ($columnReference == '') {
335
                                            $columnReference = $column;
336
                                        }
337
                                        //    Bracketed C references are relative to the current column
338 1
                                        if ($columnReference[0] == '[') {
339 1
                                            $columnReference = $column + trim($columnReference, '[]');
340
                                        }
341 1
                                        $A1CellReference = Coordinate::stringFromColumnIndex($columnReference) . $rowReference;
1 ignored issue
show
Bug introduced by
It seems like $columnReference can also be of type string; however, parameter $columnIndex of PhpOffice\PhpSpreadsheet...stringFromColumnIndex() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

341
                                        $A1CellReference = Coordinate::stringFromColumnIndex(/** @scrutinizer ignore-type */ $columnReference) . $rowReference;
Loading history...
342
343 1
                                        $value = substr_replace($value, $A1CellReference, $cellReference[0][1], strlen($cellReference[0][0]));
344
                                    }
345
                                }
346
                            }
347 1
                            unset($value);
348
                            //    Then rebuild the formula string
349 1
                            $cellDataFormula = implode('"', $temp);
350 1
                            $hasCalculatedValue = true;
351
352 1
                            break;
353
                    }
354
                }
355 1
                $columnLetter = Coordinate::stringFromColumnIndex($column);
356 1
                $cellData = Calculation::unwrapResult($cellData);
357
358
                // Set cell value
359 1
                $spreadsheet->getActiveSheet()->getCell($columnLetter . $row)->setValue(($hasCalculatedValue) ? $cellDataFormula : $cellData);
360 1
                if ($hasCalculatedValue) {
361 1
                    $cellData = Calculation::unwrapResult($cellData);
362 1
                    $spreadsheet->getActiveSheet()->getCell($columnLetter . $row)->setCalculatedValue($cellData);
363
                }
364
                //    Read cell formatting
365 1
            } elseif ($dataType == 'F') {
366 1
                $formatStyle = $columnWidth = $styleSettings = '';
367 1
                $styleData = [];
368 1
                foreach ($rowData as $rowDatum) {
369 1
                    switch ($rowDatum[0]) {
370 1
                        case 'C':
371 1
                        case 'X':
372 1
                            $column = substr($rowDatum, 1);
373
374 1
                            break;
375 1
                        case 'R':
376 1
                        case 'Y':
377 1
                            $row = substr($rowDatum, 1);
378
379 1
                            break;
380 1
                        case 'P':
381 1
                            $formatStyle = $rowDatum;
382
383 1
                            break;
384 1
                        case 'W':
385 1
                            list($startCol, $endCol, $columnWidth) = explode(' ', substr($rowDatum, 1));
386
387 1
                            break;
388 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...
389 1
                            $styleSettings = substr($rowDatum, 1);
390 1
                            $iMax = strlen($styleSettings);
391 1
                            for ($i = 0; $i < $iMax; ++$i) {
392 1
                                switch ($styleSettings[$i]) {
393 1
                                    case 'I':
394 1
                                        $styleData['font']['italic'] = true;
395
396 1
                                        break;
397 1
                                    case 'D':
398 1
                                        $styleData['font']['bold'] = true;
399
400 1
                                        break;
401 1
                                    case 'T':
402 1
                                        $styleData['borders']['top']['borderStyle'] = Border::BORDER_THIN;
403
404 1
                                        break;
405 1
                                    case 'B':
406 1
                                        $styleData['borders']['bottom']['borderStyle'] = Border::BORDER_THIN;
407
408 1
                                        break;
409 1
                                    case 'L':
410 1
                                        $styleData['borders']['left']['borderStyle'] = Border::BORDER_THIN;
411
412 1
                                        break;
413 1
                                    case 'R':
414 1
                                        $styleData['borders']['right']['borderStyle'] = Border::BORDER_THIN;
415
416 1
                                        break;
417
                                }
418
                            }
419
420 1
                            break;
421
                    }
422
                }
423 1
                if (($formatStyle > '') && ($column > '') && ($row > '')) {
424 1
                    $columnLetter = Coordinate::stringFromColumnIndex($column);
425 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...
426 1
                        $spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($this->formats[$formatStyle]);
427
                    }
428
                }
429 1
                if ((!empty($styleData)) && ($column > '') && ($row > '')) {
430 1
                    $columnLetter = Coordinate::stringFromColumnIndex($column);
431 1
                    $spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($styleData);
432
                }
433 1
                if ($columnWidth > '') {
434 1
                    if ($startCol == $endCol) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $startCol does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $endCol does not seem to be defined for all execution paths leading up to this point.
Loading history...
435 1
                        $startCol = Coordinate::stringFromColumnIndex($startCol);
436 1
                        $spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth($columnWidth);
0 ignored issues
show
Bug introduced by
$columnWidth of type string is incompatible with the type double expected by parameter $pValue of PhpOffice\PhpSpreadsheet...mnDimension::setWidth(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

436
                        $spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth(/** @scrutinizer ignore-type */ $columnWidth);
Loading history...
437
                    } else {
438 1
                        $startCol = Coordinate::stringFromColumnIndex($startCol);
439 1
                        $endCol = Coordinate::stringFromColumnIndex($endCol);
440 1
                        $spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth($columnWidth);
441
                        do {
442 1
                            $spreadsheet->getActiveSheet()->getColumnDimension(++$startCol)->setWidth($columnWidth);
443 1
                        } while ($startCol != $endCol);
444
                    }
445
                }
446
            } else {
447 1
                foreach ($rowData as $rowDatum) {
448 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...
449 1
                        case 'C':
450 1
                        case 'X':
451 1
                            $column = substr($rowDatum, 1);
452
453 1
                            break;
454 1
                        case 'R':
455 1
                        case 'Y':
456 1
                            $row = substr($rowDatum, 1);
457
458 1
                            break;
459
                    }
460
                }
461
            }
462
        }
463
464
        // Close file
465 1
        fclose($fileHandle);
466
467
        // Return
468 1
        return $spreadsheet;
469
    }
470
471
    /**
472
     * Get sheet index.
473
     *
474
     * @return int
475
     */
476
    public function getSheetIndex()
477
    {
478
        return $this->sheetIndex;
479
    }
480
481
    /**
482
     * Set sheet index.
483
     *
484
     * @param int $pValue Sheet index
485
     *
486
     * @return Slk
487
     */
488
    public function setSheetIndex($pValue)
489
    {
490
        $this->sheetIndex = $pValue;
491
492
        return $this;
493
    }
494
}
495