Total Complexity | 230 |
Total Lines | 1659 |
Duplicated Lines | 0 % |
Coverage | 75.66% |
Changes | 5 | ||
Bugs | 0 | Features | 0 |
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.
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 |
||
28 | class Html extends BaseWriter |
||
29 | { |
||
30 | /** |
||
31 | * Spreadsheet object. |
||
32 | * |
||
33 | * @var Spreadsheet |
||
34 | */ |
||
35 | protected $spreadsheet; |
||
36 | |||
37 | /** |
||
38 | * Sheet index to write. |
||
39 | * |
||
40 | * @var int |
||
41 | */ |
||
42 | private $sheetIndex = 0; |
||
43 | |||
44 | /** |
||
45 | * Images root. |
||
46 | * |
||
47 | * @var string |
||
48 | */ |
||
49 | private $imagesRoot = ''; |
||
50 | |||
51 | /** |
||
52 | * embed images, or link to images. |
||
53 | * |
||
54 | * @var bool |
||
55 | */ |
||
56 | private $embedImages = false; |
||
57 | |||
58 | /** |
||
59 | * Use inline CSS? |
||
60 | * |
||
61 | * @var bool |
||
62 | */ |
||
63 | private $useInlineCss = false; |
||
64 | |||
65 | /** |
||
66 | * Use embedded CSS? |
||
67 | * |
||
68 | * @var bool |
||
69 | */ |
||
70 | private $useEmbeddedCSS = true; |
||
71 | |||
72 | /** |
||
73 | * Array of CSS styles. |
||
74 | * |
||
75 | * @var array |
||
76 | */ |
||
77 | private $cssStyles; |
||
78 | |||
79 | /** |
||
80 | * Array of column widths in points. |
||
81 | * |
||
82 | * @var array |
||
83 | */ |
||
84 | private $columnWidths; |
||
85 | |||
86 | /** |
||
87 | * Default font. |
||
88 | * |
||
89 | * @var Font |
||
90 | */ |
||
91 | private $defaultFont; |
||
92 | |||
93 | /** |
||
94 | * Flag whether spans have been calculated. |
||
95 | * |
||
96 | * @var bool |
||
97 | */ |
||
98 | private $spansAreCalculated = false; |
||
99 | |||
100 | /** |
||
101 | * Excel cells that should not be written as HTML cells. |
||
102 | * |
||
103 | * @var array |
||
104 | */ |
||
105 | private $isSpannedCell = []; |
||
106 | |||
107 | /** |
||
108 | * Excel cells that are upper-left corner in a cell merge. |
||
109 | * |
||
110 | * @var array |
||
111 | */ |
||
112 | private $isBaseCell = []; |
||
113 | |||
114 | /** |
||
115 | * Excel rows that should not be written as HTML rows. |
||
116 | * |
||
117 | * @var array |
||
118 | */ |
||
119 | private $isSpannedRow = []; |
||
120 | |||
121 | /** |
||
122 | * Is the current writer creating PDF? |
||
123 | * |
||
124 | * @var bool |
||
125 | */ |
||
126 | protected $isPdf = false; |
||
127 | |||
128 | /** |
||
129 | * Generate the Navigation block. |
||
130 | * |
||
131 | * @var bool |
||
132 | */ |
||
133 | private $generateSheetNavigationBlock = true; |
||
134 | |||
135 | /** |
||
136 | * Create a new HTML. |
||
137 | * |
||
138 | * @param Spreadsheet $spreadsheet |
||
139 | */ |
||
140 | 18 | public function __construct(Spreadsheet $spreadsheet) |
|
141 | { |
||
142 | 18 | $this->spreadsheet = $spreadsheet; |
|
143 | 18 | $this->defaultFont = $this->spreadsheet->getDefaultStyle()->getFont(); |
|
144 | 18 | } |
|
145 | |||
146 | /** |
||
147 | * Save Spreadsheet to file. |
||
148 | * |
||
149 | * @param string $pFilename |
||
150 | * |
||
151 | * @throws WriterException |
||
152 | */ |
||
153 | 11 | public function save($pFilename) |
|
154 | { |
||
155 | // garbage collect |
||
156 | 11 | $this->spreadsheet->garbageCollect(); |
|
157 | |||
158 | 11 | $saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog(); |
|
159 | 11 | Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false); |
|
160 | 11 | $saveArrayReturnType = Calculation::getArrayReturnType(); |
|
161 | 11 | Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_VALUE); |
|
162 | |||
163 | // Build CSS |
||
164 | 11 | $this->buildCSS(!$this->useInlineCss); |
|
165 | |||
166 | // Open file |
||
167 | 11 | $fileHandle = fopen($pFilename, 'wb+'); |
|
168 | 11 | if ($fileHandle === false) { |
|
169 | throw new WriterException("Could not open file $pFilename for writing."); |
||
170 | } |
||
171 | |||
172 | // Write headers |
||
173 | 11 | fwrite($fileHandle, $this->generateHTMLHeader(!$this->useInlineCss)); |
|
174 | |||
175 | // Write navigation (tabs) |
||
176 | 11 | if ((!$this->isPdf) && ($this->generateSheetNavigationBlock)) { |
|
177 | 11 | fwrite($fileHandle, $this->generateNavigation()); |
|
178 | } |
||
179 | |||
180 | // Write data |
||
181 | 11 | fwrite($fileHandle, $this->generateSheetData()); |
|
182 | |||
183 | // Write footer |
||
184 | 11 | fwrite($fileHandle, $this->generateHTMLFooter()); |
|
185 | |||
186 | // Close file |
||
187 | 11 | fclose($fileHandle); |
|
188 | |||
189 | 11 | Calculation::setArrayReturnType($saveArrayReturnType); |
|
190 | 11 | Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog); |
|
191 | 11 | } |
|
192 | |||
193 | /** |
||
194 | * Map VAlign. |
||
195 | * |
||
196 | * @param string $vAlign Vertical alignment |
||
197 | * |
||
198 | * @return string |
||
199 | */ |
||
200 | 13 | private function mapVAlign($vAlign) |
|
201 | { |
||
202 | 13 | switch ($vAlign) { |
|
203 | case Alignment::VERTICAL_BOTTOM: |
||
204 | 13 | return 'bottom'; |
|
205 | case Alignment::VERTICAL_TOP: |
||
206 | return 'top'; |
||
207 | case Alignment::VERTICAL_CENTER: |
||
208 | case Alignment::VERTICAL_JUSTIFY: |
||
209 | 3 | return 'middle'; |
|
210 | default: |
||
211 | return 'baseline'; |
||
212 | } |
||
213 | } |
||
214 | |||
215 | /** |
||
216 | * Map HAlign. |
||
217 | * |
||
218 | * @param string $hAlign Horizontal alignment |
||
219 | * |
||
220 | * @return false|string |
||
221 | */ |
||
222 | 13 | private function mapHAlign($hAlign) |
|
223 | { |
||
224 | 13 | switch ($hAlign) { |
|
225 | case Alignment::HORIZONTAL_GENERAL: |
||
226 | 13 | return false; |
|
227 | case Alignment::HORIZONTAL_LEFT: |
||
228 | 3 | return 'left'; |
|
229 | case Alignment::HORIZONTAL_RIGHT: |
||
230 | 3 | return 'right'; |
|
231 | case Alignment::HORIZONTAL_CENTER: |
||
232 | case Alignment::HORIZONTAL_CENTER_CONTINUOUS: |
||
233 | 1 | return 'center'; |
|
234 | case Alignment::HORIZONTAL_JUSTIFY: |
||
235 | 3 | return 'justify'; |
|
236 | default: |
||
237 | return false; |
||
238 | } |
||
239 | } |
||
240 | |||
241 | /** |
||
242 | * Map border style. |
||
243 | * |
||
244 | * @param int $borderStyle Sheet index |
||
245 | * |
||
246 | * @return string |
||
247 | */ |
||
248 | 13 | private function mapBorderStyle($borderStyle) |
|
249 | { |
||
250 | 13 | switch ($borderStyle) { |
|
251 | case Border::BORDER_NONE: |
||
252 | 13 | return 'none'; |
|
253 | case Border::BORDER_DASHDOT: |
||
254 | return '1px dashed'; |
||
255 | case Border::BORDER_DASHDOTDOT: |
||
256 | return '1px dotted'; |
||
257 | case Border::BORDER_DASHED: |
||
258 | return '1px dashed'; |
||
259 | case Border::BORDER_DOTTED: |
||
260 | return '1px dotted'; |
||
261 | case Border::BORDER_DOUBLE: |
||
262 | return '3px double'; |
||
263 | case Border::BORDER_HAIR: |
||
264 | return '1px solid'; |
||
265 | case Border::BORDER_MEDIUM: |
||
266 | return '2px solid'; |
||
267 | case Border::BORDER_MEDIUMDASHDOT: |
||
268 | return '2px dashed'; |
||
269 | case Border::BORDER_MEDIUMDASHDOTDOT: |
||
270 | return '2px dotted'; |
||
271 | case Border::BORDER_MEDIUMDASHED: |
||
272 | return '2px dashed'; |
||
273 | case Border::BORDER_SLANTDASHDOT: |
||
274 | return '2px dashed'; |
||
275 | case Border::BORDER_THICK: |
||
276 | 3 | return '3px solid'; |
|
277 | case Border::BORDER_THIN: |
||
278 | 3 | return '1px solid'; |
|
279 | default: |
||
280 | // map others to thin |
||
281 | return '1px solid'; |
||
282 | } |
||
283 | } |
||
284 | |||
285 | /** |
||
286 | * Get sheet index. |
||
287 | * |
||
288 | * @return int |
||
289 | */ |
||
290 | 3 | public function getSheetIndex() |
|
291 | { |
||
292 | 3 | return $this->sheetIndex; |
|
293 | } |
||
294 | |||
295 | /** |
||
296 | * Set sheet index. |
||
297 | * |
||
298 | * @param int $pValue Sheet index |
||
299 | * |
||
300 | * @return HTML |
||
301 | */ |
||
302 | public function setSheetIndex($pValue) |
||
303 | { |
||
304 | $this->sheetIndex = $pValue; |
||
305 | |||
306 | return $this; |
||
307 | } |
||
308 | |||
309 | /** |
||
310 | * Get sheet index. |
||
311 | * |
||
312 | * @return bool |
||
313 | */ |
||
314 | public function getGenerateSheetNavigationBlock() |
||
317 | } |
||
318 | |||
319 | /** |
||
320 | * Set sheet index. |
||
321 | * |
||
322 | * @param bool $pValue Flag indicating whether the sheet navigation block should be generated or not |
||
323 | * |
||
324 | * @return HTML |
||
325 | */ |
||
326 | public function setGenerateSheetNavigationBlock($pValue) |
||
327 | { |
||
328 | $this->generateSheetNavigationBlock = (bool) $pValue; |
||
329 | |||
330 | return $this; |
||
331 | } |
||
332 | |||
333 | /** |
||
334 | * Write all sheets (resets sheetIndex to NULL). |
||
335 | */ |
||
336 | public function writeAllSheets() |
||
337 | { |
||
338 | $this->sheetIndex = null; |
||
339 | |||
340 | return $this; |
||
341 | } |
||
342 | |||
343 | /** |
||
344 | * Generate HTML header. |
||
345 | * |
||
346 | * @param bool $pIncludeStyles Include styles? |
||
347 | * |
||
348 | * @throws WriterException |
||
349 | * |
||
350 | * @return string |
||
351 | */ |
||
352 | 13 | public function generateHTMLHeader($pIncludeStyles = false) |
|
398 | } |
||
399 | |||
400 | /** |
||
401 | * Generate sheet data. |
||
402 | * |
||
403 | * @throws WriterException |
||
404 | * |
||
405 | * @return string |
||
406 | */ |
||
407 | 13 | public function generateSheetData() |
|
408 | { |
||
409 | // Ensure that Spans have been calculated? |
||
410 | 13 | if ($this->sheetIndex !== null || !$this->spansAreCalculated) { |
|
411 | 13 | $this->calculateSpans(); |
|
412 | } |
||
413 | |||
414 | // Fetch sheets |
||
415 | 13 | $sheets = []; |
|
416 | 13 | if ($this->sheetIndex === null) { |
|
417 | $sheets = $this->spreadsheet->getAllSheets(); |
||
418 | } else { |
||
419 | 13 | $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex); |
|
420 | } |
||
421 | |||
422 | // Construct HTML |
||
423 | 13 | $html = ''; |
|
424 | |||
425 | // Loop all sheets |
||
426 | 13 | $sheetId = 0; |
|
427 | 13 | foreach ($sheets as $sheet) { |
|
428 | // Write table header |
||
429 | 13 | $html .= $this->generateTableHeader($sheet); |
|
430 | |||
431 | // Get worksheet dimension |
||
432 | 13 | $dimension = explode(':', $sheet->calculateWorksheetDimension()); |
|
433 | 13 | $dimension[0] = Coordinate::coordinateFromString($dimension[0]); |
|
434 | 13 | $dimension[0][0] = Coordinate::columnIndexFromString($dimension[0][0]); |
|
435 | 13 | $dimension[1] = Coordinate::coordinateFromString($dimension[1]); |
|
436 | 13 | $dimension[1][0] = Coordinate::columnIndexFromString($dimension[1][0]); |
|
437 | |||
438 | // row min,max |
||
439 | 13 | $rowMin = $dimension[0][1]; |
|
440 | 13 | $rowMax = $dimension[1][1]; |
|
441 | |||
442 | // calculate start of <tbody>, <thead> |
||
443 | 13 | $tbodyStart = $rowMin; |
|
444 | 13 | $theadStart = $theadEnd = 0; // default: no <thead> no </thead> |
|
445 | 13 | if ($sheet->getPageSetup()->isRowsToRepeatAtTopSet()) { |
|
446 | $rowsToRepeatAtTop = $sheet->getPageSetup()->getRowsToRepeatAtTop(); |
||
447 | |||
448 | // we can only support repeating rows that start at top row |
||
449 | if ($rowsToRepeatAtTop[0] == 1) { |
||
450 | $theadStart = $rowsToRepeatAtTop[0]; |
||
451 | $theadEnd = $rowsToRepeatAtTop[1]; |
||
452 | $tbodyStart = $rowsToRepeatAtTop[1] + 1; |
||
453 | } |
||
454 | } |
||
455 | |||
456 | // Loop through cells |
||
457 | 13 | $row = $rowMin - 1; |
|
458 | 13 | while ($row++ < $rowMax) { |
|
459 | // <thead> ? |
||
460 | 13 | if ($row == $theadStart) { |
|
461 | $html .= ' <thead>' . PHP_EOL; |
||
462 | $cellType = 'th'; |
||
463 | } |
||
464 | |||
465 | // <tbody> ? |
||
466 | 13 | if ($row == $tbodyStart) { |
|
467 | 13 | $html .= ' <tbody>' . PHP_EOL; |
|
468 | 13 | $cellType = 'td'; |
|
469 | } |
||
470 | |||
471 | // Write row if there are HTML table cells in it |
||
472 | 13 | if (!isset($this->isSpannedRow[$sheet->getParent()->getIndex($sheet)][$row])) { |
|
473 | // Start a new rowData |
||
474 | 13 | $rowData = []; |
|
475 | // Loop through columns |
||
476 | 13 | $column = $dimension[0][0]; |
|
477 | 13 | while ($column <= $dimension[1][0]) { |
|
478 | // Cell exists? |
||
479 | 13 | if ($sheet->cellExistsByColumnAndRow($column, $row)) { |
|
480 | 13 | $rowData[$column] = Coordinate::stringFromColumnIndex($column) . $row; |
|
481 | } else { |
||
482 | 5 | $rowData[$column] = ''; |
|
483 | } |
||
484 | 13 | ++$column; |
|
485 | } |
||
486 | 13 | $html .= $this->generateRow($sheet, $rowData, $row - 1, $cellType); |
|
|
|||
487 | } |
||
488 | |||
489 | // </thead> ? |
||
490 | 13 | if ($row == $theadEnd) { |
|
491 | $html .= ' </thead>' . PHP_EOL; |
||
492 | } |
||
493 | } |
||
494 | 13 | $html .= $this->extendRowsForChartsAndImages($sheet, $row); |
|
495 | |||
496 | // Close table body. |
||
497 | 13 | $html .= ' </tbody>' . PHP_EOL; |
|
498 | |||
499 | // Write table footer |
||
500 | 13 | $html .= $this->generateTableFooter(); |
|
501 | |||
502 | // Writing PDF? |
||
503 | 13 | if ($this->isPdf) { |
|
504 | 3 | if ($this->sheetIndex === null && $sheetId + 1 < $this->spreadsheet->getSheetCount()) { |
|
505 | $html .= '<div style="page-break-before:always" />'; |
||
506 | } |
||
507 | } |
||
508 | |||
509 | // Next sheet |
||
510 | 13 | ++$sheetId; |
|
511 | } |
||
512 | |||
513 | 13 | return $html; |
|
514 | } |
||
515 | |||
516 | /** |
||
517 | * Generate sheet tabs. |
||
518 | * |
||
519 | * @throws WriterException |
||
520 | * |
||
521 | * @return string |
||
522 | */ |
||
523 | 11 | public function generateNavigation() |
|
524 | { |
||
525 | // Fetch sheets |
||
526 | 11 | $sheets = []; |
|
527 | 11 | if ($this->sheetIndex === null) { |
|
528 | $sheets = $this->spreadsheet->getAllSheets(); |
||
529 | } else { |
||
530 | 11 | $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex); |
|
531 | } |
||
532 | |||
533 | // Construct HTML |
||
534 | 11 | $html = ''; |
|
535 | |||
536 | // Only if there are more than 1 sheets |
||
537 | 11 | if (count($sheets) > 1) { |
|
538 | // Loop all sheets |
||
539 | $sheetId = 0; |
||
540 | |||
541 | $html .= '<ul class="navigation">' . PHP_EOL; |
||
542 | |||
543 | foreach ($sheets as $sheet) { |
||
544 | $html .= ' <li class="sheet' . $sheetId . '"><a href="#sheet' . $sheetId . '">' . $sheet->getTitle() . '</a></li>' . PHP_EOL; |
||
545 | ++$sheetId; |
||
546 | } |
||
547 | |||
548 | $html .= '</ul>' . PHP_EOL; |
||
549 | } |
||
550 | |||
551 | 11 | return $html; |
|
552 | } |
||
553 | |||
554 | 13 | private function extendRowsForChartsAndImages(Worksheet $pSheet, $row) |
|
555 | { |
||
556 | 13 | $rowMax = $row; |
|
557 | 13 | $colMax = 'A'; |
|
558 | 13 | if ($this->includeCharts) { |
|
559 | foreach ($pSheet->getChartCollection() as $chart) { |
||
560 | if ($chart instanceof Chart) { |
||
561 | $chartCoordinates = $chart->getTopLeftPosition(); |
||
562 | $chartTL = Coordinate::coordinateFromString($chartCoordinates['cell']); |
||
563 | $chartCol = Coordinate::columnIndexFromString($chartTL[0]); |
||
564 | if ($chartTL[1] > $rowMax) { |
||
565 | $rowMax = $chartTL[1]; |
||
566 | if ($chartCol > Coordinate::columnIndexFromString($colMax)) { |
||
567 | $colMax = $chartTL[0]; |
||
568 | } |
||
569 | } |
||
570 | } |
||
571 | } |
||
572 | } |
||
573 | |||
574 | 13 | foreach ($pSheet->getDrawingCollection() as $drawing) { |
|
575 | 4 | if ($drawing instanceof Drawing) { |
|
576 | 3 | $imageTL = Coordinate::coordinateFromString($drawing->getCoordinates()); |
|
577 | 3 | $imageCol = Coordinate::columnIndexFromString($imageTL[0]); |
|
578 | 3 | if ($imageTL[1] > $rowMax) { |
|
579 | $rowMax = $imageTL[1]; |
||
580 | if ($imageCol > Coordinate::columnIndexFromString($colMax)) { |
||
581 | $colMax = $imageTL[0]; |
||
582 | } |
||
583 | } |
||
584 | } |
||
585 | } |
||
586 | |||
587 | // Don't extend rows if not needed |
||
588 | 13 | if ($row === $rowMax) { |
|
589 | 13 | return ''; |
|
590 | } |
||
591 | |||
592 | $html = ''; |
||
593 | ++$colMax; |
||
594 | |||
595 | while ($row <= $rowMax) { |
||
596 | $html .= '<tr>'; |
||
597 | for ($col = 'A'; $col != $colMax; ++$col) { |
||
598 | $html .= '<td>'; |
||
599 | $html .= $this->writeImageInCell($pSheet, $col . $row); |
||
600 | if ($this->includeCharts) { |
||
601 | $html .= $this->writeChartInCell($pSheet, $col . $row); |
||
602 | } |
||
603 | $html .= '</td>'; |
||
604 | } |
||
605 | ++$row; |
||
606 | $html .= '</tr>'; |
||
607 | } |
||
608 | |||
609 | return $html; |
||
610 | } |
||
611 | |||
612 | /** |
||
613 | * Generate image tag in cell. |
||
614 | * |
||
615 | * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet |
||
616 | * @param string $coordinates Cell coordinates |
||
617 | * |
||
618 | * @return string |
||
619 | */ |
||
620 | 13 | private function writeImageInCell(Worksheet $pSheet, $coordinates) |
|
694 | } |
||
695 | |||
696 | /** |
||
697 | * Generate chart tag in cell. |
||
698 | * |
||
699 | * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet |
||
700 | * @param string $coordinates Cell coordinates |
||
701 | * |
||
702 | * @return string |
||
703 | */ |
||
704 | private function writeChartInCell(Worksheet $pSheet, $coordinates) |
||
741 | } |
||
742 | |||
743 | /** |
||
744 | * Generate CSS styles. |
||
745 | * |
||
746 | * @param bool $generateSurroundingHTML Generate surrounding HTML tags? (<style> and </style>) |
||
747 | * |
||
748 | * @throws WriterException |
||
749 | * |
||
750 | * @return string |
||
751 | */ |
||
752 | 11 | public function generateStyles($generateSurroundingHTML = true) |
|
780 | } |
||
781 | |||
782 | /** |
||
783 | * Build CSS styles. |
||
784 | * |
||
785 | * @param bool $generateSurroundingHTML Generate surrounding HTML style? (html { }) |
||
786 | * |
||
787 | * @throws WriterException |
||
788 | * |
||
789 | * @return array |
||
790 | */ |
||
791 | 13 | public function buildCSS($generateSurroundingHTML = true) |
|
951 | } |
||
952 | |||
953 | /** |
||
954 | * Create CSS style. |
||
955 | * |
||
956 | * @param Style $pStyle |
||
957 | * |
||
958 | * @return array |
||
959 | */ |
||
960 | 13 | private function createCSSStyle(Style $pStyle) |
|
961 | { |
||
962 | // Create CSS |
||
963 | 13 | return array_merge( |
|
964 | 13 | $this->createCSSStyleAlignment($pStyle->getAlignment()), |
|
965 | 13 | $this->createCSSStyleBorders($pStyle->getBorders()), |
|
966 | 13 | $this->createCSSStyleFont($pStyle->getFont()), |
|
967 | 13 | $this->createCSSStyleFill($pStyle->getFill()) |
|
968 | ); |
||
969 | } |
||
970 | |||
971 | /** |
||
972 | * Create CSS style (\PhpOffice\PhpSpreadsheet\Style\Alignment). |
||
973 | * |
||
974 | * @param Alignment $pStyle \PhpOffice\PhpSpreadsheet\Style\Alignment |
||
975 | * |
||
976 | * @return array |
||
977 | */ |
||
978 | 13 | private function createCSSStyleAlignment(Alignment $pStyle) |
|
979 | { |
||
980 | // Construct CSS |
||
981 | 13 | $css = []; |
|
982 | |||
983 | // Create CSS |
||
984 | 13 | $css['vertical-align'] = $this->mapVAlign($pStyle->getVertical()); |
|
985 | 13 | if ($textAlign = $this->mapHAlign($pStyle->getHorizontal())) { |
|
986 | 4 | $css['text-align'] = $textAlign; |
|
987 | 4 | if (in_array($textAlign, ['left', 'right'])) { |
|
988 | 3 | $css['padding-' . $textAlign] = (string) ((int) $pStyle->getIndent() * 9) . 'px'; |
|
989 | } |
||
990 | } |
||
991 | |||
992 | 13 | return $css; |
|
993 | } |
||
994 | |||
995 | /** |
||
996 | * Create CSS style (\PhpOffice\PhpSpreadsheet\Style\Font). |
||
997 | * |
||
998 | * @param Font $pStyle |
||
999 | * |
||
1000 | * @return array |
||
1001 | */ |
||
1002 | 13 | private function createCSSStyleFont(Font $pStyle) |
|
1003 | { |
||
1004 | // Construct CSS |
||
1005 | 13 | $css = []; |
|
1006 | |||
1007 | // Create CSS |
||
1008 | 13 | if ($pStyle->getBold()) { |
|
1009 | 4 | $css['font-weight'] = 'bold'; |
|
1010 | } |
||
1011 | 13 | if ($pStyle->getUnderline() != Font::UNDERLINE_NONE && $pStyle->getStrikethrough()) { |
|
1012 | $css['text-decoration'] = 'underline line-through'; |
||
1013 | 13 | } elseif ($pStyle->getUnderline() != Font::UNDERLINE_NONE) { |
|
1014 | 3 | $css['text-decoration'] = 'underline'; |
|
1015 | 13 | } elseif ($pStyle->getStrikethrough()) { |
|
1016 | $css['text-decoration'] = 'line-through'; |
||
1017 | } |
||
1018 | 13 | if ($pStyle->getItalic()) { |
|
1019 | 3 | $css['font-style'] = 'italic'; |
|
1020 | } |
||
1021 | |||
1022 | 13 | $css['color'] = '#' . $pStyle->getColor()->getRGB(); |
|
1023 | 13 | $css['font-family'] = '\'' . $pStyle->getName() . '\''; |
|
1024 | 13 | $css['font-size'] = $pStyle->getSize() . 'pt'; |
|
1025 | |||
1026 | 13 | return $css; |
|
1027 | } |
||
1028 | |||
1029 | /** |
||
1030 | * Create CSS style (Borders). |
||
1031 | * |
||
1032 | * @param Borders $pStyle Borders |
||
1033 | * |
||
1034 | * @return array |
||
1035 | */ |
||
1036 | 13 | private function createCSSStyleBorders(Borders $pStyle) |
|
1037 | { |
||
1038 | // Construct CSS |
||
1039 | 13 | $css = []; |
|
1040 | |||
1041 | // Create CSS |
||
1042 | 13 | $css['border-bottom'] = $this->createCSSStyleBorder($pStyle->getBottom()); |
|
1043 | 13 | $css['border-top'] = $this->createCSSStyleBorder($pStyle->getTop()); |
|
1044 | 13 | $css['border-left'] = $this->createCSSStyleBorder($pStyle->getLeft()); |
|
1045 | 13 | $css['border-right'] = $this->createCSSStyleBorder($pStyle->getRight()); |
|
1046 | |||
1047 | 13 | return $css; |
|
1048 | } |
||
1049 | |||
1050 | /** |
||
1051 | * Create CSS style (Border). |
||
1052 | * |
||
1053 | * @param Border $pStyle Border |
||
1054 | * |
||
1055 | * @return string |
||
1056 | */ |
||
1057 | 13 | private function createCSSStyleBorder(Border $pStyle) |
|
1058 | { |
||
1059 | // Create CSS - add !important to non-none border styles for merged cells |
||
1060 | 13 | $borderStyle = $this->mapBorderStyle($pStyle->getBorderStyle()); |
|
1061 | |||
1062 | 13 | return $borderStyle . ' #' . $pStyle->getColor()->getRGB() . (($borderStyle == 'none') ? '' : ' !important'); |
|
1063 | } |
||
1064 | |||
1065 | /** |
||
1066 | * Create CSS style (Fill). |
||
1067 | * |
||
1068 | * @param Fill $pStyle Fill |
||
1069 | * |
||
1070 | * @return array |
||
1071 | */ |
||
1072 | 13 | private function createCSSStyleFill(Fill $pStyle) |
|
1073 | { |
||
1074 | // Construct HTML |
||
1075 | 13 | $css = []; |
|
1076 | |||
1077 | // Create CSS |
||
1078 | 13 | $value = $pStyle->getFillType() == Fill::FILL_NONE ? |
|
1079 | 13 | 'white' : '#' . $pStyle->getStartColor()->getRGB(); |
|
1080 | 13 | $css['background-color'] = $value; |
|
1081 | |||
1082 | 13 | return $css; |
|
1083 | } |
||
1084 | |||
1085 | /** |
||
1086 | * Generate HTML footer. |
||
1087 | */ |
||
1088 | 13 | public function generateHTMLFooter() |
|
1096 | } |
||
1097 | |||
1098 | /** |
||
1099 | * Generate table header. |
||
1100 | * |
||
1101 | * @param Worksheet $pSheet The worksheet for the table we are writing |
||
1102 | * |
||
1103 | * @return string |
||
1104 | */ |
||
1105 | 13 | private function generateTableHeader($pSheet) |
|
1106 | { |
||
1107 | 13 | $sheetIndex = $pSheet->getParent()->getIndex($pSheet); |
|
1108 | |||
1109 | // Construct HTML |
||
1110 | 13 | $html = ''; |
|
1111 | 13 | if ($this->useEmbeddedCSS) { |
|
1112 | 13 | $html .= $this->setMargins($pSheet); |
|
1113 | } |
||
1114 | |||
1115 | 13 | if (!$this->useInlineCss) { |
|
1116 | 11 | $gridlines = $pSheet->getShowGridlines() ? ' gridlines' : ''; |
|
1117 | 11 | $html .= ' <table border="0" cellpadding="0" cellspacing="0" id="sheet' . $sheetIndex . '" class="sheet' . $sheetIndex . $gridlines . '">' . PHP_EOL; |
|
1118 | } else { |
||
1119 | 3 | $style = isset($this->cssStyles['table']) ? |
|
1120 | 3 | $this->assembleCSS($this->cssStyles['table']) : ''; |
|
1121 | |||
1122 | 3 | if ($this->isPdf && $pSheet->getShowGridlines()) { |
|
1123 | 1 | $html .= ' <table border="1" cellpadding="1" id="sheet' . $sheetIndex . '" cellspacing="1" style="' . $style . '">' . PHP_EOL; |
|
1124 | } else { |
||
1125 | 2 | $html .= ' <table border="0" cellpadding="1" id="sheet' . $sheetIndex . '" cellspacing="0" style="' . $style . '">' . PHP_EOL; |
|
1126 | } |
||
1127 | } |
||
1128 | |||
1129 | // Write <col> elements |
||
1130 | 13 | $highestColumnIndex = Coordinate::columnIndexFromString($pSheet->getHighestColumn()) - 1; |
|
1131 | 13 | $i = -1; |
|
1132 | 13 | while ($i++ < $highestColumnIndex) { |
|
1133 | 13 | if (!$this->isPdf) { |
|
1134 | 11 | if (!$this->useInlineCss) { |
|
1135 | 11 | $html .= ' <col class="col' . $i . '">' . PHP_EOL; |
|
1136 | } else { |
||
1137 | $style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) ? |
||
1138 | $this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) : ''; |
||
1139 | $html .= ' <col style="' . $style . '">' . PHP_EOL; |
||
1140 | } |
||
1141 | } |
||
1142 | } |
||
1143 | |||
1144 | 13 | return $html; |
|
1145 | } |
||
1146 | |||
1147 | /** |
||
1148 | * Generate table footer. |
||
1149 | */ |
||
1150 | 13 | private function generateTableFooter() |
|
1151 | { |
||
1152 | 13 | return ' </table>' . PHP_EOL; |
|
1153 | } |
||
1154 | |||
1155 | /** |
||
1156 | * Generate row. |
||
1157 | * |
||
1158 | * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet |
||
1159 | * @param array $pValues Array containing cells in a row |
||
1160 | * @param int $pRow Row number (0-based) |
||
1161 | * @param string $cellType eg: 'td' |
||
1162 | * |
||
1163 | * @throws WriterException |
||
1164 | * |
||
1165 | * @return string |
||
1166 | */ |
||
1167 | 13 | private function generateRow(Worksheet $pSheet, array $pValues, $pRow, $cellType) |
|
1168 | { |
||
1169 | // Construct HTML |
||
1170 | 13 | $html = ''; |
|
1171 | |||
1172 | // Sheet index |
||
1173 | 13 | $sheetIndex = $pSheet->getParent()->getIndex($pSheet); |
|
1174 | |||
1175 | // Dompdf and breaks |
||
1176 | 13 | if ($this->isPdf && count($pSheet->getBreaks()) > 0) { |
|
1177 | $breaks = $pSheet->getBreaks(); |
||
1178 | |||
1179 | // check if a break is needed before this row |
||
1180 | if (isset($breaks['A' . $pRow])) { |
||
1181 | // close table: </table> |
||
1182 | $html .= $this->generateTableFooter(); |
||
1183 | |||
1184 | // insert page break |
||
1185 | $html .= '<div style="page-break-before:always" />'; |
||
1186 | |||
1187 | // open table again: <table> + <col> etc. |
||
1188 | $html .= $this->generateTableHeader($pSheet); |
||
1189 | } |
||
1190 | } |
||
1191 | |||
1192 | // Write row start |
||
1193 | 13 | if (!$this->useInlineCss) { |
|
1194 | 11 | $html .= ' <tr class="row' . $pRow . '">' . PHP_EOL; |
|
1195 | } else { |
||
1196 | 3 | $style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]) |
|
1197 | 3 | ? $this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]) : ''; |
|
1198 | |||
1199 | 3 | $html .= ' <tr style="' . $style . '">' . PHP_EOL; |
|
1200 | } |
||
1201 | |||
1202 | // Write cells |
||
1203 | 13 | $colNum = 0; |
|
1204 | 13 | foreach ($pValues as $cellAddress) { |
|
1205 | 13 | $cell = ($cellAddress > '') ? $pSheet->getCell($cellAddress) : ''; |
|
1206 | 13 | $coordinate = Coordinate::stringFromColumnIndex($colNum + 1) . ($pRow + 1); |
|
1207 | 13 | if (!$this->useInlineCss) { |
|
1208 | 11 | $cssClass = 'column' . $colNum; |
|
1209 | } else { |
||
1210 | 3 | $cssClass = []; |
|
1211 | 3 | if ($cellType == 'th') { |
|
1212 | if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' th.column' . $colNum])) { |
||
1213 | $this->cssStyles['table.sheet' . $sheetIndex . ' th.column' . $colNum]; |
||
1214 | } |
||
1215 | } else { |
||
1216 | 3 | if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum])) { |
|
1217 | $this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum]; |
||
1218 | } |
||
1219 | } |
||
1220 | } |
||
1221 | 13 | $colSpan = 1; |
|
1222 | 13 | $rowSpan = 1; |
|
1223 | |||
1224 | // initialize |
||
1225 | 13 | $cellData = ' '; |
|
1226 | |||
1227 | // Cell |
||
1228 | 13 | if ($cell instanceof Cell) { |
|
1229 | 13 | $cellData = ''; |
|
1230 | 13 | if ($cell->getParent() === null) { |
|
1231 | $cell->attach($pSheet); |
||
1232 | } |
||
1233 | // Value |
||
1234 | 13 | if ($cell->getValue() instanceof RichText) { |
|
1235 | // Loop through rich text elements |
||
1236 | 4 | $elements = $cell->getValue()->getRichTextElements(); |
|
1237 | 4 | foreach ($elements as $element) { |
|
1238 | // Rich text start? |
||
1239 | 4 | if ($element instanceof Run) { |
|
1240 | 4 | $cellData .= '<span style="' . $this->assembleCSS($this->createCSSStyleFont($element->getFont())) . '">'; |
|
1241 | |||
1242 | 4 | if ($element->getFont()->getSuperscript()) { |
|
1243 | $cellData .= '<sup>'; |
||
1244 | 4 | } elseif ($element->getFont()->getSubscript()) { |
|
1245 | $cellData .= '<sub>'; |
||
1246 | } |
||
1247 | } |
||
1248 | |||
1249 | // Convert UTF8 data to PCDATA |
||
1250 | 4 | $cellText = $element->getText(); |
|
1251 | 4 | $cellData .= htmlspecialchars($cellText); |
|
1252 | |||
1253 | 4 | if ($element instanceof Run) { |
|
1254 | 4 | if ($element->getFont()->getSuperscript()) { |
|
1255 | $cellData .= '</sup>'; |
||
1256 | 4 | } elseif ($element->getFont()->getSubscript()) { |
|
1257 | $cellData .= '</sub>'; |
||
1258 | } |
||
1259 | |||
1260 | 4 | $cellData .= '</span>'; |
|
1261 | } |
||
1262 | } |
||
1263 | } else { |
||
1264 | 13 | if ($this->preCalculateFormulas) { |
|
1265 | 13 | $cellData = NumberFormat::toFormattedString( |
|
1266 | 13 | $cell->getCalculatedValue(), |
|
1267 | 13 | $pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode(), |
|
1268 | 13 | [$this, 'formatColor'] |
|
1269 | ); |
||
1270 | } else { |
||
1271 | $cellData = NumberFormat::toFormattedString( |
||
1272 | $cell->getValue(), |
||
1273 | $pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode(), |
||
1274 | [$this, 'formatColor'] |
||
1275 | ); |
||
1276 | } |
||
1277 | 13 | $cellData = htmlspecialchars($cellData); |
|
1278 | 13 | if ($pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSuperscript()) { |
|
1279 | $cellData = '<sup>' . $cellData . '</sup>'; |
||
1280 | 13 | } elseif ($pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSubscript()) { |
|
1281 | $cellData = '<sub>' . $cellData . '</sub>'; |
||
1282 | } |
||
1283 | } |
||
1284 | |||
1285 | // Converts the cell content so that spaces occuring at beginning of each new line are replaced by |
||
1286 | // Example: " Hello\n to the world" is converted to " Hello\n to the world" |
||
1287 | 13 | $cellData = preg_replace('/(?m)(?:^|\\G) /', ' ', $cellData); |
|
1288 | |||
1289 | // convert newline "\n" to '<br>' |
||
1290 | 13 | $cellData = nl2br($cellData); |
|
1291 | |||
1292 | // Extend CSS class? |
||
1293 | 13 | if (!$this->useInlineCss) { |
|
1294 | 11 | $cssClass .= ' style' . $cell->getXfIndex(); |
|
1295 | 11 | $cssClass .= ' ' . $cell->getDataType(); |
|
1296 | } else { |
||
1297 | 3 | if ($cellType == 'th') { |
|
1298 | if (isset($this->cssStyles['th.style' . $cell->getXfIndex()])) { |
||
1299 | $cssClass = array_merge($cssClass, $this->cssStyles['th.style' . $cell->getXfIndex()]); |
||
1300 | } |
||
1301 | } else { |
||
1302 | 3 | if (isset($this->cssStyles['td.style' . $cell->getXfIndex()])) { |
|
1303 | 3 | $cssClass = array_merge($cssClass, $this->cssStyles['td.style' . $cell->getXfIndex()]); |
|
1304 | } |
||
1305 | } |
||
1306 | |||
1307 | // General horizontal alignment: Actual horizontal alignment depends on dataType |
||
1308 | 3 | $sharedStyle = $pSheet->getParent()->getCellXfByIndex($cell->getXfIndex()); |
|
1309 | 3 | if ($sharedStyle->getAlignment()->getHorizontal() == Alignment::HORIZONTAL_GENERAL |
|
1310 | 3 | && isset($this->cssStyles['.' . $cell->getDataType()]['text-align']) |
|
1311 | ) { |
||
1312 | 3 | $cssClass['text-align'] = $this->cssStyles['.' . $cell->getDataType()]['text-align']; |
|
1313 | } |
||
1314 | } |
||
1315 | } |
||
1316 | |||
1317 | // Hyperlink? |
||
1318 | 13 | if ($pSheet->hyperlinkExists($coordinate) && !$pSheet->getHyperlink($coordinate)->isInternal()) { |
|
1319 | 3 | $cellData = '<a href="' . htmlspecialchars($pSheet->getHyperlink($coordinate)->getUrl()) . '" title="' . htmlspecialchars($pSheet->getHyperlink($coordinate)->getTooltip()) . '">' . $cellData . '</a>'; |
|
1320 | } |
||
1321 | |||
1322 | // Should the cell be written or is it swallowed by a rowspan or colspan? |
||
1323 | 13 | $writeCell = !(isset($this->isSpannedCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum]) |
|
1324 | 13 | && $this->isSpannedCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum]); |
|
1325 | |||
1326 | // Colspan and Rowspan |
||
1327 | 13 | $colspan = 1; |
|
1328 | 13 | $rowspan = 1; |
|
1329 | 13 | if (isset($this->isBaseCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum])) { |
|
1330 | 5 | $spans = $this->isBaseCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum]; |
|
1331 | 5 | $rowSpan = $spans['rowspan']; |
|
1332 | 5 | $colSpan = $spans['colspan']; |
|
1333 | |||
1334 | // Also apply style from last cell in merge to fix borders - |
||
1335 | // relies on !important for non-none border declarations in createCSSStyleBorder |
||
1336 | 5 | $endCellCoord = Coordinate::stringFromColumnIndex($colNum + $colSpan) . ($pRow + $rowSpan); |
|
1337 | 5 | if (!$this->useInlineCss) { |
|
1338 | 3 | $cssClass .= ' style' . $pSheet->getCell($endCellCoord)->getXfIndex(); |
|
1339 | } |
||
1340 | } |
||
1341 | |||
1342 | // Write |
||
1343 | 13 | if ($writeCell) { |
|
1344 | // Column start |
||
1345 | 13 | $html .= ' <' . $cellType; |
|
1346 | 13 | if (!$this->useInlineCss) { |
|
1347 | 11 | $html .= ' class="' . $cssClass . '"'; |
|
1 ignored issue
–
show
|
|||
1348 | } else { |
||
1349 | //** Necessary redundant code for the sake of \PhpOffice\PhpSpreadsheet\Writer\Pdf ** |
||
1350 | // We must explicitly write the width of the <td> element because TCPDF |
||
1351 | // does not recognize e.g. <col style="width:42pt"> |
||
1352 | 3 | $width = 0; |
|
1353 | 3 | $i = $colNum - 1; |
|
1354 | 3 | $e = $colNum + $colSpan - 1; |
|
1355 | 3 | while ($i++ < $e) { |
|
1356 | 3 | if (isset($this->columnWidths[$sheetIndex][$i])) { |
|
1357 | 3 | $width += $this->columnWidths[$sheetIndex][$i]; |
|
1358 | } |
||
1359 | } |
||
1360 | 3 | $cssClass['width'] = $width . 'pt'; |
|
1361 | |||
1362 | // We must also explicitly write the height of the <td> element because TCPDF |
||
1363 | // does not recognize e.g. <tr style="height:50pt"> |
||
1364 | 3 | if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]['height'])) { |
|
1365 | 1 | $height = $this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]['height']; |
|
1366 | 1 | $cssClass['height'] = $height; |
|
1367 | } |
||
1368 | //** end of redundant code ** |
||
1369 | |||
1370 | 3 | $html .= ' style="' . $this->assembleCSS($cssClass) . '"'; |
|
1 ignored issue
–
show
|
|||
1371 | } |
||
1372 | 13 | if ($colSpan > 1) { |
|
1373 | 5 | $html .= ' colspan="' . $colSpan . '"'; |
|
1374 | } |
||
1375 | 13 | if ($rowSpan > 1) { |
|
1376 | $html .= ' rowspan="' . $rowSpan . '"'; |
||
1377 | } |
||
1378 | 13 | $html .= '>'; |
|
1379 | |||
1380 | 13 | $html .= $this->writeComment($pSheet, $coordinate); |
|
1381 | |||
1382 | // Image? |
||
1383 | 13 | $html .= $this->writeImageInCell($pSheet, $coordinate); |
|
1384 | |||
1385 | // Chart? |
||
1386 | 13 | if ($this->includeCharts) { |
|
1387 | $html .= $this->writeChartInCell($pSheet, $coordinate); |
||
1388 | } |
||
1389 | |||
1390 | // Cell data |
||
1391 | 13 | $html .= $cellData; |
|
1392 | |||
1393 | // Column end |
||
1394 | 13 | $html .= '</' . $cellType . '>' . PHP_EOL; |
|
1395 | } |
||
1396 | |||
1397 | // Next column |
||
1398 | 13 | ++$colNum; |
|
1399 | } |
||
1400 | |||
1401 | // Write row end |
||
1402 | 13 | $html .= ' </tr>' . PHP_EOL; |
|
1403 | |||
1404 | // Return |
||
1405 | 13 | return $html; |
|
1406 | } |
||
1407 | |||
1408 | /** |
||
1409 | * Takes array where of CSS properties / values and converts to CSS string. |
||
1410 | * |
||
1411 | * @param array $pValue |
||
1412 | * |
||
1413 | * @return string |
||
1414 | */ |
||
1415 | 13 | private function assembleCSS(array $pValue = []) |
|
1416 | { |
||
1417 | 13 | $pairs = []; |
|
1418 | 13 | foreach ($pValue as $property => $value) { |
|
1419 | 13 | $pairs[] = $property . ':' . $value; |
|
1420 | } |
||
1421 | 13 | $string = implode('; ', $pairs); |
|
1422 | |||
1423 | 13 | return $string; |
|
1424 | } |
||
1425 | |||
1426 | /** |
||
1427 | * Get images root. |
||
1428 | * |
||
1429 | * @return string |
||
1430 | */ |
||
1431 | 3 | public function getImagesRoot() |
|
1432 | { |
||
1433 | 3 | return $this->imagesRoot; |
|
1434 | } |
||
1435 | |||
1436 | /** |
||
1437 | * Set images root. |
||
1438 | * |
||
1439 | * @param string $pValue |
||
1440 | * |
||
1441 | * @return HTML |
||
1442 | */ |
||
1443 | public function setImagesRoot($pValue) |
||
1444 | { |
||
1445 | $this->imagesRoot = $pValue; |
||
1446 | |||
1447 | return $this; |
||
1448 | } |
||
1449 | |||
1450 | /** |
||
1451 | * Get embed images. |
||
1452 | * |
||
1453 | * @return bool |
||
1454 | */ |
||
1455 | public function getEmbedImages() |
||
1456 | { |
||
1457 | return $this->embedImages; |
||
1458 | } |
||
1459 | |||
1460 | /** |
||
1461 | * Set embed images. |
||
1462 | * |
||
1463 | * @param bool $pValue |
||
1464 | * |
||
1465 | * @return HTML |
||
1466 | */ |
||
1467 | public function setEmbedImages($pValue) |
||
1468 | { |
||
1469 | $this->embedImages = $pValue; |
||
1470 | |||
1471 | return $this; |
||
1472 | } |
||
1473 | |||
1474 | /** |
||
1475 | * Get use inline CSS? |
||
1476 | * |
||
1477 | * @return bool |
||
1478 | */ |
||
1479 | public function getUseInlineCss() |
||
1480 | { |
||
1481 | return $this->useInlineCss; |
||
1482 | } |
||
1483 | |||
1484 | /** |
||
1485 | * Set use inline CSS? |
||
1486 | * |
||
1487 | * @param bool $pValue |
||
1488 | * |
||
1489 | * @return HTML |
||
1490 | */ |
||
1491 | 7 | public function setUseInlineCss($pValue) |
|
1492 | { |
||
1493 | 7 | $this->useInlineCss = $pValue; |
|
1494 | |||
1495 | 7 | return $this; |
|
1496 | } |
||
1497 | |||
1498 | /** |
||
1499 | * Get use embedded CSS? |
||
1500 | * |
||
1501 | * @return bool |
||
1502 | */ |
||
1503 | public function getUseEmbeddedCSS() |
||
1504 | { |
||
1505 | return $this->useEmbeddedCSS; |
||
1506 | } |
||
1507 | |||
1508 | /** |
||
1509 | * Set use embedded CSS? |
||
1510 | * |
||
1511 | * @param bool $pValue |
||
1512 | * |
||
1513 | * @return HTML |
||
1514 | */ |
||
1515 | public function setUseEmbeddedCSS($pValue) |
||
1516 | { |
||
1517 | $this->useEmbeddedCSS = $pValue; |
||
1518 | |||
1519 | return $this; |
||
1520 | } |
||
1521 | |||
1522 | /** |
||
1523 | * Add color to formatted string as inline style. |
||
1524 | * |
||
1525 | * @param string $pValue Plain formatted value without color |
||
1526 | * @param string $pFormat Format code |
||
1527 | * |
||
1528 | * @return string |
||
1529 | */ |
||
1530 | 3 | public function formatColor($pValue, $pFormat) |
|
1531 | { |
||
1532 | // Color information, e.g. [Red] is always at the beginning |
||
1533 | 3 | $color = null; // initialize |
|
1534 | 3 | $matches = []; |
|
1535 | |||
1536 | 3 | $color_regex = '/^\\[[a-zA-Z]+\\]/'; |
|
1537 | 3 | if (preg_match($color_regex, $pFormat, $matches)) { |
|
1538 | $color = str_replace(['[', ']'], '', $matches[0]); |
||
1539 | $color = strtolower($color); |
||
1540 | } |
||
1541 | |||
1542 | // convert to PCDATA |
||
1543 | 3 | $value = htmlspecialchars($pValue); |
|
1544 | |||
1545 | // color span tag |
||
1546 | 3 | if ($color !== null) { |
|
1547 | $value = '<span style="color:' . $color . '">' . $value . '</span>'; |
||
1548 | } |
||
1549 | |||
1550 | 3 | return $value; |
|
1551 | } |
||
1552 | |||
1553 | /** |
||
1554 | * Calculate information about HTML colspan and rowspan which is not always the same as Excel's. |
||
1555 | */ |
||
1556 | 13 | private function calculateSpans() |
|
1557 | { |
||
1558 | // Identify all cells that should be omitted in HTML due to cell merge. |
||
1559 | // In HTML only the upper-left cell should be written and it should have |
||
1560 | // appropriate rowspan / colspan attribute |
||
1561 | 13 | $sheetIndexes = $this->sheetIndex !== null ? |
|
1562 | 13 | [$this->sheetIndex] : range(0, $this->spreadsheet->getSheetCount() - 1); |
|
1563 | |||
1564 | 13 | foreach ($sheetIndexes as $sheetIndex) { |
|
1565 | 13 | $sheet = $this->spreadsheet->getSheet($sheetIndex); |
|
1566 | |||
1567 | 13 | $candidateSpannedRow = []; |
|
1568 | |||
1569 | // loop through all Excel merged cells |
||
1570 | 13 | foreach ($sheet->getMergeCells() as $cells) { |
|
1571 | 5 | [$cells] = Coordinate::splitRange($cells); |
|
1572 | 5 | $first = $cells[0]; |
|
1573 | 5 | $last = $cells[1]; |
|
1574 | |||
1575 | 5 | [$fc, $fr] = Coordinate::coordinateFromString($first); |
|
1576 | 5 | $fc = Coordinate::columnIndexFromString($fc) - 1; |
|
1577 | |||
1578 | 5 | [$lc, $lr] = Coordinate::coordinateFromString($last); |
|
1579 | 5 | $lc = Coordinate::columnIndexFromString($lc) - 1; |
|
1580 | |||
1581 | // loop through the individual cells in the individual merge |
||
1582 | 5 | $r = $fr - 1; |
|
1583 | 5 | while ($r++ < $lr) { |
|
1584 | // also, flag this row as a HTML row that is candidate to be omitted |
||
1585 | 5 | $candidateSpannedRow[$r] = $r; |
|
1586 | |||
1587 | 5 | $c = $fc - 1; |
|
1588 | 5 | while ($c++ < $lc) { |
|
1589 | 5 | if (!($c == $fc && $r == $fr)) { |
|
1590 | // not the upper-left cell (should not be written in HTML) |
||
1591 | 5 | $this->isSpannedCell[$sheetIndex][$r][$c] = [ |
|
1592 | 5 | 'baseCell' => [$fr, $fc], |
|
1593 | ]; |
||
1594 | } else { |
||
1595 | // upper-left is the base cell that should hold the colspan/rowspan attribute |
||
1596 | 5 | $this->isBaseCell[$sheetIndex][$r][$c] = [ |
|
1597 | 5 | 'xlrowspan' => $lr - $fr + 1, // Excel rowspan |
|
1598 | 5 | 'rowspan' => $lr - $fr + 1, // HTML rowspan, value may change |
|
1599 | 5 | 'xlcolspan' => $lc - $fc + 1, // Excel colspan |
|
1600 | 5 | 'colspan' => $lc - $fc + 1, // HTML colspan, value may change |
|
1601 | ]; |
||
1602 | } |
||
1603 | } |
||
1604 | } |
||
1605 | } |
||
1606 | |||
1607 | // Identify which rows should be omitted in HTML. These are the rows where all the cells |
||
1608 | // participate in a merge and the where base cells are somewhere above. |
||
1609 | 13 | $countColumns = Coordinate::columnIndexFromString($sheet->getHighestColumn()); |
|
1610 | 13 | foreach ($candidateSpannedRow as $rowIndex) { |
|
1611 | 5 | if (isset($this->isSpannedCell[$sheetIndex][$rowIndex])) { |
|
1612 | 5 | if (count($this->isSpannedCell[$sheetIndex][$rowIndex]) == $countColumns) { |
|
1613 | 3 | $this->isSpannedRow[$sheetIndex][$rowIndex] = $rowIndex; |
|
1614 | } |
||
1615 | } |
||
1616 | } |
||
1617 | |||
1618 | // For each of the omitted rows we found above, the affected rowspans should be subtracted by 1 |
||
1619 | 13 | if (isset($this->isSpannedRow[$sheetIndex])) { |
|
1620 | 3 | foreach ($this->isSpannedRow[$sheetIndex] as $rowIndex) { |
|
1621 | 3 | $adjustedBaseCells = []; |
|
1622 | 3 | $c = -1; |
|
1623 | 3 | $e = $countColumns - 1; |
|
1624 | 3 | while ($c++ < $e) { |
|
1625 | 3 | $baseCell = $this->isSpannedCell[$sheetIndex][$rowIndex][$c]['baseCell']; |
|
1626 | |||
1627 | 3 | if (!in_array($baseCell, $adjustedBaseCells)) { |
|
1628 | // subtract rowspan by 1 |
||
1629 | 3 | --$this->isBaseCell[$sheetIndex][$baseCell[0]][$baseCell[1]]['rowspan']; |
|
1630 | 3 | $adjustedBaseCells[] = $baseCell; |
|
1631 | } |
||
1632 | } |
||
1633 | } |
||
1634 | } |
||
1635 | |||
1636 | // TODO: Same for columns |
||
1637 | } |
||
1638 | |||
1639 | // We have calculated the spans |
||
1640 | 13 | $this->spansAreCalculated = true; |
|
1641 | 13 | } |
|
1642 | |||
1643 | 13 | private function setMargins(Worksheet $pSheet) |
|
1644 | { |
||
1645 | 13 | $htmlPage = '@page { '; |
|
1646 | 13 | $htmlBody = 'body { '; |
|
1647 | |||
1648 | 13 | $left = StringHelper::formatNumber($pSheet->getPageMargins()->getLeft()) . 'in; '; |
|
1649 | 13 | $htmlPage .= 'margin-left: ' . $left; |
|
1650 | 13 | $htmlBody .= 'margin-left: ' . $left; |
|
1651 | 13 | $right = StringHelper::formatNumber($pSheet->getPageMargins()->getRight()) . 'in; '; |
|
1652 | 13 | $htmlPage .= 'margin-right: ' . $right; |
|
1653 | 13 | $htmlBody .= 'margin-right: ' . $right; |
|
1654 | 13 | $top = StringHelper::formatNumber($pSheet->getPageMargins()->getTop()) . 'in; '; |
|
1655 | 13 | $htmlPage .= 'margin-top: ' . $top; |
|
1656 | 13 | $htmlBody .= 'margin-top: ' . $top; |
|
1657 | 13 | $bottom = StringHelper::formatNumber($pSheet->getPageMargins()->getBottom()) . 'in; '; |
|
1658 | 13 | $htmlPage .= 'margin-bottom: ' . $bottom; |
|
1659 | 13 | $htmlBody .= 'margin-bottom: ' . $bottom; |
|
1660 | |||
1661 | 13 | $htmlPage .= "}\n"; |
|
1662 | 13 | $htmlBody .= "}\n"; |
|
1663 | |||
1664 | 13 | return "<style>\n" . $htmlPage . $htmlBody . "</style>\n"; |
|
1665 | } |
||
1666 | |||
1667 | /** |
||
1668 | * Write a comment in the same format as LibreOffice. |
||
1669 | * |
||
1670 | * @see https://github.com/LibreOffice/core/blob/9fc9bf3240f8c62ad7859947ab8a033ac1fe93fa/sc/source/filter/html/htmlexp.cxx#L1073-L1092 |
||
1671 | * |
||
1672 | * @param Worksheet $pSheet |
||
1673 | * @param string $coordinate |
||
1674 | * |
||
1675 | * @return string |
||
1676 | */ |
||
1677 | 13 | private function writeComment(Worksheet $pSheet, $coordinate) |
|
1687 | } |
||
1688 | } |
||
1689 |