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