Total Complexity | 84 |
Total Lines | 480 |
Duplicated Lines | 23.33 % |
Coverage | 75.11% |
Changes | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Slk often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Slk, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
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); |
|
69 | |||
70 | // Count delimiters in file |
||
71 | 3 | $delimiterCount = substr_count($data, ';'); |
|
1 ignored issue
–
show
|
|||
72 | 3 | $hasDelimiter = $delimiterCount > 0; |
|
73 | |||
74 | // Analyze first line looking for ID; signature |
||
75 | 3 | $lines = explode("\n", $data); |
|
1 ignored issue
–
show
|
|||
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]) { |
|
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); |
|
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': |
252 | 1 | $styleSettings = substr($rowDatum, 1); |
|
253 | 1 | for ($i = 0; $i < strlen($styleSettings); ++$i) { |
|
1 ignored issue
–
show
|
|||
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); |
|
1 ignored issue
–
show
|
|||
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) { |
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, '[]'); |
|
1 ignored issue
–
show
|
|||
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 = Coordinate::stringFromColumnIndex($columnReference) . $rowReference; |
|
2 ignored issues
–
show
|
|||
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 = Coordinate::stringFromColumnIndex($column); |
|
355 | 1 | $cellData = Calculation::unwrapResult($cellData); |
|
356 | |||
357 | // Set cell value |
||
358 | 1 | $spreadsheet->getActiveSheet()->getCell($columnLetter . $row)->setValue(($hasCalculatedValue) ? $cellDataFormula : $cellData); |
|
1 ignored issue
–
show
|
|||
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 = ''; |
|
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)); |
|
1 ignored issue
–
show
|
|||
385 | |||
386 | 1 | break; |
|
387 | 1 | View Code Duplication | case 'S': |
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 = Coordinate::stringFromColumnIndex($column); |
|
423 | 1 | View Code Duplication | if (isset($this->formats[$formatStyle])) { |
424 | 1 | $spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($this->formats[$formatStyle]); |
|
425 | } |
||
426 | } |
||
427 | 1 | if ((!empty($styleData)) && ($column > '') && ($row > '')) { |
|
428 | 1 | $columnLetter = Coordinate::stringFromColumnIndex($column); |
|
429 | 1 | $spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($styleData); |
|
430 | } |
||
431 | 1 | if ($columnWidth > '') { |
|
432 | 1 | if ($startCol == $endCol) { |
|
433 | 1 | $startCol = Coordinate::stringFromColumnIndex($startCol); |
|
434 | 1 | $spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth($columnWidth); |
|
435 | } else { |
||
436 | 1 | $startCol = Coordinate::stringFromColumnIndex($startCol); |
|
437 | 1 | $endCol = Coordinate::stringFromColumnIndex($endCol); |
|
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]) { |
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() |
||
477 | } |
||
478 | |||
479 | /** |
||
480 | * Set sheet index. |
||
481 | * |
||
482 | * @param int $pValue Sheet index |
||
483 | * |
||
484 | * @return Slk |
||
485 | */ |
||
486 | public function setSheetIndex($pValue) |
||
491 | } |
||
492 | } |
||
493 |