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 Html 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
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 Html, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
48 | class Html extends BaseWriter implements IWriter |
||
49 | { |
||
50 | /** |
||
51 | * Spreadsheet object. |
||
52 | * |
||
53 | * @var Spreadsheet |
||
54 | */ |
||
55 | protected $spreadsheet; |
||
56 | |||
57 | /** |
||
58 | * Sheet index to write. |
||
59 | * |
||
60 | * @var int |
||
61 | */ |
||
62 | private $sheetIndex = 0; |
||
63 | |||
64 | /** |
||
65 | * Images root. |
||
66 | * |
||
67 | * @var string |
||
68 | */ |
||
69 | private $imagesRoot = ''; |
||
70 | |||
71 | /** |
||
72 | * embed images, or link to images. |
||
73 | * |
||
74 | * @var bool |
||
75 | */ |
||
76 | private $embedImages = false; |
||
77 | |||
78 | /** |
||
79 | * Use inline CSS? |
||
80 | * |
||
81 | * @var bool |
||
82 | */ |
||
83 | private $useInlineCss = false; |
||
84 | |||
85 | /** |
||
86 | * Array of CSS styles. |
||
87 | * |
||
88 | * @var array |
||
89 | */ |
||
90 | private $cssStyles; |
||
91 | |||
92 | /** |
||
93 | * Array of column widths in points. |
||
94 | * |
||
95 | * @var array |
||
96 | */ |
||
97 | private $columnWidths; |
||
98 | |||
99 | /** |
||
100 | * Default font. |
||
101 | * |
||
102 | * @var Font |
||
103 | */ |
||
104 | private $defaultFont; |
||
105 | |||
106 | /** |
||
107 | * Flag whether spans have been calculated. |
||
108 | * |
||
109 | * @var bool |
||
110 | */ |
||
111 | private $spansAreCalculated = false; |
||
112 | |||
113 | /** |
||
114 | * Excel cells that should not be written as HTML cells. |
||
115 | * |
||
116 | * @var array |
||
117 | */ |
||
118 | private $isSpannedCell = []; |
||
119 | |||
120 | /** |
||
121 | * Excel cells that are upper-left corner in a cell merge. |
||
122 | * |
||
123 | * @var array |
||
124 | */ |
||
125 | private $isBaseCell = []; |
||
126 | |||
127 | /** |
||
128 | * Excel rows that should not be written as HTML rows. |
||
129 | * |
||
130 | * @var array |
||
131 | */ |
||
132 | private $isSpannedRow = []; |
||
133 | |||
134 | /** |
||
135 | * Is the current writer creating PDF? |
||
136 | * |
||
137 | * @var bool |
||
138 | */ |
||
139 | protected $isPdf = false; |
||
140 | |||
141 | /** |
||
142 | * Generate the Navigation block. |
||
143 | * |
||
144 | * @var bool |
||
145 | */ |
||
146 | private $generateSheetNavigationBlock = true; |
||
147 | |||
148 | /** |
||
149 | * Create a new HTML. |
||
150 | * |
||
151 | * @param Spreadsheet $spreadsheet |
||
152 | */ |
||
153 | 6 | public function __construct(Spreadsheet $spreadsheet) |
|
154 | { |
||
155 | 6 | $this->spreadsheet = $spreadsheet; |
|
156 | 6 | $this->defaultFont = $this->spreadsheet->getDefaultStyle()->getFont(); |
|
157 | 6 | } |
|
158 | |||
159 | /** |
||
160 | * Save Spreadsheet to file. |
||
161 | * |
||
162 | * @param string $pFilename |
||
163 | * |
||
164 | * @throws WriterException |
||
165 | */ |
||
166 | 3 | public function save($pFilename) |
|
167 | { |
||
168 | // garbage collect |
||
169 | 3 | $this->spreadsheet->garbageCollect(); |
|
170 | |||
171 | 3 | $saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog(); |
|
172 | 3 | Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false); |
|
173 | 3 | $saveArrayReturnType = Calculation::getArrayReturnType(); |
|
174 | 3 | Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_VALUE); |
|
175 | |||
176 | // Build CSS |
||
177 | 3 | $this->buildCSS(!$this->useInlineCss); |
|
178 | |||
179 | // Open file |
||
180 | 3 | $fileHandle = fopen($pFilename, 'wb+'); |
|
181 | 3 | if ($fileHandle === false) { |
|
182 | throw new WriterException("Could not open file $pFilename for writing."); |
||
183 | } |
||
184 | |||
185 | // Write headers |
||
186 | 3 | fwrite($fileHandle, $this->generateHTMLHeader(!$this->useInlineCss)); |
|
187 | |||
188 | // Write navigation (tabs) |
||
189 | 3 | if ((!$this->isPdf) && ($this->generateSheetNavigationBlock)) { |
|
190 | 3 | fwrite($fileHandle, $this->generateNavigation()); |
|
191 | } |
||
192 | |||
193 | // Write data |
||
194 | 3 | fwrite($fileHandle, $this->generateSheetData()); |
|
195 | |||
196 | // Write footer |
||
197 | 3 | fwrite($fileHandle, $this->generateHTMLFooter()); |
|
198 | |||
199 | // Close file |
||
200 | 3 | fclose($fileHandle); |
|
201 | |||
202 | 3 | Calculation::setArrayReturnType($saveArrayReturnType); |
|
203 | 3 | Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog); |
|
204 | 3 | } |
|
205 | |||
206 | /** |
||
207 | * Map VAlign. |
||
208 | * |
||
209 | * @param string $vAlign Vertical alignment |
||
210 | * |
||
211 | * @return string |
||
212 | */ |
||
213 | 6 | private function mapVAlign($vAlign) |
|
214 | { |
||
215 | switch ($vAlign) { |
||
216 | 6 | case Alignment::VERTICAL_BOTTOM: |
|
217 | 6 | return 'bottom'; |
|
218 | 4 | case Alignment::VERTICAL_TOP: |
|
219 | return 'top'; |
||
220 | 4 | case Alignment::VERTICAL_CENTER: |
|
221 | case Alignment::VERTICAL_JUSTIFY: |
||
222 | 4 | return 'middle'; |
|
223 | default: |
||
224 | return 'baseline'; |
||
225 | } |
||
226 | } |
||
227 | |||
228 | /** |
||
229 | * Map HAlign. |
||
230 | * |
||
231 | * @param string $hAlign Horizontal alignment |
||
232 | * |
||
233 | * @return string|false |
||
234 | */ |
||
235 | 6 | private function mapHAlign($hAlign) |
|
236 | { |
||
237 | switch ($hAlign) { |
||
238 | 6 | case Alignment::HORIZONTAL_GENERAL: |
|
239 | 6 | return false; |
|
240 | 5 | case Alignment::HORIZONTAL_LEFT: |
|
241 | 4 | return 'left'; |
|
242 | 5 | case Alignment::HORIZONTAL_RIGHT: |
|
243 | 4 | return 'right'; |
|
244 | 5 | case Alignment::HORIZONTAL_CENTER: |
|
245 | 4 | case Alignment::HORIZONTAL_CENTER_CONTINUOUS: |
|
246 | 1 | return 'center'; |
|
247 | 4 | case Alignment::HORIZONTAL_JUSTIFY: |
|
248 | 4 | return 'justify'; |
|
249 | default: |
||
250 | return false; |
||
251 | } |
||
252 | } |
||
253 | |||
254 | /** |
||
255 | * Map border style. |
||
256 | * |
||
257 | * @param int $borderStyle Sheet index |
||
258 | * |
||
259 | * @return string |
||
260 | */ |
||
261 | 6 | private function mapBorderStyle($borderStyle) |
|
262 | { |
||
263 | switch ($borderStyle) { |
||
264 | 6 | case Border::BORDER_NONE: |
|
265 | 6 | return 'none'; |
|
266 | 4 | case Border::BORDER_DASHDOT: |
|
267 | return '1px dashed'; |
||
268 | 4 | case Border::BORDER_DASHDOTDOT: |
|
269 | return '1px dotted'; |
||
270 | 4 | case Border::BORDER_DASHED: |
|
271 | return '1px dashed'; |
||
272 | 4 | case Border::BORDER_DOTTED: |
|
273 | return '1px dotted'; |
||
274 | 4 | case Border::BORDER_DOUBLE: |
|
275 | return '3px double'; |
||
276 | 4 | case Border::BORDER_HAIR: |
|
277 | return '1px solid'; |
||
278 | 4 | case Border::BORDER_MEDIUM: |
|
279 | return '2px solid'; |
||
280 | 4 | case Border::BORDER_MEDIUMDASHDOT: |
|
281 | return '2px dashed'; |
||
282 | 4 | case Border::BORDER_MEDIUMDASHDOTDOT: |
|
283 | return '2px dotted'; |
||
284 | 4 | case Border::BORDER_MEDIUMDASHED: |
|
285 | return '2px dashed'; |
||
286 | 4 | case Border::BORDER_SLANTDASHDOT: |
|
287 | return '2px dashed'; |
||
288 | 4 | case Border::BORDER_THICK: |
|
289 | 4 | return '3px solid'; |
|
290 | 4 | case Border::BORDER_THIN: |
|
291 | 4 | return '1px solid'; |
|
292 | default: |
||
293 | // map others to thin |
||
294 | return '1px solid'; |
||
295 | } |
||
296 | } |
||
297 | |||
298 | /** |
||
299 | * Get sheet index. |
||
300 | * |
||
301 | * @return int |
||
302 | */ |
||
303 | 4 | public function getSheetIndex() |
|
304 | { |
||
305 | 4 | return $this->sheetIndex; |
|
306 | } |
||
307 | |||
308 | /** |
||
309 | * Set sheet index. |
||
310 | * |
||
311 | * @param int $pValue Sheet index |
||
312 | * |
||
313 | * @return HTML |
||
314 | */ |
||
315 | public function setSheetIndex($pValue) |
||
321 | |||
322 | /** |
||
323 | * Get sheet index. |
||
324 | * |
||
325 | * @return bool |
||
326 | */ |
||
327 | public function getGenerateSheetNavigationBlock() |
||
331 | |||
332 | /** |
||
333 | * Set sheet index. |
||
334 | * |
||
335 | * @param bool $pValue Flag indicating whether the sheet navigation block should be generated or not |
||
336 | * |
||
337 | * @return HTML |
||
338 | */ |
||
339 | public function setGenerateSheetNavigationBlock($pValue) |
||
345 | |||
346 | /** |
||
347 | * Write all sheets (resets sheetIndex to NULL). |
||
348 | */ |
||
349 | public function writeAllSheets() |
||
355 | |||
356 | /** |
||
357 | * Generate HTML header. |
||
358 | * |
||
359 | * @param bool $pIncludeStyles Include styles? |
||
360 | * |
||
361 | * @throws WriterException |
||
362 | * |
||
363 | * @return string |
||
364 | */ |
||
365 | 6 | public function generateHTMLHeader($pIncludeStyles = false) |
|
366 | { |
||
367 | // Spreadsheet object known? |
||
368 | 6 | if (is_null($this->spreadsheet)) { |
|
369 | throw new WriterException('Internal Spreadsheet object not set to an instance of an object.'); |
||
370 | } |
||
371 | |||
372 | // Construct HTML |
||
373 | 6 | $properties = $this->spreadsheet->getProperties(); |
|
374 | 6 | $html = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">' . PHP_EOL; |
|
375 | 6 | $html .= '<!-- Generated by Spreadsheet - https://github.com/PHPOffice/Spreadsheet -->' . PHP_EOL; |
|
376 | 6 | $html .= '<html>' . PHP_EOL; |
|
377 | 6 | $html .= ' <head>' . PHP_EOL; |
|
378 | 6 | $html .= ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8">' . PHP_EOL; |
|
379 | 6 | if ($properties->getTitle() > '') { |
|
380 | 6 | $html .= ' <title>' . htmlspecialchars($properties->getTitle()) . '</title>' . PHP_EOL; |
|
381 | } |
||
382 | 6 | if ($properties->getCreator() > '') { |
|
383 | 6 | $html .= ' <meta name="author" content="' . htmlspecialchars($properties->getCreator()) . '" />' . PHP_EOL; |
|
384 | } |
||
385 | 6 | if ($properties->getTitle() > '') { |
|
386 | 6 | $html .= ' <meta name="title" content="' . htmlspecialchars($properties->getTitle()) . '" />' . PHP_EOL; |
|
387 | } |
||
388 | 6 | View Code Duplication | if ($properties->getDescription() > '') { |
|
|||
389 | 5 | $html .= ' <meta name="description" content="' . htmlspecialchars($properties->getDescription()) . '" />' . PHP_EOL; |
|
390 | } |
||
391 | 6 | if ($properties->getSubject() > '') { |
|
392 | 6 | $html .= ' <meta name="subject" content="' . htmlspecialchars($properties->getSubject()) . '" />' . PHP_EOL; |
|
393 | } |
||
394 | 6 | if ($properties->getKeywords() > '') { |
|
395 | 6 | $html .= ' <meta name="keywords" content="' . htmlspecialchars($properties->getKeywords()) . '" />' . PHP_EOL; |
|
396 | } |
||
397 | 6 | if ($properties->getCategory() > '') { |
|
398 | 6 | $html .= ' <meta name="category" content="' . htmlspecialchars($properties->getCategory()) . '" />' . PHP_EOL; |
|
399 | } |
||
400 | 6 | View Code Duplication | if ($properties->getCompany() > '') { |
401 | 6 | $html .= ' <meta name="company" content="' . htmlspecialchars($properties->getCompany()) . '" />' . PHP_EOL; |
|
402 | } |
||
403 | 6 | View Code Duplication | if ($properties->getManager() > '') { |
404 | $html .= ' <meta name="manager" content="' . htmlspecialchars($properties->getManager()) . '" />' . PHP_EOL; |
||
405 | } |
||
406 | |||
407 | 6 | if ($pIncludeStyles) { |
|
408 | 3 | $html .= $this->generateStyles(true); |
|
409 | } |
||
410 | |||
411 | 6 | $html .= ' </head>' . PHP_EOL; |
|
412 | 6 | $html .= '' . PHP_EOL; |
|
413 | 6 | $html .= ' <body>' . PHP_EOL; |
|
414 | |||
415 | 6 | return $html; |
|
416 | } |
||
417 | |||
418 | /** |
||
419 | * Generate sheet data. |
||
420 | * |
||
421 | * @throws WriterException |
||
422 | * |
||
423 | * @return string |
||
424 | */ |
||
425 | 6 | public function generateSheetData() |
|
426 | { |
||
427 | // Spreadsheet object known? |
||
428 | 6 | if (is_null($this->spreadsheet)) { |
|
429 | throw new WriterException('Internal Spreadsheet object not set to an instance of an object.'); |
||
430 | } |
||
431 | |||
432 | // Ensure that Spans have been calculated? |
||
433 | 6 | if ($this->sheetIndex !== null || !$this->spansAreCalculated) { |
|
434 | 6 | $this->calculateSpans(); |
|
435 | } |
||
436 | |||
437 | // Fetch sheets |
||
438 | 6 | $sheets = []; |
|
439 | 6 | View Code Duplication | if (is_null($this->sheetIndex)) { |
440 | $sheets = $this->spreadsheet->getAllSheets(); |
||
441 | } else { |
||
442 | 6 | $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex); |
|
443 | } |
||
444 | |||
445 | // Construct HTML |
||
446 | 6 | $html = ''; |
|
447 | |||
448 | // Loop all sheets |
||
449 | 6 | $sheetId = 0; |
|
450 | 6 | foreach ($sheets as $sheet) { |
|
451 | // Write table header |
||
452 | 6 | $html .= $this->generateTableHeader($sheet); |
|
453 | |||
454 | // Get worksheet dimension |
||
455 | 6 | $dimension = explode(':', $sheet->calculateWorksheetDimension()); |
|
456 | 6 | $dimension[0] = Cell::coordinateFromString($dimension[0]); |
|
457 | 6 | $dimension[0][0] = Cell::columnIndexFromString($dimension[0][0]) - 1; |
|
458 | 6 | $dimension[1] = Cell::coordinateFromString($dimension[1]); |
|
459 | 6 | $dimension[1][0] = Cell::columnIndexFromString($dimension[1][0]) - 1; |
|
460 | |||
461 | // row min,max |
||
462 | 6 | $rowMin = $dimension[0][1]; |
|
463 | 6 | $rowMax = $dimension[1][1]; |
|
464 | |||
465 | // calculate start of <tbody>, <thead> |
||
466 | 6 | $tbodyStart = $rowMin; |
|
467 | 6 | $theadStart = $theadEnd = 0; // default: no <thead> no </thead> |
|
468 | 6 | if ($sheet->getPageSetup()->isRowsToRepeatAtTopSet()) { |
|
469 | $rowsToRepeatAtTop = $sheet->getPageSetup()->getRowsToRepeatAtTop(); |
||
470 | |||
471 | // we can only support repeating rows that start at top row |
||
472 | if ($rowsToRepeatAtTop[0] == 1) { |
||
473 | $theadStart = $rowsToRepeatAtTop[0]; |
||
474 | $theadEnd = $rowsToRepeatAtTop[1]; |
||
475 | $tbodyStart = $rowsToRepeatAtTop[1] + 1; |
||
476 | } |
||
477 | } |
||
478 | |||
479 | // Loop through cells |
||
480 | 6 | $row = $rowMin - 1; |
|
481 | 6 | while ($row++ < $rowMax) { |
|
482 | // <thead> ? |
||
483 | 6 | if ($row == $theadStart) { |
|
484 | $html .= ' <thead>' . PHP_EOL; |
||
485 | $cellType = 'th'; |
||
486 | } |
||
487 | |||
488 | // <tbody> ? |
||
489 | 6 | if ($row == $tbodyStart) { |
|
490 | 6 | $html .= ' <tbody>' . PHP_EOL; |
|
491 | 6 | $cellType = 'td'; |
|
492 | } |
||
493 | |||
494 | // Write row if there are HTML table cells in it |
||
495 | 6 | if (!isset($this->isSpannedRow[$sheet->getParent()->getIndex($sheet)][$row])) { |
|
496 | // Start a new rowData |
||
497 | 6 | $rowData = []; |
|
498 | // Loop through columns |
||
499 | 6 | $column = $dimension[0][0] - 1; |
|
500 | 6 | while ($column++ < $dimension[1][0]) { |
|
501 | // Cell exists? |
||
502 | 6 | if ($sheet->cellExistsByColumnAndRow($column, $row)) { |
|
503 | 6 | $rowData[$column] = Cell::stringFromColumnIndex($column) . $row; |
|
504 | } else { |
||
505 | 4 | $rowData[$column] = ''; |
|
506 | } |
||
507 | } |
||
508 | 6 | $html .= $this->generateRow($sheet, $rowData, $row - 1, $cellType); |
|
509 | } |
||
510 | |||
511 | // </thead> ? |
||
512 | 6 | if ($row == $theadEnd) { |
|
513 | $html .= ' </thead>' . PHP_EOL; |
||
514 | } |
||
515 | } |
||
516 | 6 | $html .= $this->extendRowsForChartsAndImages($sheet, $row); |
|
517 | |||
518 | // Close table body. |
||
519 | 6 | $html .= ' </tbody>' . PHP_EOL; |
|
520 | |||
521 | // Write table footer |
||
522 | 6 | $html .= $this->generateTableFooter(); |
|
523 | |||
524 | // Writing PDF? |
||
525 | 6 | if ($this->isPdf) { |
|
526 | 4 | if (is_null($this->sheetIndex) && $sheetId + 1 < $this->spreadsheet->getSheetCount()) { |
|
527 | $html .= '<div style="page-break-before:always" />'; |
||
528 | } |
||
529 | } |
||
530 | |||
531 | // Next sheet |
||
532 | 6 | ++$sheetId; |
|
533 | } |
||
534 | |||
535 | 6 | return $html; |
|
536 | } |
||
537 | |||
538 | /** |
||
539 | * Generate sheet tabs. |
||
540 | * |
||
541 | * @throws WriterException |
||
542 | * |
||
543 | * @return string |
||
544 | */ |
||
545 | 3 | public function generateNavigation() |
|
546 | { |
||
547 | // Spreadsheet object known? |
||
548 | 3 | if (is_null($this->spreadsheet)) { |
|
549 | throw new WriterException('Internal Spreadsheet object not set to an instance of an object.'); |
||
550 | } |
||
551 | |||
552 | // Fetch sheets |
||
553 | 3 | $sheets = []; |
|
554 | 3 | View Code Duplication | if (is_null($this->sheetIndex)) { |
555 | $sheets = $this->spreadsheet->getAllSheets(); |
||
556 | } else { |
||
557 | 3 | $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex); |
|
558 | } |
||
559 | |||
560 | // Construct HTML |
||
561 | 3 | $html = ''; |
|
562 | |||
563 | // Only if there are more than 1 sheets |
||
564 | 3 | if (count($sheets) > 1) { |
|
565 | // Loop all sheets |
||
566 | $sheetId = 0; |
||
567 | |||
568 | $html .= '<ul class="navigation">' . PHP_EOL; |
||
569 | |||
570 | foreach ($sheets as $sheet) { |
||
571 | $html .= ' <li class="sheet' . $sheetId . '"><a href="#sheet' . $sheetId . '">' . $sheet->getTitle() . '</a></li>' . PHP_EOL; |
||
572 | ++$sheetId; |
||
573 | } |
||
574 | |||
575 | $html .= '</ul>' . PHP_EOL; |
||
576 | } |
||
577 | |||
578 | 3 | return $html; |
|
579 | } |
||
580 | |||
581 | 6 | private function extendRowsForChartsAndImages(Worksheet $pSheet, $row) |
|
582 | { |
||
583 | 6 | $rowMax = $row; |
|
584 | 6 | $colMax = 'A'; |
|
585 | 6 | if ($this->includeCharts) { |
|
586 | foreach ($pSheet->getChartCollection() as $chart) { |
||
587 | if ($chart instanceof Chart) { |
||
588 | $chartCoordinates = $chart->getTopLeftPosition(); |
||
589 | $chartTL = Cell::coordinateFromString($chartCoordinates['cell']); |
||
590 | $chartCol = Cell::columnIndexFromString($chartTL[0]); |
||
591 | if ($chartTL[1] > $rowMax) { |
||
592 | $rowMax = $chartTL[1]; |
||
593 | if ($chartCol > Cell::columnIndexFromString($colMax)) { |
||
594 | $colMax = $chartTL[0]; |
||
595 | } |
||
596 | } |
||
597 | } |
||
598 | } |
||
599 | } |
||
600 | |||
601 | 6 | foreach ($pSheet->getDrawingCollection() as $drawing) { |
|
602 | 5 | if ($drawing instanceof Drawing) { |
|
603 | 4 | $imageTL = Cell::coordinateFromString($drawing->getCoordinates()); |
|
604 | 4 | $imageCol = Cell::columnIndexFromString($imageTL[0]); |
|
605 | 4 | if ($imageTL[1] > $rowMax) { |
|
606 | $rowMax = $imageTL[1]; |
||
607 | if ($imageCol > Cell::columnIndexFromString($colMax)) { |
||
608 | 5 | $colMax = $imageTL[0]; |
|
609 | } |
||
610 | } |
||
611 | } |
||
612 | } |
||
613 | |||
614 | // Don't extend rows if not needed |
||
615 | 6 | if ($row === $rowMax) { |
|
616 | 6 | return ''; |
|
617 | } |
||
618 | |||
619 | $html = ''; |
||
620 | ++$colMax; |
||
621 | |||
622 | while ($row <= $rowMax) { |
||
623 | $html .= '<tr>'; |
||
624 | for ($col = 'A'; $col != $colMax; ++$col) { |
||
625 | $html .= '<td>'; |
||
626 | $html .= $this->writeImageInCell($pSheet, $col . $row); |
||
627 | if ($this->includeCharts) { |
||
628 | $html .= $this->writeChartInCell($pSheet, $col . $row); |
||
629 | } |
||
630 | $html .= '</td>'; |
||
631 | } |
||
632 | ++$row; |
||
633 | $html .= '</tr>'; |
||
634 | } |
||
635 | |||
636 | return $html; |
||
637 | } |
||
638 | |||
639 | /** |
||
640 | * Generate image tag in cell. |
||
641 | * |
||
642 | * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet |
||
643 | * @param string $coordinates Cell coordinates |
||
644 | * |
||
645 | * @throws WriterException |
||
646 | * |
||
647 | * @return string |
||
648 | */ |
||
649 | 6 | private function writeImageInCell(Worksheet $pSheet, $coordinates) |
|
650 | { |
||
651 | // Construct HTML |
||
652 | 6 | $html = ''; |
|
653 | |||
654 | // Write images |
||
655 | 6 | foreach ($pSheet->getDrawingCollection() as $drawing) { |
|
656 | 5 | if ($drawing instanceof Drawing) { |
|
657 | 4 | if ($drawing->getCoordinates() == $coordinates) { |
|
658 | 4 | $filename = $drawing->getPath(); |
|
659 | |||
660 | // Strip off eventual '.' |
||
661 | 4 | if (substr($filename, 0, 1) == '.') { |
|
662 | $filename = substr($filename, 1); |
||
663 | } |
||
664 | |||
665 | // Prepend images root |
||
666 | 4 | $filename = $this->getImagesRoot() . $filename; |
|
667 | |||
668 | // Strip off eventual '.' |
||
669 | 4 | if (substr($filename, 0, 1) == '.' && substr($filename, 0, 2) != './') { |
|
670 | $filename = substr($filename, 1); |
||
671 | } |
||
672 | |||
673 | // Convert UTF8 data to PCDATA |
||
674 | 4 | $filename = htmlspecialchars($filename); |
|
675 | |||
676 | 4 | $html .= PHP_EOL; |
|
677 | 4 | if ((!$this->embedImages) || ($this->isPdf)) { |
|
678 | 4 | $imageData = $filename; |
|
679 | } else { |
||
680 | $imageDetails = getimagesize($filename); |
||
681 | if ($fp = fopen($filename, 'rb', 0)) { |
||
682 | $picture = fread($fp, filesize($filename)); |
||
683 | fclose($fp); |
||
684 | // base64 encode the binary data, then break it |
||
685 | // into chunks according to RFC 2045 semantics |
||
686 | $base64 = chunk_split(base64_encode($picture)); |
||
687 | $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64; |
||
688 | } else { |
||
689 | $imageData = $filename; |
||
690 | } |
||
691 | } |
||
692 | |||
693 | 4 | $html .= '<div style="position: relative;">'; |
|
694 | $html .= '<img style="position: absolute; z-index: 1; left: ' . |
||
695 | 4 | $drawing->getOffsetX() . 'px; top: ' . $drawing->getOffsetY() . 'px; width: ' . |
|
696 | 4 | $drawing->getWidth() . 'px; height: ' . $drawing->getHeight() . 'px;" src="' . |
|
697 | 4 | $imageData . '" border="0" />'; |
|
698 | 4 | $html .= '</div>'; |
|
699 | } |
||
700 | } elseif ($drawing instanceof MemoryDrawing) { |
||
701 | 1 | if ($drawing->getCoordinates() != $coordinates) { |
|
702 | continue; |
||
703 | } |
||
704 | 1 | ob_start(); // Let's start output buffering. |
|
705 | 1 | imagepng($drawing->getImageResource()); // This will normally output the image, but because of ob_start(), it won't. |
|
706 | 1 | $contents = ob_get_contents(); // Instead, output above is saved to $contents |
|
707 | 1 | ob_end_clean(); // End the output buffer. |
|
708 | |||
709 | 1 | $dataUri = 'data:image/jpeg;base64,' . base64_encode($contents); |
|
710 | |||
711 | // Because of the nature of tables, width is more important than height. |
||
712 | // max-width: 100% ensures that image doesnt overflow containing cell |
||
713 | // width: X sets width of supplied image. |
||
714 | // As a result, images bigger than cell will be contained and images smaller will not get stretched |
||
715 | 5 | $html .= '<img src="' . $dataUri . '" style="max-width:100%;width:' . $drawing->getWidth() . 'px;" />'; |
|
716 | } |
||
717 | } |
||
718 | |||
719 | 6 | return $html; |
|
720 | } |
||
721 | |||
722 | /** |
||
723 | * Generate chart tag in cell. |
||
724 | * |
||
725 | * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet |
||
726 | * @param string $coordinates Cell coordinates |
||
727 | * |
||
728 | * @throws WriterException |
||
729 | * |
||
730 | * @return string |
||
731 | */ |
||
732 | private function writeChartInCell(Worksheet $pSheet, $coordinates) |
||
770 | |||
771 | /** |
||
772 | * Generate CSS styles. |
||
773 | * |
||
774 | * @param bool $generateSurroundingHTML Generate surrounding HTML tags? (<style> and </style>) |
||
775 | * |
||
776 | * @throws WriterException |
||
777 | * |
||
778 | * @return string |
||
779 | */ |
||
780 | 3 | public function generateStyles($generateSurroundingHTML = true) |
|
781 | { |
||
814 | |||
815 | /** |
||
816 | * Build CSS styles. |
||
817 | * |
||
818 | * @param bool $generateSurroundingHTML Generate surrounding HTML style? (html { }) |
||
819 | * |
||
820 | * @throws WriterException |
||
821 | * |
||
822 | * @return array |
||
823 | */ |
||
824 | 6 | public function buildCSS($generateSurroundingHTML = true) |
|
971 | |||
972 | /** |
||
973 | * Create CSS style. |
||
974 | * |
||
975 | * @param Style $pStyle |
||
976 | * |
||
977 | * @return array |
||
978 | */ |
||
979 | 6 | private function createCSSStyle(Style $pStyle) |
|
992 | |||
993 | /** |
||
994 | * Create CSS style (\PhpOffice\PhpSpreadsheet\Style\Alignment). |
||
995 | * |
||
996 | * @param Alignment $pStyle \PhpOffice\PhpSpreadsheet\Style\Alignment |
||
997 | * |
||
998 | * @return array |
||
999 | */ |
||
1000 | 6 | private function createCSSStyleAlignment(Alignment $pStyle) |
|
1016 | |||
1017 | /** |
||
1018 | * Create CSS style (\PhpOffice\PhpSpreadsheet\Style\Font). |
||
1019 | * |
||
1020 | * @param Font $pStyle |
||
1021 | * |
||
1022 | * @return array |
||
1023 | */ |
||
1024 | 6 | private function createCSSStyleFont(Font $pStyle) |
|
1050 | |||
1051 | /** |
||
1052 | * Create CSS style (Borders). |
||
1053 | * |
||
1054 | * @param Borders $pStyle Borders |
||
1055 | * |
||
1056 | * @return array |
||
1057 | */ |
||
1058 | 6 | private function createCSSStyleBorders(Borders $pStyle) |
|
1071 | |||
1072 | /** |
||
1073 | * Create CSS style (Border). |
||
1074 | * |
||
1075 | * @param Border $pStyle Border |
||
1076 | * |
||
1077 | * @return string |
||
1078 | */ |
||
1079 | 6 | private function createCSSStyleBorder(Border $pStyle) |
|
1087 | |||
1088 | /** |
||
1089 | * Create CSS style (Fill). |
||
1090 | * |
||
1091 | * @param Fill $pStyle Fill |
||
1092 | * |
||
1093 | * @return array |
||
1094 | */ |
||
1095 | 6 | private function createCSSStyleFill(Fill $pStyle) |
|
1107 | |||
1108 | /** |
||
1109 | * Generate HTML footer. |
||
1110 | */ |
||
1111 | 6 | public function generateHTMLFooter() |
|
1120 | |||
1121 | /** |
||
1122 | * Generate table header. |
||
1123 | * |
||
1124 | * @param Worksheet $pSheet The worksheet for the table we are writing |
||
1125 | * |
||
1126 | * @throws WriterException |
||
1127 | * |
||
1128 | * @return string |
||
1129 | */ |
||
1130 | 6 | private function generateTableHeader($pSheet) |
|
1169 | |||
1170 | /** |
||
1171 | * Generate table footer. |
||
1172 | * |
||
1173 | * @throws WriterException |
||
1174 | */ |
||
1175 | 6 | private function generateTableFooter() |
|
1181 | |||
1182 | /** |
||
1183 | * Generate row. |
||
1184 | * |
||
1185 | * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet |
||
1186 | * @param array $pValues Array containing cells in a row |
||
1187 | * @param int $pRow Row number (0-based) |
||
1188 | * @param mixed $cellType eg: 'td' |
||
1189 | * |
||
1190 | * @throws WriterException |
||
1191 | * |
||
1192 | * @return string |
||
1193 | */ |
||
1194 | 6 | private function generateRow(Worksheet $pSheet, array $pValues, $pRow, $cellType) |
|
1432 | |||
1433 | /** |
||
1434 | * Takes array where of CSS properties / values and converts to CSS string. |
||
1435 | * |
||
1436 | * @param array |
||
1437 | * @param mixed $pValue |
||
1438 | * |
||
1439 | * @return string |
||
1440 | */ |
||
1441 | 6 | private function assembleCSS($pValue = []) |
|
1451 | |||
1452 | /** |
||
1453 | * Get images root. |
||
1454 | * |
||
1455 | * @return string |
||
1456 | */ |
||
1457 | 4 | public function getImagesRoot() |
|
1461 | |||
1462 | /** |
||
1463 | * Set images root. |
||
1464 | * |
||
1465 | * @param string $pValue |
||
1466 | * |
||
1467 | * @return HTML |
||
1468 | */ |
||
1469 | public function setImagesRoot($pValue) |
||
1475 | |||
1476 | /** |
||
1477 | * Get embed images. |
||
1478 | * |
||
1479 | * @return bool |
||
1480 | */ |
||
1481 | public function getEmbedImages() |
||
1485 | |||
1486 | /** |
||
1487 | * Set embed images. |
||
1488 | * |
||
1489 | * @param bool $pValue |
||
1490 | * |
||
1491 | * @return HTML |
||
1492 | */ |
||
1493 | public function setEmbedImages($pValue) |
||
1499 | |||
1500 | /** |
||
1501 | * Get use inline CSS? |
||
1502 | * |
||
1503 | * @return bool |
||
1504 | */ |
||
1505 | public function getUseInlineCss() |
||
1509 | |||
1510 | /** |
||
1511 | * Set use inline CSS? |
||
1512 | * |
||
1513 | * @param bool $pValue |
||
1514 | * |
||
1515 | * @return HTML |
||
1516 | */ |
||
1517 | 4 | public function setUseInlineCss($pValue) |
|
1523 | |||
1524 | /** |
||
1525 | * Add color to formatted string as inline style. |
||
1526 | * |
||
1527 | * @param string $pValue Plain formatted value without color |
||
1528 | * @param string $pFormat Format code |
||
1529 | * |
||
1530 | * @return string |
||
1531 | */ |
||
1532 | 4 | public function formatColor($pValue, $pFormat) |
|
1554 | |||
1555 | /** |
||
1556 | * Calculate information about HTML colspan and rowspan which is not always the same as Excel's. |
||
1557 | */ |
||
1558 | 6 | private function calculateSpans() |
|
1644 | |||
1645 | 6 | private function setMargins(Worksheet $pSheet) |
|
1668 | } |
||
1669 |
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.