Total Complexity | 84 |
Total Lines | 482 |
Duplicated Lines | 26.14 % |
Coverage | 75.32% |
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); |
|
1 ignored issue
–
show
|
|||
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
|
|||
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() |
||
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) |
||
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) |
|
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
|
|||
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
|
|||
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 | $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) { |
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
|
|||
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': |
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])) { |
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) { |
|
435 | 1 | $startCol = Coordinate::stringFromColumnIndex($startCol); |
|
436 | 1 | $spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth($columnWidth); |
|
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]) { |
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() |
||
479 | } |
||
480 | |||
481 | /** |
||
482 | * Set sheet index. |
||
483 | * |
||
484 | * @param int $pValue Sheet index |
||
485 | * |
||
486 | * @return Slk |
||
487 | */ |
||
488 | public function setSheetIndex($pValue) |
||
493 | } |
||
494 | } |
||
495 |