Total Complexity | 230 |
Total Lines | 1661 |
Duplicated Lines | 0 % |
Coverage | 75.66% |
Changes | 4 | ||
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 | 19 | public function __construct(Spreadsheet $spreadsheet) |
|
141 | { |
||
142 | 19 | $this->spreadsheet = $spreadsheet; |
|
143 | 19 | $this->defaultFont = $this->spreadsheet->getDefaultStyle()->getFont(); |
|
144 | 19 | } |
|
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 | 14 | private function mapVAlign($vAlign) |
|
201 | { |
||
202 | 14 | switch ($vAlign) { |
|
203 | case Alignment::VERTICAL_BOTTOM: |
||
204 | 14 | return 'bottom'; |
|
205 | case Alignment::VERTICAL_TOP: |
||
206 | return 'top'; |
||
207 | case Alignment::VERTICAL_CENTER: |
||
208 | case Alignment::VERTICAL_JUSTIFY: |
||
209 | 4 | 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 | 14 | private function mapHAlign($hAlign) |
|
223 | { |
||
224 | 14 | switch ($hAlign) { |
|
225 | case Alignment::HORIZONTAL_GENERAL: |
||
226 | 14 | return false; |
|
227 | case Alignment::HORIZONTAL_LEFT: |
||
228 | 4 | return 'left'; |
|
229 | case Alignment::HORIZONTAL_RIGHT: |
||
230 | 4 | return 'right'; |
|
231 | case Alignment::HORIZONTAL_CENTER: |
||
232 | case Alignment::HORIZONTAL_CENTER_CONTINUOUS: |
||
233 | 1 | return 'center'; |
|
234 | case Alignment::HORIZONTAL_JUSTIFY: |
||
235 | 4 | 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 | 14 | private function mapBorderStyle($borderStyle) |
|
249 | { |
||
250 | 14 | switch ($borderStyle) { |
|
251 | case Border::BORDER_NONE: |
||
252 | 14 | 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 | 4 | return '3px solid'; |
|
277 | case Border::BORDER_THIN: |
||
278 | 4 | 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 | 4 | public function getSheetIndex() |
|
291 | { |
||
292 | 4 | return $this->sheetIndex; |
|
293 | } |
||
294 | |||
295 | /** |
||
296 | * Set sheet index. |
||
297 | * |
||
298 | * @param int $pValue Sheet index |
||
299 | * |
||
300 | * @return $this |
||
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 $this |
||
325 | */ |
||
326 | public function setGenerateSheetNavigationBlock($pValue) |
||
331 | } |
||
332 | |||
333 | /** |
||
334 | * Write all sheets (resets sheetIndex to NULL). |
||
335 | * |
||
336 | * @return $this |
||
337 | */ |
||
338 | public function writeAllSheets() |
||
339 | { |
||
340 | $this->sheetIndex = null; |
||
341 | |||
342 | return $this; |
||
343 | } |
||
344 | |||
345 | /** |
||
346 | * Generate HTML header. |
||
347 | * |
||
348 | * @param bool $pIncludeStyles Include styles? |
||
349 | * |
||
350 | * @throws WriterException |
||
351 | * |
||
352 | * @return string |
||
353 | */ |
||
354 | 14 | public function generateHTMLHeader($pIncludeStyles = false) |
|
400 | } |
||
401 | |||
402 | /** |
||
403 | * Generate sheet data. |
||
404 | * |
||
405 | * @throws WriterException |
||
406 | * |
||
407 | * @return string |
||
408 | */ |
||
409 | 14 | public function generateSheetData() |
|
410 | { |
||
411 | // Ensure that Spans have been calculated? |
||
412 | 14 | if ($this->sheetIndex !== null || !$this->spansAreCalculated) { |
|
413 | 14 | $this->calculateSpans(); |
|
414 | } |
||
415 | |||
416 | // Fetch sheets |
||
417 | 14 | $sheets = []; |
|
418 | 14 | if ($this->sheetIndex === null) { |
|
419 | $sheets = $this->spreadsheet->getAllSheets(); |
||
420 | } else { |
||
421 | 14 | $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex); |
|
422 | } |
||
423 | |||
424 | // Construct HTML |
||
425 | 14 | $html = ''; |
|
426 | |||
427 | // Loop all sheets |
||
428 | 14 | $sheetId = 0; |
|
429 | 14 | foreach ($sheets as $sheet) { |
|
430 | // Write table header |
||
431 | 14 | $html .= $this->generateTableHeader($sheet); |
|
432 | |||
433 | // Get worksheet dimension |
||
434 | 14 | $dimension = explode(':', $sheet->calculateWorksheetDimension()); |
|
435 | 14 | $dimension[0] = Coordinate::coordinateFromString($dimension[0]); |
|
436 | 14 | $dimension[0][0] = Coordinate::columnIndexFromString($dimension[0][0]); |
|
437 | 14 | $dimension[1] = Coordinate::coordinateFromString($dimension[1]); |
|
438 | 14 | $dimension[1][0] = Coordinate::columnIndexFromString($dimension[1][0]); |
|
439 | |||
440 | // row min,max |
||
441 | 14 | $rowMin = $dimension[0][1]; |
|
442 | 14 | $rowMax = $dimension[1][1]; |
|
443 | |||
444 | // calculate start of <tbody>, <thead> |
||
445 | 14 | $tbodyStart = $rowMin; |
|
446 | 14 | $theadStart = $theadEnd = 0; // default: no <thead> no </thead> |
|
447 | 14 | if ($sheet->getPageSetup()->isRowsToRepeatAtTopSet()) { |
|
448 | $rowsToRepeatAtTop = $sheet->getPageSetup()->getRowsToRepeatAtTop(); |
||
449 | |||
450 | // we can only support repeating rows that start at top row |
||
451 | if ($rowsToRepeatAtTop[0] == 1) { |
||
452 | $theadStart = $rowsToRepeatAtTop[0]; |
||
453 | $theadEnd = $rowsToRepeatAtTop[1]; |
||
454 | $tbodyStart = $rowsToRepeatAtTop[1] + 1; |
||
455 | } |
||
456 | } |
||
457 | |||
458 | // Loop through cells |
||
459 | 14 | $row = $rowMin - 1; |
|
460 | 14 | while ($row++ < $rowMax) { |
|
461 | // <thead> ? |
||
462 | 14 | if ($row == $theadStart) { |
|
463 | $html .= ' <thead>' . PHP_EOL; |
||
464 | $cellType = 'th'; |
||
465 | } |
||
466 | |||
467 | // <tbody> ? |
||
468 | 14 | if ($row == $tbodyStart) { |
|
469 | 14 | $html .= ' <tbody>' . PHP_EOL; |
|
470 | 14 | $cellType = 'td'; |
|
471 | } |
||
472 | |||
473 | // Write row if there are HTML table cells in it |
||
474 | 14 | if (!isset($this->isSpannedRow[$sheet->getParent()->getIndex($sheet)][$row])) { |
|
475 | // Start a new rowData |
||
476 | 14 | $rowData = []; |
|
477 | // Loop through columns |
||
478 | 14 | $column = $dimension[0][0]; |
|
479 | 14 | while ($column <= $dimension[1][0]) { |
|
480 | // Cell exists? |
||
481 | 14 | if ($sheet->cellExistsByColumnAndRow($column, $row)) { |
|
482 | 14 | $rowData[$column] = Coordinate::stringFromColumnIndex($column) . $row; |
|
483 | } else { |
||
484 | 6 | $rowData[$column] = ''; |
|
485 | } |
||
486 | 14 | ++$column; |
|
487 | } |
||
488 | 14 | $html .= $this->generateRow($sheet, $rowData, $row - 1, $cellType); |
|
|
|||
489 | } |
||
490 | |||
491 | // </thead> ? |
||
492 | 14 | if ($row == $theadEnd) { |
|
493 | $html .= ' </thead>' . PHP_EOL; |
||
494 | } |
||
495 | } |
||
496 | 14 | $html .= $this->extendRowsForChartsAndImages($sheet, $row); |
|
497 | |||
498 | // Close table body. |
||
499 | 14 | $html .= ' </tbody>' . PHP_EOL; |
|
500 | |||
501 | // Write table footer |
||
502 | 14 | $html .= $this->generateTableFooter(); |
|
503 | |||
504 | // Writing PDF? |
||
505 | 14 | if ($this->isPdf) { |
|
506 | 4 | if ($this->sheetIndex === null && $sheetId + 1 < $this->spreadsheet->getSheetCount()) { |
|
507 | $html .= '<div style="page-break-before:always" />'; |
||
508 | } |
||
509 | } |
||
510 | |||
511 | // Next sheet |
||
512 | 14 | ++$sheetId; |
|
513 | } |
||
514 | |||
515 | 14 | return $html; |
|
516 | } |
||
517 | |||
518 | /** |
||
519 | * Generate sheet tabs. |
||
520 | * |
||
521 | * @throws WriterException |
||
522 | * |
||
523 | * @return string |
||
524 | */ |
||
525 | 11 | public function generateNavigation() |
|
526 | { |
||
527 | // Fetch sheets |
||
528 | 11 | $sheets = []; |
|
529 | 11 | if ($this->sheetIndex === null) { |
|
530 | $sheets = $this->spreadsheet->getAllSheets(); |
||
531 | } else { |
||
532 | 11 | $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex); |
|
533 | } |
||
534 | |||
535 | // Construct HTML |
||
536 | 11 | $html = ''; |
|
537 | |||
538 | // Only if there are more than 1 sheets |
||
539 | 11 | if (count($sheets) > 1) { |
|
540 | // Loop all sheets |
||
541 | $sheetId = 0; |
||
542 | |||
543 | $html .= '<ul class="navigation">' . PHP_EOL; |
||
544 | |||
545 | foreach ($sheets as $sheet) { |
||
546 | $html .= ' <li class="sheet' . $sheetId . '"><a href="#sheet' . $sheetId . '">' . $sheet->getTitle() . '</a></li>' . PHP_EOL; |
||
547 | ++$sheetId; |
||
548 | } |
||
549 | |||
550 | $html .= '</ul>' . PHP_EOL; |
||
551 | } |
||
552 | |||
553 | 11 | return $html; |
|
554 | } |
||
555 | |||
556 | 14 | private function extendRowsForChartsAndImages(Worksheet $pSheet, $row) |
|
557 | { |
||
558 | 14 | $rowMax = $row; |
|
559 | 14 | $colMax = 'A'; |
|
560 | 14 | if ($this->includeCharts) { |
|
561 | foreach ($pSheet->getChartCollection() as $chart) { |
||
562 | if ($chart instanceof Chart) { |
||
563 | $chartCoordinates = $chart->getTopLeftPosition(); |
||
564 | $chartTL = Coordinate::coordinateFromString($chartCoordinates['cell']); |
||
565 | $chartCol = Coordinate::columnIndexFromString($chartTL[0]); |
||
566 | if ($chartTL[1] > $rowMax) { |
||
567 | $rowMax = $chartTL[1]; |
||
568 | if ($chartCol > Coordinate::columnIndexFromString($colMax)) { |
||
569 | $colMax = $chartTL[0]; |
||
570 | } |
||
571 | } |
||
572 | } |
||
573 | } |
||
574 | } |
||
575 | |||
576 | 14 | foreach ($pSheet->getDrawingCollection() as $drawing) { |
|
577 | 5 | if ($drawing instanceof Drawing) { |
|
578 | 4 | $imageTL = Coordinate::coordinateFromString($drawing->getCoordinates()); |
|
579 | 4 | $imageCol = Coordinate::columnIndexFromString($imageTL[0]); |
|
580 | 4 | if ($imageTL[1] > $rowMax) { |
|
581 | $rowMax = $imageTL[1]; |
||
582 | if ($imageCol > Coordinate::columnIndexFromString($colMax)) { |
||
583 | $colMax = $imageTL[0]; |
||
584 | } |
||
585 | } |
||
586 | } |
||
587 | } |
||
588 | |||
589 | // Don't extend rows if not needed |
||
590 | 14 | if ($row === $rowMax) { |
|
591 | 14 | return ''; |
|
592 | } |
||
593 | |||
594 | $html = ''; |
||
595 | ++$colMax; |
||
596 | |||
597 | while ($row <= $rowMax) { |
||
598 | $html .= '<tr>'; |
||
599 | for ($col = 'A'; $col != $colMax; ++$col) { |
||
600 | $html .= '<td>'; |
||
601 | $html .= $this->writeImageInCell($pSheet, $col . $row); |
||
602 | if ($this->includeCharts) { |
||
603 | $html .= $this->writeChartInCell($pSheet, $col . $row); |
||
604 | } |
||
605 | $html .= '</td>'; |
||
606 | } |
||
607 | ++$row; |
||
608 | $html .= '</tr>'; |
||
609 | } |
||
610 | |||
611 | return $html; |
||
612 | } |
||
613 | |||
614 | /** |
||
615 | * Generate image tag in cell. |
||
616 | * |
||
617 | * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet |
||
618 | * @param string $coordinates Cell coordinates |
||
619 | * |
||
620 | * @return string |
||
621 | */ |
||
622 | 14 | private function writeImageInCell(Worksheet $pSheet, $coordinates) |
|
623 | { |
||
624 | // Construct HTML |
||
625 | 14 | $html = ''; |
|
626 | |||
627 | // Write images |
||
628 | 14 | foreach ($pSheet->getDrawingCollection() as $drawing) { |
|
629 | 5 | if ($drawing instanceof Drawing) { |
|
630 | 4 | if ($drawing->getCoordinates() == $coordinates) { |
|
631 | 4 | $filename = $drawing->getPath(); |
|
632 | |||
633 | // Strip off eventual '.' |
||
634 | 4 | if (substr($filename, 0, 1) == '.') { |
|
635 | $filename = substr($filename, 1); |
||
636 | } |
||
637 | |||
638 | // Prepend images root |
||
639 | 4 | $filename = $this->getImagesRoot() . $filename; |
|
640 | |||
641 | // Strip off eventual '.' |
||
642 | 4 | if (substr($filename, 0, 1) == '.' && substr($filename, 0, 2) != './') { |
|
643 | $filename = substr($filename, 1); |
||
644 | } |
||
645 | |||
646 | // Convert UTF8 data to PCDATA |
||
647 | 4 | $filename = htmlspecialchars($filename); |
|
648 | |||
649 | 4 | $html .= PHP_EOL; |
|
650 | 4 | if ((!$this->embedImages) || ($this->isPdf)) { |
|
651 | 4 | $imageData = $filename; |
|
652 | } else { |
||
653 | $imageDetails = getimagesize($filename); |
||
654 | if ($fp = fopen($filename, 'rb', 0)) { |
||
655 | $picture = ''; |
||
656 | while (!feof($fp)) { |
||
657 | $picture .= fread($fp, 1024); |
||
658 | } |
||
659 | fclose($fp); |
||
660 | // base64 encode the binary data, then break it |
||
661 | // into chunks according to RFC 2045 semantics |
||
662 | $base64 = chunk_split(base64_encode($picture)); |
||
663 | $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64; |
||
664 | } else { |
||
665 | $imageData = $filename; |
||
666 | } |
||
667 | } |
||
668 | |||
669 | 4 | $html .= '<div style="position: relative;">'; |
|
670 | $html .= '<img style="position: absolute; z-index: 1; left: ' . |
||
671 | 4 | $drawing->getOffsetX() . 'px; top: ' . $drawing->getOffsetY() . 'px; width: ' . |
|
672 | 4 | $drawing->getWidth() . 'px; height: ' . $drawing->getHeight() . 'px;" src="' . |
|
673 | 4 | $imageData . '" border="0" />'; |
|
674 | 4 | $html .= '</div>'; |
|
675 | } |
||
676 | 1 | } elseif ($drawing instanceof MemoryDrawing) { |
|
677 | 1 | if ($drawing->getCoordinates() != $coordinates) { |
|
678 | continue; |
||
679 | } |
||
680 | 1 | ob_start(); // Let's start output buffering. |
|
681 | 1 | imagepng($drawing->getImageResource()); // This will normally output the image, but because of ob_start(), it won't. |
|
682 | 1 | $contents = ob_get_contents(); // Instead, output above is saved to $contents |
|
683 | 1 | ob_end_clean(); // End the output buffer. |
|
684 | |||
685 | 1 | $dataUri = 'data:image/jpeg;base64,' . base64_encode($contents); |
|
686 | |||
687 | // Because of the nature of tables, width is more important than height. |
||
688 | // max-width: 100% ensures that image doesnt overflow containing cell |
||
689 | // width: X sets width of supplied image. |
||
690 | // As a result, images bigger than cell will be contained and images smaller will not get stretched |
||
691 | 1 | $html .= '<img src="' . $dataUri . '" style="max-width:100%;width:' . $drawing->getWidth() . 'px;" />'; |
|
692 | } |
||
693 | } |
||
694 | |||
695 | 14 | return $html; |
|
696 | } |
||
697 | |||
698 | /** |
||
699 | * Generate chart tag in cell. |
||
700 | * |
||
701 | * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet |
||
702 | * @param string $coordinates Cell coordinates |
||
703 | * |
||
704 | * @return string |
||
705 | */ |
||
706 | private function writeChartInCell(Worksheet $pSheet, $coordinates) |
||
707 | { |
||
708 | // Construct HTML |
||
709 | $html = ''; |
||
710 | |||
711 | // Write charts |
||
712 | foreach ($pSheet->getChartCollection() as $chart) { |
||
713 | if ($chart instanceof Chart) { |
||
714 | $chartCoordinates = $chart->getTopLeftPosition(); |
||
715 | if ($chartCoordinates['cell'] == $coordinates) { |
||
716 | $chartFileName = File::sysGetTempDir() . '/' . uniqid('', true) . '.png'; |
||
717 | if (!$chart->render($chartFileName)) { |
||
718 | return; |
||
719 | } |
||
720 | |||
721 | $html .= PHP_EOL; |
||
722 | $imageDetails = getimagesize($chartFileName); |
||
723 | if ($fp = fopen($chartFileName, 'rb', 0)) { |
||
724 | $picture = fread($fp, filesize($chartFileName)); |
||
725 | fclose($fp); |
||
726 | // base64 encode the binary data, then break it |
||
727 | // into chunks according to RFC 2045 semantics |
||
728 | $base64 = chunk_split(base64_encode($picture)); |
||
729 | $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64; |
||
730 | |||
731 | $html .= '<div style="position: relative;">'; |
||
732 | $html .= '<img style="position: absolute; z-index: 1; left: ' . $chartCoordinates['xOffset'] . 'px; top: ' . $chartCoordinates['yOffset'] . 'px; width: ' . $imageDetails[0] . 'px; height: ' . $imageDetails[1] . 'px;" src="' . $imageData . '" border="0" />' . PHP_EOL; |
||
733 | $html .= '</div>'; |
||
734 | |||
735 | unlink($chartFileName); |
||
736 | } |
||
737 | } |
||
738 | } |
||
739 | } |
||
740 | |||
741 | // Return |
||
742 | return $html; |
||
743 | } |
||
744 | |||
745 | /** |
||
746 | * Generate CSS styles. |
||
747 | * |
||
748 | * @param bool $generateSurroundingHTML Generate surrounding HTML tags? (<style> and </style>) |
||
749 | * |
||
750 | * @throws WriterException |
||
751 | * |
||
752 | * @return string |
||
753 | */ |
||
754 | 11 | public function generateStyles($generateSurroundingHTML = true) |
|
755 | { |
||
756 | // Build CSS |
||
757 | 11 | $css = $this->buildCSS($generateSurroundingHTML); |
|
758 | |||
759 | // Construct HTML |
||
760 | 11 | $html = ''; |
|
761 | |||
762 | // Start styles |
||
763 | 11 | if ($generateSurroundingHTML) { |
|
764 | 11 | $html .= ' <style type="text/css">' . PHP_EOL; |
|
765 | 11 | $html .= ' html { ' . $this->assembleCSS($css['html']) . ' }' . PHP_EOL; |
|
766 | } |
||
767 | |||
768 | // Write all other styles |
||
769 | 11 | foreach ($css as $styleName => $styleDefinition) { |
|
770 | 11 | if ($styleName != 'html') { |
|
771 | 11 | $html .= ' ' . $styleName . ' { ' . $this->assembleCSS($styleDefinition) . ' }' . PHP_EOL; |
|
772 | } |
||
773 | } |
||
774 | |||
775 | // End styles |
||
776 | 11 | if ($generateSurroundingHTML) { |
|
777 | 11 | $html .= ' </style>' . PHP_EOL; |
|
778 | } |
||
779 | |||
780 | // Return |
||
781 | 11 | return $html; |
|
782 | } |
||
783 | |||
784 | /** |
||
785 | * Build CSS styles. |
||
786 | * |
||
787 | * @param bool $generateSurroundingHTML Generate surrounding HTML style? (html { }) |
||
788 | * |
||
789 | * @throws WriterException |
||
790 | * |
||
791 | * @return array |
||
792 | */ |
||
793 | 14 | public function buildCSS($generateSurroundingHTML = true) |
|
794 | { |
||
795 | // Cached? |
||
796 | 14 | if ($this->cssStyles !== null) { |
|
797 | 11 | return $this->cssStyles; |
|
798 | } |
||
799 | |||
800 | // Ensure that spans have been calculated |
||
801 | 14 | if (!$this->spansAreCalculated) { |
|
802 | 14 | $this->calculateSpans(); |
|
803 | } |
||
804 | |||
805 | // Construct CSS |
||
806 | 14 | $css = []; |
|
807 | |||
808 | // Start styles |
||
809 | 14 | if ($generateSurroundingHTML) { |
|
810 | // html { } |
||
811 | 14 | $css['html']['font-family'] = 'Calibri, Arial, Helvetica, sans-serif'; |
|
812 | 14 | $css['html']['font-size'] = '11pt'; |
|
813 | 14 | $css['html']['background-color'] = 'white'; |
|
814 | } |
||
815 | |||
816 | // CSS for comments as found in LibreOffice |
||
817 | 14 | $css['a.comment-indicator:hover + div.comment'] = [ |
|
818 | 'background' => '#ffd', |
||
819 | 'position' => 'absolute', |
||
820 | 'display' => 'block', |
||
821 | 'border' => '1px solid black', |
||
822 | 'padding' => '0.5em', |
||
823 | ]; |
||
824 | |||
825 | 14 | $css['a.comment-indicator'] = [ |
|
826 | 'background' => 'red', |
||
827 | 'display' => 'inline-block', |
||
828 | 'border' => '1px solid black', |
||
829 | 'width' => '0.5em', |
||
830 | 'height' => '0.5em', |
||
831 | ]; |
||
832 | |||
833 | 14 | $css['div.comment']['display'] = 'none'; |
|
834 | |||
835 | // table { } |
||
836 | 14 | $css['table']['border-collapse'] = 'collapse'; |
|
837 | 14 | if (!$this->isPdf) { |
|
838 | 11 | $css['table']['page-break-after'] = 'always'; |
|
839 | } |
||
840 | |||
841 | // .gridlines td { } |
||
842 | 14 | $css['.gridlines td']['border'] = '1px dotted black'; |
|
843 | 14 | $css['.gridlines th']['border'] = '1px dotted black'; |
|
844 | |||
845 | // .b {} |
||
846 | 14 | $css['.b']['text-align'] = 'center'; // BOOL |
|
847 | |||
848 | // .e {} |
||
849 | 14 | $css['.e']['text-align'] = 'center'; // ERROR |
|
850 | |||
851 | // .f {} |
||
852 | 14 | $css['.f']['text-align'] = 'right'; // FORMULA |
|
853 | |||
854 | // .inlineStr {} |
||
855 | 14 | $css['.inlineStr']['text-align'] = 'left'; // INLINE |
|
856 | |||
857 | // .n {} |
||
858 | 14 | $css['.n']['text-align'] = 'right'; // NUMERIC |
|
859 | |||
860 | // .s {} |
||
861 | 14 | $css['.s']['text-align'] = 'left'; // STRING |
|
862 | |||
863 | // Calculate cell style hashes |
||
864 | 14 | foreach ($this->spreadsheet->getCellXfCollection() as $index => $style) { |
|
865 | 14 | $css['td.style' . $index] = $this->createCSSStyle($style); |
|
866 | 14 | $css['th.style' . $index] = $this->createCSSStyle($style); |
|
867 | } |
||
868 | |||
869 | // Fetch sheets |
||
870 | 14 | $sheets = []; |
|
871 | 14 | if ($this->sheetIndex === null) { |
|
872 | $sheets = $this->spreadsheet->getAllSheets(); |
||
873 | } else { |
||
874 | 14 | $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex); |
|
875 | } |
||
876 | |||
877 | // Build styles per sheet |
||
878 | 14 | foreach ($sheets as $sheet) { |
|
879 | // Calculate hash code |
||
880 | 14 | $sheetIndex = $sheet->getParent()->getIndex($sheet); |
|
881 | |||
882 | // Build styles |
||
883 | // Calculate column widths |
||
884 | 14 | $sheet->calculateColumnWidths(); |
|
885 | |||
886 | // col elements, initialize |
||
887 | 14 | $highestColumnIndex = Coordinate::columnIndexFromString($sheet->getHighestColumn()) - 1; |
|
888 | 14 | $column = -1; |
|
889 | 14 | while ($column++ < $highestColumnIndex) { |
|
890 | 14 | $this->columnWidths[$sheetIndex][$column] = 42; // approximation |
|
891 | 14 | $css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = '42pt'; |
|
892 | } |
||
893 | |||
894 | // col elements, loop through columnDimensions and set width |
||
895 | 14 | foreach ($sheet->getColumnDimensions() as $columnDimension) { |
|
896 | 5 | if (($width = SharedDrawing::cellDimensionToPixels($columnDimension->getWidth(), $this->defaultFont)) >= 0) { |
|
897 | 5 | $width = SharedDrawing::pixelsToPoints($width); |
|
898 | 5 | $column = Coordinate::columnIndexFromString($columnDimension->getColumnIndex()) - 1; |
|
899 | 5 | $this->columnWidths[$sheetIndex][$column] = $width; |
|
900 | 5 | $css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = $width . 'pt'; |
|
901 | |||
902 | 5 | if ($columnDimension->getVisible() === false) { |
|
903 | $css['table.sheet' . $sheetIndex . ' .column' . $column]['visibility'] = 'collapse'; |
||
904 | $css['table.sheet' . $sheetIndex . ' .column' . $column]['display'] = 'none'; // target IE6+7 |
||
905 | } |
||
906 | } |
||
907 | } |
||
908 | |||
909 | // Default row height |
||
910 | 14 | $rowDimension = $sheet->getDefaultRowDimension(); |
|
911 | |||
912 | // table.sheetN tr { } |
||
913 | 14 | $css['table.sheet' . $sheetIndex . ' tr'] = []; |
|
914 | |||
915 | 14 | if ($rowDimension->getRowHeight() == -1) { |
|
916 | 14 | $pt_height = SharedFont::getDefaultRowHeightByFont($this->spreadsheet->getDefaultStyle()->getFont()); |
|
917 | } else { |
||
918 | $pt_height = $rowDimension->getRowHeight(); |
||
919 | } |
||
920 | 14 | $css['table.sheet' . $sheetIndex . ' tr']['height'] = $pt_height . 'pt'; |
|
921 | 14 | if ($rowDimension->getVisible() === false) { |
|
922 | $css['table.sheet' . $sheetIndex . ' tr']['display'] = 'none'; |
||
923 | $css['table.sheet' . $sheetIndex . ' tr']['visibility'] = 'hidden'; |
||
924 | } |
||
925 | |||
926 | // Calculate row heights |
||
927 | 14 | foreach ($sheet->getRowDimensions() as $rowDimension) { |
|
928 | 2 | $row = $rowDimension->getRowIndex() - 1; |
|
929 | |||
930 | // table.sheetN tr.rowYYYYYY { } |
||
931 | 2 | $css['table.sheet' . $sheetIndex . ' tr.row' . $row] = []; |
|
932 | |||
933 | 2 | if ($rowDimension->getRowHeight() == -1) { |
|
934 | 2 | $pt_height = SharedFont::getDefaultRowHeightByFont($this->spreadsheet->getDefaultStyle()->getFont()); |
|
935 | } else { |
||
936 | 1 | $pt_height = $rowDimension->getRowHeight(); |
|
937 | } |
||
938 | 2 | $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['height'] = $pt_height . 'pt'; |
|
939 | 2 | if ($rowDimension->getVisible() === false) { |
|
940 | $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['display'] = 'none'; |
||
941 | $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['visibility'] = 'hidden'; |
||
942 | } |
||
943 | } |
||
944 | } |
||
945 | |||
946 | // Cache |
||
947 | 14 | if ($this->cssStyles === null) { |
|
948 | 14 | $this->cssStyles = $css; |
|
949 | } |
||
950 | |||
951 | // Return |
||
952 | 14 | return $css; |
|
953 | } |
||
954 | |||
955 | /** |
||
956 | * Create CSS style. |
||
957 | * |
||
958 | * @param Style $pStyle |
||
959 | * |
||
960 | * @return array |
||
961 | */ |
||
962 | 14 | private function createCSSStyle(Style $pStyle) |
|
963 | { |
||
964 | // Create CSS |
||
965 | 14 | return array_merge( |
|
966 | 14 | $this->createCSSStyleAlignment($pStyle->getAlignment()), |
|
967 | 14 | $this->createCSSStyleBorders($pStyle->getBorders()), |
|
968 | 14 | $this->createCSSStyleFont($pStyle->getFont()), |
|
969 | 14 | $this->createCSSStyleFill($pStyle->getFill()) |
|
970 | ); |
||
971 | } |
||
972 | |||
973 | /** |
||
974 | * Create CSS style (\PhpOffice\PhpSpreadsheet\Style\Alignment). |
||
975 | * |
||
976 | * @param Alignment $pStyle \PhpOffice\PhpSpreadsheet\Style\Alignment |
||
977 | * |
||
978 | * @return array |
||
979 | */ |
||
980 | 14 | private function createCSSStyleAlignment(Alignment $pStyle) |
|
981 | { |
||
982 | // Construct CSS |
||
983 | 14 | $css = []; |
|
984 | |||
985 | // Create CSS |
||
986 | 14 | $css['vertical-align'] = $this->mapVAlign($pStyle->getVertical()); |
|
987 | 14 | if ($textAlign = $this->mapHAlign($pStyle->getHorizontal())) { |
|
988 | 5 | $css['text-align'] = $textAlign; |
|
989 | 5 | if (in_array($textAlign, ['left', 'right'])) { |
|
990 | 4 | $css['padding-' . $textAlign] = (string) ((int) $pStyle->getIndent() * 9) . 'px'; |
|
991 | } |
||
992 | } |
||
993 | |||
994 | 14 | return $css; |
|
995 | } |
||
996 | |||
997 | /** |
||
998 | * Create CSS style (\PhpOffice\PhpSpreadsheet\Style\Font). |
||
999 | * |
||
1000 | * @param Font $pStyle |
||
1001 | * |
||
1002 | * @return array |
||
1003 | */ |
||
1004 | 14 | private function createCSSStyleFont(Font $pStyle) |
|
1005 | { |
||
1006 | // Construct CSS |
||
1007 | 14 | $css = []; |
|
1008 | |||
1009 | // Create CSS |
||
1010 | 14 | if ($pStyle->getBold()) { |
|
1011 | 5 | $css['font-weight'] = 'bold'; |
|
1012 | } |
||
1013 | 14 | if ($pStyle->getUnderline() != Font::UNDERLINE_NONE && $pStyle->getStrikethrough()) { |
|
1014 | $css['text-decoration'] = 'underline line-through'; |
||
1015 | 14 | } elseif ($pStyle->getUnderline() != Font::UNDERLINE_NONE) { |
|
1016 | 4 | $css['text-decoration'] = 'underline'; |
|
1017 | 14 | } elseif ($pStyle->getStrikethrough()) { |
|
1018 | $css['text-decoration'] = 'line-through'; |
||
1019 | } |
||
1020 | 14 | if ($pStyle->getItalic()) { |
|
1021 | 4 | $css['font-style'] = 'italic'; |
|
1022 | } |
||
1023 | |||
1024 | 14 | $css['color'] = '#' . $pStyle->getColor()->getRGB(); |
|
1025 | 14 | $css['font-family'] = '\'' . $pStyle->getName() . '\''; |
|
1026 | 14 | $css['font-size'] = $pStyle->getSize() . 'pt'; |
|
1027 | |||
1028 | 14 | return $css; |
|
1029 | } |
||
1030 | |||
1031 | /** |
||
1032 | * Create CSS style (Borders). |
||
1033 | * |
||
1034 | * @param Borders $pStyle Borders |
||
1035 | * |
||
1036 | * @return array |
||
1037 | */ |
||
1038 | 14 | private function createCSSStyleBorders(Borders $pStyle) |
|
1039 | { |
||
1040 | // Construct CSS |
||
1041 | 14 | $css = []; |
|
1042 | |||
1043 | // Create CSS |
||
1044 | 14 | $css['border-bottom'] = $this->createCSSStyleBorder($pStyle->getBottom()); |
|
1045 | 14 | $css['border-top'] = $this->createCSSStyleBorder($pStyle->getTop()); |
|
1046 | 14 | $css['border-left'] = $this->createCSSStyleBorder($pStyle->getLeft()); |
|
1047 | 14 | $css['border-right'] = $this->createCSSStyleBorder($pStyle->getRight()); |
|
1048 | |||
1049 | 14 | return $css; |
|
1050 | } |
||
1051 | |||
1052 | /** |
||
1053 | * Create CSS style (Border). |
||
1054 | * |
||
1055 | * @param Border $pStyle Border |
||
1056 | * |
||
1057 | * @return string |
||
1058 | */ |
||
1059 | 14 | private function createCSSStyleBorder(Border $pStyle) |
|
1060 | { |
||
1061 | // Create CSS - add !important to non-none border styles for merged cells |
||
1062 | 14 | $borderStyle = $this->mapBorderStyle($pStyle->getBorderStyle()); |
|
1063 | |||
1064 | 14 | return $borderStyle . ' #' . $pStyle->getColor()->getRGB() . (($borderStyle == 'none') ? '' : ' !important'); |
|
1065 | } |
||
1066 | |||
1067 | /** |
||
1068 | * Create CSS style (Fill). |
||
1069 | * |
||
1070 | * @param Fill $pStyle Fill |
||
1071 | * |
||
1072 | * @return array |
||
1073 | */ |
||
1074 | 14 | private function createCSSStyleFill(Fill $pStyle) |
|
1075 | { |
||
1076 | // Construct HTML |
||
1077 | 14 | $css = []; |
|
1078 | |||
1079 | // Create CSS |
||
1080 | 14 | $value = $pStyle->getFillType() == Fill::FILL_NONE ? |
|
1081 | 14 | 'white' : '#' . $pStyle->getStartColor()->getRGB(); |
|
1082 | 14 | $css['background-color'] = $value; |
|
1083 | |||
1084 | 14 | return $css; |
|
1085 | } |
||
1086 | |||
1087 | /** |
||
1088 | * Generate HTML footer. |
||
1089 | */ |
||
1090 | 14 | public function generateHTMLFooter() |
|
1098 | } |
||
1099 | |||
1100 | /** |
||
1101 | * Generate table header. |
||
1102 | * |
||
1103 | * @param Worksheet $pSheet The worksheet for the table we are writing |
||
1104 | * |
||
1105 | * @return string |
||
1106 | */ |
||
1107 | 14 | private function generateTableHeader($pSheet) |
|
1108 | { |
||
1109 | 14 | $sheetIndex = $pSheet->getParent()->getIndex($pSheet); |
|
1110 | |||
1111 | // Construct HTML |
||
1112 | 14 | $html = ''; |
|
1113 | 14 | if ($this->useEmbeddedCSS) { |
|
1114 | 14 | $html .= $this->setMargins($pSheet); |
|
1115 | } |
||
1116 | |||
1117 | 14 | if (!$this->useInlineCss) { |
|
1118 | 11 | $gridlines = $pSheet->getShowGridlines() ? ' gridlines' : ''; |
|
1119 | 11 | $html .= ' <table border="0" cellpadding="0" cellspacing="0" id="sheet' . $sheetIndex . '" class="sheet' . $sheetIndex . $gridlines . '">' . PHP_EOL; |
|
1120 | } else { |
||
1121 | 4 | $style = isset($this->cssStyles['table']) ? |
|
1122 | 4 | $this->assembleCSS($this->cssStyles['table']) : ''; |
|
1123 | |||
1124 | 4 | if ($this->isPdf && $pSheet->getShowGridlines()) { |
|
1125 | 1 | $html .= ' <table border="1" cellpadding="1" id="sheet' . $sheetIndex . '" cellspacing="1" style="' . $style . '">' . PHP_EOL; |
|
1126 | } else { |
||
1127 | 3 | $html .= ' <table border="0" cellpadding="1" id="sheet' . $sheetIndex . '" cellspacing="0" style="' . $style . '">' . PHP_EOL; |
|
1128 | } |
||
1129 | } |
||
1130 | |||
1131 | // Write <col> elements |
||
1132 | 14 | $highestColumnIndex = Coordinate::columnIndexFromString($pSheet->getHighestColumn()) - 1; |
|
1133 | 14 | $i = -1; |
|
1134 | 14 | while ($i++ < $highestColumnIndex) { |
|
1135 | 14 | if (!$this->isPdf) { |
|
1136 | 11 | if (!$this->useInlineCss) { |
|
1137 | 11 | $html .= ' <col class="col' . $i . '">' . PHP_EOL; |
|
1138 | } else { |
||
1139 | $style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) ? |
||
1140 | $this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) : ''; |
||
1141 | $html .= ' <col style="' . $style . '">' . PHP_EOL; |
||
1142 | } |
||
1143 | } |
||
1144 | } |
||
1145 | |||
1146 | 14 | return $html; |
|
1147 | } |
||
1148 | |||
1149 | /** |
||
1150 | * Generate table footer. |
||
1151 | */ |
||
1152 | 14 | private function generateTableFooter() |
|
1153 | { |
||
1154 | 14 | return ' </table>' . PHP_EOL; |
|
1155 | } |
||
1156 | |||
1157 | /** |
||
1158 | * Generate row. |
||
1159 | * |
||
1160 | * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet |
||
1161 | * @param array $pValues Array containing cells in a row |
||
1162 | * @param int $pRow Row number (0-based) |
||
1163 | * @param string $cellType eg: 'td' |
||
1164 | * |
||
1165 | * @throws WriterException |
||
1166 | * |
||
1167 | * @return string |
||
1168 | */ |
||
1169 | 14 | private function generateRow(Worksheet $pSheet, array $pValues, $pRow, $cellType) |
|
1170 | { |
||
1171 | // Construct HTML |
||
1172 | 14 | $html = ''; |
|
1173 | |||
1174 | // Sheet index |
||
1175 | 14 | $sheetIndex = $pSheet->getParent()->getIndex($pSheet); |
|
1176 | |||
1177 | // Dompdf and breaks |
||
1178 | 14 | if ($this->isPdf && count($pSheet->getBreaks()) > 0) { |
|
1179 | $breaks = $pSheet->getBreaks(); |
||
1180 | |||
1181 | // check if a break is needed before this row |
||
1182 | if (isset($breaks['A' . $pRow])) { |
||
1183 | // close table: </table> |
||
1184 | $html .= $this->generateTableFooter(); |
||
1185 | |||
1186 | // insert page break |
||
1187 | $html .= '<div style="page-break-before:always" />'; |
||
1188 | |||
1189 | // open table again: <table> + <col> etc. |
||
1190 | $html .= $this->generateTableHeader($pSheet); |
||
1191 | } |
||
1192 | } |
||
1193 | |||
1194 | // Write row start |
||
1195 | 14 | if (!$this->useInlineCss) { |
|
1196 | 11 | $html .= ' <tr class="row' . $pRow . '">' . PHP_EOL; |
|
1197 | } else { |
||
1198 | 4 | $style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]) |
|
1199 | 4 | ? $this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]) : ''; |
|
1200 | |||
1201 | 4 | $html .= ' <tr style="' . $style . '">' . PHP_EOL; |
|
1202 | } |
||
1203 | |||
1204 | // Write cells |
||
1205 | 14 | $colNum = 0; |
|
1206 | 14 | foreach ($pValues as $cellAddress) { |
|
1207 | 14 | $cell = ($cellAddress > '') ? $pSheet->getCell($cellAddress) : ''; |
|
1208 | 14 | $coordinate = Coordinate::stringFromColumnIndex($colNum + 1) . ($pRow + 1); |
|
1209 | 14 | if (!$this->useInlineCss) { |
|
1210 | 11 | $cssClass = 'column' . $colNum; |
|
1211 | } else { |
||
1212 | 4 | $cssClass = []; |
|
1213 | 4 | if ($cellType == 'th') { |
|
1214 | if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' th.column' . $colNum])) { |
||
1215 | $this->cssStyles['table.sheet' . $sheetIndex . ' th.column' . $colNum]; |
||
1216 | } |
||
1217 | } else { |
||
1218 | 4 | if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum])) { |
|
1219 | $this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum]; |
||
1220 | } |
||
1221 | } |
||
1222 | } |
||
1223 | 14 | $colSpan = 1; |
|
1224 | 14 | $rowSpan = 1; |
|
1225 | |||
1226 | // initialize |
||
1227 | 14 | $cellData = ' '; |
|
1228 | |||
1229 | // Cell |
||
1230 | 14 | if ($cell instanceof Cell) { |
|
1231 | 14 | $cellData = ''; |
|
1232 | 14 | if ($cell->getParent() === null) { |
|
1233 | $cell->attach($pSheet); |
||
1234 | } |
||
1235 | // Value |
||
1236 | 14 | if ($cell->getValue() instanceof RichText) { |
|
1237 | // Loop through rich text elements |
||
1238 | 5 | $elements = $cell->getValue()->getRichTextElements(); |
|
1239 | 5 | foreach ($elements as $element) { |
|
1240 | // Rich text start? |
||
1241 | 5 | if ($element instanceof Run) { |
|
1242 | 5 | $cellData .= '<span style="' . $this->assembleCSS($this->createCSSStyleFont($element->getFont())) . '">'; |
|
1243 | |||
1244 | 5 | if ($element->getFont()->getSuperscript()) { |
|
1245 | $cellData .= '<sup>'; |
||
1246 | 5 | } elseif ($element->getFont()->getSubscript()) { |
|
1247 | $cellData .= '<sub>'; |
||
1248 | } |
||
1249 | } |
||
1250 | |||
1251 | // Convert UTF8 data to PCDATA |
||
1252 | 5 | $cellText = $element->getText(); |
|
1253 | 5 | $cellData .= htmlspecialchars($cellText); |
|
1254 | |||
1255 | 5 | if ($element instanceof Run) { |
|
1256 | 5 | if ($element->getFont()->getSuperscript()) { |
|
1257 | $cellData .= '</sup>'; |
||
1258 | 5 | } elseif ($element->getFont()->getSubscript()) { |
|
1259 | $cellData .= '</sub>'; |
||
1260 | } |
||
1261 | |||
1262 | 5 | $cellData .= '</span>'; |
|
1263 | } |
||
1264 | } |
||
1265 | } else { |
||
1266 | 14 | if ($this->preCalculateFormulas) { |
|
1267 | 14 | $cellData = NumberFormat::toFormattedString( |
|
1268 | 14 | $cell->getCalculatedValue(), |
|
1269 | 14 | $pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode(), |
|
1270 | 14 | [$this, 'formatColor'] |
|
1271 | ); |
||
1272 | } else { |
||
1273 | $cellData = NumberFormat::toFormattedString( |
||
1274 | $cell->getValue(), |
||
1275 | $pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode(), |
||
1276 | [$this, 'formatColor'] |
||
1277 | ); |
||
1278 | } |
||
1279 | 14 | $cellData = htmlspecialchars($cellData); |
|
1280 | 14 | if ($pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSuperscript()) { |
|
1281 | $cellData = '<sup>' . $cellData . '</sup>'; |
||
1282 | 14 | } elseif ($pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSubscript()) { |
|
1283 | $cellData = '<sub>' . $cellData . '</sub>'; |
||
1284 | } |
||
1285 | } |
||
1286 | |||
1287 | // Converts the cell content so that spaces occuring at beginning of each new line are replaced by |
||
1288 | // Example: " Hello\n to the world" is converted to " Hello\n to the world" |
||
1289 | 14 | $cellData = preg_replace('/(?m)(?:^|\\G) /', ' ', $cellData); |
|
1290 | |||
1291 | // convert newline "\n" to '<br>' |
||
1292 | 14 | $cellData = nl2br($cellData); |
|
1293 | |||
1294 | // Extend CSS class? |
||
1295 | 14 | if (!$this->useInlineCss) { |
|
1296 | 11 | $cssClass .= ' style' . $cell->getXfIndex(); |
|
1297 | 11 | $cssClass .= ' ' . $cell->getDataType(); |
|
1298 | } else { |
||
1299 | 4 | if ($cellType == 'th') { |
|
1300 | if (isset($this->cssStyles['th.style' . $cell->getXfIndex()])) { |
||
1301 | $cssClass = array_merge($cssClass, $this->cssStyles['th.style' . $cell->getXfIndex()]); |
||
1302 | } |
||
1303 | } else { |
||
1304 | 4 | if (isset($this->cssStyles['td.style' . $cell->getXfIndex()])) { |
|
1305 | 4 | $cssClass = array_merge($cssClass, $this->cssStyles['td.style' . $cell->getXfIndex()]); |
|
1306 | } |
||
1307 | } |
||
1308 | |||
1309 | // General horizontal alignment: Actual horizontal alignment depends on dataType |
||
1310 | 4 | $sharedStyle = $pSheet->getParent()->getCellXfByIndex($cell->getXfIndex()); |
|
1311 | 4 | if ($sharedStyle->getAlignment()->getHorizontal() == Alignment::HORIZONTAL_GENERAL |
|
1312 | 4 | && isset($this->cssStyles['.' . $cell->getDataType()]['text-align']) |
|
1313 | ) { |
||
1314 | 4 | $cssClass['text-align'] = $this->cssStyles['.' . $cell->getDataType()]['text-align']; |
|
1315 | } |
||
1316 | } |
||
1317 | } |
||
1318 | |||
1319 | // Hyperlink? |
||
1320 | 14 | if ($pSheet->hyperlinkExists($coordinate) && !$pSheet->getHyperlink($coordinate)->isInternal()) { |
|
1321 | 4 | $cellData = '<a href="' . htmlspecialchars($pSheet->getHyperlink($coordinate)->getUrl()) . '" title="' . htmlspecialchars($pSheet->getHyperlink($coordinate)->getTooltip()) . '">' . $cellData . '</a>'; |
|
1322 | } |
||
1323 | |||
1324 | // Should the cell be written or is it swallowed by a rowspan or colspan? |
||
1325 | 14 | $writeCell = !(isset($this->isSpannedCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum]) |
|
1326 | 14 | && $this->isSpannedCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum]); |
|
1327 | |||
1328 | // Colspan and Rowspan |
||
1329 | 14 | $colspan = 1; |
|
1330 | 14 | $rowspan = 1; |
|
1331 | 14 | if (isset($this->isBaseCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum])) { |
|
1332 | 6 | $spans = $this->isBaseCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum]; |
|
1333 | 6 | $rowSpan = $spans['rowspan']; |
|
1334 | 6 | $colSpan = $spans['colspan']; |
|
1335 | |||
1336 | // Also apply style from last cell in merge to fix borders - |
||
1337 | // relies on !important for non-none border declarations in createCSSStyleBorder |
||
1338 | 6 | $endCellCoord = Coordinate::stringFromColumnIndex($colNum + $colSpan) . ($pRow + $rowSpan); |
|
1339 | 6 | if (!$this->useInlineCss) { |
|
1340 | 3 | $cssClass .= ' style' . $pSheet->getCell($endCellCoord)->getXfIndex(); |
|
1341 | } |
||
1342 | } |
||
1343 | |||
1344 | // Write |
||
1345 | 14 | if ($writeCell) { |
|
1346 | // Column start |
||
1347 | 14 | $html .= ' <' . $cellType; |
|
1348 | 14 | if (!$this->useInlineCss) { |
|
1349 | 11 | $html .= ' class="' . $cssClass . '"'; |
|
1 ignored issue
–
show
|
|||
1350 | } else { |
||
1351 | //** Necessary redundant code for the sake of \PhpOffice\PhpSpreadsheet\Writer\Pdf ** |
||
1352 | // We must explicitly write the width of the <td> element because TCPDF |
||
1353 | // does not recognize e.g. <col style="width:42pt"> |
||
1354 | 4 | $width = 0; |
|
1355 | 4 | $i = $colNum - 1; |
|
1356 | 4 | $e = $colNum + $colSpan - 1; |
|
1357 | 4 | while ($i++ < $e) { |
|
1358 | 4 | if (isset($this->columnWidths[$sheetIndex][$i])) { |
|
1359 | 4 | $width += $this->columnWidths[$sheetIndex][$i]; |
|
1360 | } |
||
1361 | } |
||
1362 | 4 | $cssClass['width'] = $width . 'pt'; |
|
1363 | |||
1364 | // We must also explicitly write the height of the <td> element because TCPDF |
||
1365 | // does not recognize e.g. <tr style="height:50pt"> |
||
1366 | 4 | if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]['height'])) { |
|
1367 | 1 | $height = $this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]['height']; |
|
1368 | 1 | $cssClass['height'] = $height; |
|
1369 | } |
||
1370 | //** end of redundant code ** |
||
1371 | |||
1372 | 4 | $html .= ' style="' . $this->assembleCSS($cssClass) . '"'; |
|
1 ignored issue
–
show
|
|||
1373 | } |
||
1374 | 14 | if ($colSpan > 1) { |
|
1375 | 6 | $html .= ' colspan="' . $colSpan . '"'; |
|
1376 | } |
||
1377 | 14 | if ($rowSpan > 1) { |
|
1378 | $html .= ' rowspan="' . $rowSpan . '"'; |
||
1379 | } |
||
1380 | 14 | $html .= '>'; |
|
1381 | |||
1382 | 14 | $html .= $this->writeComment($pSheet, $coordinate); |
|
1383 | |||
1384 | // Image? |
||
1385 | 14 | $html .= $this->writeImageInCell($pSheet, $coordinate); |
|
1386 | |||
1387 | // Chart? |
||
1388 | 14 | if ($this->includeCharts) { |
|
1389 | $html .= $this->writeChartInCell($pSheet, $coordinate); |
||
1390 | } |
||
1391 | |||
1392 | // Cell data |
||
1393 | 14 | $html .= $cellData; |
|
1394 | |||
1395 | // Column end |
||
1396 | 14 | $html .= '</' . $cellType . '>' . PHP_EOL; |
|
1397 | } |
||
1398 | |||
1399 | // Next column |
||
1400 | 14 | ++$colNum; |
|
1401 | } |
||
1402 | |||
1403 | // Write row end |
||
1404 | 14 | $html .= ' </tr>' . PHP_EOL; |
|
1405 | |||
1406 | // Return |
||
1407 | 14 | return $html; |
|
1408 | } |
||
1409 | |||
1410 | /** |
||
1411 | * Takes array where of CSS properties / values and converts to CSS string. |
||
1412 | * |
||
1413 | * @param array $pValue |
||
1414 | * |
||
1415 | * @return string |
||
1416 | */ |
||
1417 | 14 | private function assembleCSS(array $pValue = []) |
|
1418 | { |
||
1419 | 14 | $pairs = []; |
|
1420 | 14 | foreach ($pValue as $property => $value) { |
|
1421 | 14 | $pairs[] = $property . ':' . $value; |
|
1422 | } |
||
1423 | 14 | $string = implode('; ', $pairs); |
|
1424 | |||
1425 | 14 | return $string; |
|
1426 | } |
||
1427 | |||
1428 | /** |
||
1429 | * Get images root. |
||
1430 | * |
||
1431 | * @return string |
||
1432 | */ |
||
1433 | 4 | public function getImagesRoot() |
|
1434 | { |
||
1435 | 4 | return $this->imagesRoot; |
|
1436 | } |
||
1437 | |||
1438 | /** |
||
1439 | * Set images root. |
||
1440 | * |
||
1441 | * @param string $pValue |
||
1442 | * |
||
1443 | * @return $this |
||
1444 | */ |
||
1445 | public function setImagesRoot($pValue) |
||
1446 | { |
||
1447 | $this->imagesRoot = $pValue; |
||
1448 | |||
1449 | return $this; |
||
1450 | } |
||
1451 | |||
1452 | /** |
||
1453 | * Get embed images. |
||
1454 | * |
||
1455 | * @return bool |
||
1456 | */ |
||
1457 | public function getEmbedImages() |
||
1458 | { |
||
1459 | return $this->embedImages; |
||
1460 | } |
||
1461 | |||
1462 | /** |
||
1463 | * Set embed images. |
||
1464 | * |
||
1465 | * @param bool $pValue |
||
1466 | * |
||
1467 | * @return $this |
||
1468 | */ |
||
1469 | public function setEmbedImages($pValue) |
||
1470 | { |
||
1471 | $this->embedImages = $pValue; |
||
1472 | |||
1473 | return $this; |
||
1474 | } |
||
1475 | |||
1476 | /** |
||
1477 | * Get use inline CSS? |
||
1478 | * |
||
1479 | * @return bool |
||
1480 | */ |
||
1481 | public function getUseInlineCss() |
||
1482 | { |
||
1483 | return $this->useInlineCss; |
||
1484 | } |
||
1485 | |||
1486 | /** |
||
1487 | * Set use inline CSS? |
||
1488 | * |
||
1489 | * @param bool $pValue |
||
1490 | * |
||
1491 | * @return $this |
||
1492 | */ |
||
1493 | 8 | public function setUseInlineCss($pValue) |
|
1494 | { |
||
1495 | 8 | $this->useInlineCss = $pValue; |
|
1496 | |||
1497 | 8 | return $this; |
|
1498 | } |
||
1499 | |||
1500 | /** |
||
1501 | * Get use embedded CSS? |
||
1502 | * |
||
1503 | * @return bool |
||
1504 | */ |
||
1505 | public function getUseEmbeddedCSS() |
||
1506 | { |
||
1507 | return $this->useEmbeddedCSS; |
||
1508 | } |
||
1509 | |||
1510 | /** |
||
1511 | * Set use embedded CSS? |
||
1512 | * |
||
1513 | * @param bool $pValue |
||
1514 | * |
||
1515 | * @return $this |
||
1516 | */ |
||
1517 | public function setUseEmbeddedCSS($pValue) |
||
1518 | { |
||
1519 | $this->useEmbeddedCSS = $pValue; |
||
1520 | |||
1521 | return $this; |
||
1522 | } |
||
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) |
|
1533 | { |
||
1534 | // Color information, e.g. [Red] is always at the beginning |
||
1535 | 4 | $color = null; // initialize |
|
1536 | 4 | $matches = []; |
|
1537 | |||
1538 | 4 | $color_regex = '/^\\[[a-zA-Z]+\\]/'; |
|
1539 | 4 | if (preg_match($color_regex, $pFormat, $matches)) { |
|
1540 | $color = str_replace(['[', ']'], '', $matches[0]); |
||
1541 | $color = strtolower($color); |
||
1542 | } |
||
1543 | |||
1544 | // convert to PCDATA |
||
1545 | 4 | $value = htmlspecialchars($pValue); |
|
1546 | |||
1547 | // color span tag |
||
1548 | 4 | if ($color !== null) { |
|
1549 | $value = '<span style="color:' . $color . '">' . $value . '</span>'; |
||
1550 | } |
||
1551 | |||
1552 | 4 | return $value; |
|
1553 | } |
||
1554 | |||
1555 | /** |
||
1556 | * Calculate information about HTML colspan and rowspan which is not always the same as Excel's. |
||
1557 | */ |
||
1558 | 14 | private function calculateSpans() |
|
1559 | { |
||
1560 | // Identify all cells that should be omitted in HTML due to cell merge. |
||
1561 | // In HTML only the upper-left cell should be written and it should have |
||
1562 | // appropriate rowspan / colspan attribute |
||
1563 | 14 | $sheetIndexes = $this->sheetIndex !== null ? |
|
1564 | 14 | [$this->sheetIndex] : range(0, $this->spreadsheet->getSheetCount() - 1); |
|
1565 | |||
1566 | 14 | foreach ($sheetIndexes as $sheetIndex) { |
|
1567 | 14 | $sheet = $this->spreadsheet->getSheet($sheetIndex); |
|
1568 | |||
1569 | 14 | $candidateSpannedRow = []; |
|
1570 | |||
1571 | // loop through all Excel merged cells |
||
1572 | 14 | foreach ($sheet->getMergeCells() as $cells) { |
|
1573 | 6 | [$cells] = Coordinate::splitRange($cells); |
|
1574 | 6 | $first = $cells[0]; |
|
1575 | 6 | $last = $cells[1]; |
|
1576 | |||
1577 | 6 | [$fc, $fr] = Coordinate::coordinateFromString($first); |
|
1578 | 6 | $fc = Coordinate::columnIndexFromString($fc) - 1; |
|
1579 | |||
1580 | 6 | [$lc, $lr] = Coordinate::coordinateFromString($last); |
|
1581 | 6 | $lc = Coordinate::columnIndexFromString($lc) - 1; |
|
1582 | |||
1583 | // loop through the individual cells in the individual merge |
||
1584 | 6 | $r = $fr - 1; |
|
1585 | 6 | while ($r++ < $lr) { |
|
1586 | // also, flag this row as a HTML row that is candidate to be omitted |
||
1587 | 6 | $candidateSpannedRow[$r] = $r; |
|
1588 | |||
1589 | 6 | $c = $fc - 1; |
|
1590 | 6 | while ($c++ < $lc) { |
|
1591 | 6 | if (!($c == $fc && $r == $fr)) { |
|
1592 | // not the upper-left cell (should not be written in HTML) |
||
1593 | 6 | $this->isSpannedCell[$sheetIndex][$r][$c] = [ |
|
1594 | 6 | 'baseCell' => [$fr, $fc], |
|
1595 | ]; |
||
1596 | } else { |
||
1597 | // upper-left is the base cell that should hold the colspan/rowspan attribute |
||
1598 | 6 | $this->isBaseCell[$sheetIndex][$r][$c] = [ |
|
1599 | 6 | 'xlrowspan' => $lr - $fr + 1, // Excel rowspan |
|
1600 | 6 | 'rowspan' => $lr - $fr + 1, // HTML rowspan, value may change |
|
1601 | 6 | 'xlcolspan' => $lc - $fc + 1, // Excel colspan |
|
1602 | 6 | 'colspan' => $lc - $fc + 1, // HTML colspan, value may change |
|
1603 | ]; |
||
1604 | } |
||
1605 | } |
||
1606 | } |
||
1607 | } |
||
1608 | |||
1609 | // Identify which rows should be omitted in HTML. These are the rows where all the cells |
||
1610 | // participate in a merge and the where base cells are somewhere above. |
||
1611 | 14 | $countColumns = Coordinate::columnIndexFromString($sheet->getHighestColumn()); |
|
1612 | 14 | foreach ($candidateSpannedRow as $rowIndex) { |
|
1613 | 6 | if (isset($this->isSpannedCell[$sheetIndex][$rowIndex])) { |
|
1614 | 6 | if (count($this->isSpannedCell[$sheetIndex][$rowIndex]) == $countColumns) { |
|
1615 | 4 | $this->isSpannedRow[$sheetIndex][$rowIndex] = $rowIndex; |
|
1616 | } |
||
1617 | } |
||
1618 | } |
||
1619 | |||
1620 | // For each of the omitted rows we found above, the affected rowspans should be subtracted by 1 |
||
1621 | 14 | if (isset($this->isSpannedRow[$sheetIndex])) { |
|
1622 | 4 | foreach ($this->isSpannedRow[$sheetIndex] as $rowIndex) { |
|
1623 | 4 | $adjustedBaseCells = []; |
|
1624 | 4 | $c = -1; |
|
1625 | 4 | $e = $countColumns - 1; |
|
1626 | 4 | while ($c++ < $e) { |
|
1627 | 4 | $baseCell = $this->isSpannedCell[$sheetIndex][$rowIndex][$c]['baseCell']; |
|
1628 | |||
1629 | 4 | if (!in_array($baseCell, $adjustedBaseCells)) { |
|
1630 | // subtract rowspan by 1 |
||
1631 | 4 | --$this->isBaseCell[$sheetIndex][$baseCell[0]][$baseCell[1]]['rowspan']; |
|
1632 | 4 | $adjustedBaseCells[] = $baseCell; |
|
1633 | } |
||
1634 | } |
||
1635 | } |
||
1636 | } |
||
1637 | |||
1638 | // TODO: Same for columns |
||
1639 | } |
||
1640 | |||
1641 | // We have calculated the spans |
||
1642 | 14 | $this->spansAreCalculated = true; |
|
1643 | 14 | } |
|
1644 | |||
1645 | 14 | private function setMargins(Worksheet $pSheet) |
|
1646 | { |
||
1647 | 14 | $htmlPage = '@page { '; |
|
1648 | 14 | $htmlBody = 'body { '; |
|
1649 | |||
1650 | 14 | $left = StringHelper::formatNumber($pSheet->getPageMargins()->getLeft()) . 'in; '; |
|
1651 | 14 | $htmlPage .= 'margin-left: ' . $left; |
|
1652 | 14 | $htmlBody .= 'margin-left: ' . $left; |
|
1653 | 14 | $right = StringHelper::formatNumber($pSheet->getPageMargins()->getRight()) . 'in; '; |
|
1654 | 14 | $htmlPage .= 'margin-right: ' . $right; |
|
1655 | 14 | $htmlBody .= 'margin-right: ' . $right; |
|
1656 | 14 | $top = StringHelper::formatNumber($pSheet->getPageMargins()->getTop()) . 'in; '; |
|
1657 | 14 | $htmlPage .= 'margin-top: ' . $top; |
|
1658 | 14 | $htmlBody .= 'margin-top: ' . $top; |
|
1659 | 14 | $bottom = StringHelper::formatNumber($pSheet->getPageMargins()->getBottom()) . 'in; '; |
|
1660 | 14 | $htmlPage .= 'margin-bottom: ' . $bottom; |
|
1661 | 14 | $htmlBody .= 'margin-bottom: ' . $bottom; |
|
1662 | |||
1663 | 14 | $htmlPage .= "}\n"; |
|
1664 | 14 | $htmlBody .= "}\n"; |
|
1665 | |||
1666 | 14 | return "<style>\n" . $htmlPage . $htmlBody . "</style>\n"; |
|
1667 | } |
||
1668 | |||
1669 | /** |
||
1670 | * Write a comment in the same format as LibreOffice. |
||
1671 | * |
||
1672 | * @see https://github.com/LibreOffice/core/blob/9fc9bf3240f8c62ad7859947ab8a033ac1fe93fa/sc/source/filter/html/htmlexp.cxx#L1073-L1092 |
||
1673 | * |
||
1674 | * @param Worksheet $pSheet |
||
1675 | * @param string $coordinate |
||
1676 | * |
||
1677 | * @return string |
||
1678 | */ |
||
1679 | 14 | private function writeComment(Worksheet $pSheet, $coordinate) |
|
1689 | } |
||
1690 | } |
||
1691 |