Completed
Push — master ( 7cb488...7e1257 )
by Adrien
09:30
created

Html::loadIntoExisting()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.2098

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 7
nc 3
nop 2
dl 0
loc 16
ccs 5
cts 7
cp 0.7143
crap 3.2098
rs 10
c 1
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Reader;
4
5
use DOMDocument;
6
use DOMElement;
7
use DOMNode;
8
use DOMText;
9
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
10
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
11
use PhpOffice\PhpSpreadsheet\Spreadsheet;
12
use PhpOffice\PhpSpreadsheet\Style\Border;
13
use PhpOffice\PhpSpreadsheet\Style\Color;
14
use PhpOffice\PhpSpreadsheet\Style\Fill;
15
use PhpOffice\PhpSpreadsheet\Style\Font;
16
use PhpOffice\PhpSpreadsheet\Style\Style;
17
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
18
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
19
20
/** PhpSpreadsheet root directory */
21
class Html extends BaseReader
22
{
23
    /**
24
     * Sample size to read to determine if it's HTML or not.
25
     */
26
    const TEST_SAMPLE_SIZE = 2048;
27
28
    /**
29
     * Input encoding.
30
     *
31
     * @var string
32
     */
33
    protected $inputEncoding = 'ANSI';
34
35
    /**
36
     * Sheet index to read.
37
     *
38
     * @var int
39
     */
40
    protected $sheetIndex = 0;
41
42
    /**
43
     * Formats.
44
     *
45
     * @var array
46
     */
47
    protected $formats = [
48
        'h1' => [
49
            'font' => [
50
                'bold' => true,
51
                'size' => 24,
52
            ],
53
        ], //    Bold, 24pt
54
        'h2' => [
55
            'font' => [
56
                'bold' => true,
57
                'size' => 18,
58
            ],
59
        ], //    Bold, 18pt
60
        'h3' => [
61
            'font' => [
62
                'bold' => true,
63
                'size' => 13.5,
64
            ],
65
        ], //    Bold, 13.5pt
66
        'h4' => [
67
            'font' => [
68
                'bold' => true,
69
                'size' => 12,
70
            ],
71
        ], //    Bold, 12pt
72
        'h5' => [
73
            'font' => [
74
                'bold' => true,
75
                'size' => 10,
76
            ],
77
        ], //    Bold, 10pt
78
        'h6' => [
79
            'font' => [
80
                'bold' => true,
81
                'size' => 7.5,
82
            ],
83
        ], //    Bold, 7.5pt
84
        'a' => [
85
            'font' => [
86
                'underline' => true,
87
                'color' => [
88
                    'argb' => Color::COLOR_BLUE,
89
                ],
90
            ],
91
        ], //    Blue underlined
92
        'hr' => [
93
            'borders' => [
94
                'bottom' => [
95
                    'borderStyle' => Border::BORDER_THIN,
96
                    'color' => [
97
                        Color::COLOR_BLACK,
98
                    ],
99
                ],
100
            ],
101
        ], //    Bottom border
102
        'strong' => [
103
            'font' => [
104
                'bold' => true,
105
            ],
106
        ], //    Bold
107
        'b' => [
108
            'font' => [
109
                'bold' => true,
110
            ],
111
        ], //    Bold
112
        'i' => [
113
            'font' => [
114
                'italic' => true,
115
            ],
116
        ], //    Italic
117
        'em' => [
118
            'font' => [
119
                'italic' => true,
120
            ],
121
        ], //    Italic
122
    ];
123
124
    protected $rowspan = [];
125
126
    /**
127
     * Create a new HTML Reader instance.
128
     */
129 32
    public function __construct()
130
    {
131 32
        parent::__construct();
132 32
        $this->securityScanner = XmlScanner::getInstance($this);
133 32
    }
134
135
    /**
136
     * Validate that the current file is an HTML file.
137
     *
138
     * @param string $pFilename
139
     *
140
     * @return bool
141
     */
142 28
    public function canRead($pFilename)
143
    {
144
        // Check if file exists
145
        try {
146 28
            $this->openFile($pFilename);
147
        } catch (Exception $e) {
148
            return false;
149
        }
150
151 28
        $beginning = $this->readBeginning();
152 28
        $startWithTag = self::startsWithTag($beginning);
153 28
        $containsTags = self::containsTags($beginning);
154 28
        $endsWithTag = self::endsWithTag($this->readEnding());
155
156 28
        fclose($this->fileHandle);
157
158 28
        return $startWithTag && $containsTags && $endsWithTag;
159
    }
160
161 28
    private function readBeginning()
162
    {
163 28
        fseek($this->fileHandle, 0);
164
165 28
        return fread($this->fileHandle, self::TEST_SAMPLE_SIZE);
166
    }
167
168 28
    private function readEnding()
169
    {
170 28
        $meta = stream_get_meta_data($this->fileHandle);
171 28
        $filename = $meta['uri'];
172
173 28
        $size = filesize($filename);
174 28
        if ($size === 0) {
175 1
            return '';
176
        }
177
178 27
        $blockSize = self::TEST_SAMPLE_SIZE;
179 27
        if ($size < $blockSize) {
180 13
            $blockSize = $size;
181
        }
182
183 27
        fseek($this->fileHandle, $size - $blockSize);
184
185 27
        return fread($this->fileHandle, $blockSize);
186
    }
187
188 28
    private static function startsWithTag($data)
189
    {
190 28
        return '<' === substr(trim($data), 0, 1);
191
    }
192
193 28
    private static function endsWithTag($data)
194
    {
195 28
        return '>' === substr(trim($data), -1, 1);
196
    }
197
198 28
    private static function containsTags($data)
199
    {
200 28
        return strlen($data) !== strlen(strip_tags($data));
201
    }
202
203
    /**
204
     * Loads Spreadsheet from file.
205
     *
206
     * @param string $pFilename
207
     *
208
     * @return Spreadsheet
209
     */
210 21
    public function load($pFilename)
211
    {
212
        // Create new Spreadsheet
213 21
        $spreadsheet = new Spreadsheet();
214
215
        // Load into this instance
216 21
        return $this->loadIntoExisting($pFilename, $spreadsheet);
217
    }
218
219
    /**
220
     * Set input encoding.
221
     *
222
     * @param string $pValue Input encoding, eg: 'ANSI'
223
     *
224
     * @return $this
225
     */
226 2
    public function setInputEncoding($pValue)
227
    {
228 2
        $this->inputEncoding = $pValue;
229
230 2
        return $this;
231
    }
232
233
    /**
234
     * Get input encoding.
235
     *
236
     * @return string
237
     */
238
    public function getInputEncoding()
239
    {
240
        return $this->inputEncoding;
241
    }
242
243
    //    Data Array used for testing only, should write to Spreadsheet object on completion of tests
244
    protected $dataArray = [];
245
246
    protected $tableLevel = 0;
247
248
    protected $nestedColumn = ['A'];
249
250 23
    protected function setTableStartColumn($column)
251
    {
252 23
        if ($this->tableLevel == 0) {
253 23
            $column = 'A';
254
        }
255 23
        ++$this->tableLevel;
256 23
        $this->nestedColumn[$this->tableLevel] = $column;
257
258 23
        return $this->nestedColumn[$this->tableLevel];
259
    }
260
261 23
    protected function getTableStartColumn()
262
    {
263 23
        return $this->nestedColumn[$this->tableLevel];
264
    }
265
266 23
    protected function releaseTableStartColumn()
267
    {
268 23
        --$this->tableLevel;
269
270 23
        return array_pop($this->nestedColumn);
271
    }
272
273 23
    protected function flushCell(Worksheet $sheet, $column, $row, &$cellContent): void
274
    {
275 23
        if (is_string($cellContent)) {
276
            //    Simple String content
277 23
            if (trim($cellContent) > '') {
278
                //    Only actually write it if there's content in the string
279
                //    Write to worksheet to be done here...
280
                //    ... we return the cell so we can mess about with styles more easily
281 22
                $sheet->setCellValue($column . $row, $cellContent);
282 23
                $this->dataArray[$row][$column] = $cellContent;
283
            }
284
        } else {
285
            //    We have a Rich Text run
286
            //    TODO
287
            $this->dataArray[$row][$column] = 'RICH TEXT: ' . $cellContent;
288
        }
289 23
        $cellContent = (string) '';
290 23
    }
291
292
    /**
293
     * @param int $row
294
     * @param string $column
295
     * @param string $cellContent
296
     */
297
    protected function processDomElement(DOMNode $element, Worksheet $sheet, &$row, &$column, &$cellContent): void
298
    {
299 23
        foreach ($element->childNodes as $child) {
300
            if ($child instanceof DOMText) {
301 23
                $domText = preg_replace('/\s+/u', ' ', trim($child->nodeValue));
302 23
                if (is_string($cellContent)) {
303 23
                    //    simply append the text if the cell content is a plain text string
304 23
                    $cellContent .= $domText;
305
                }
306 23
                //    but if we have a rich text run instead, we need to append it correctly
307
                    //    TODO
308
            } elseif ($child instanceof DOMElement) {
309
                $attributeArray = [];
310 23
                foreach ($child->attributes as $attribute) {
311 23
                    $attributeArray[$attribute->name] = $attribute->value;
312 23
                }
313 20
314
                switch ($child->nodeName) {
315
                    case 'meta':
316 23
                        foreach ($attributeArray as $attributeName => $attributeValue) {
317 23
                            // Extract character set, so we can convert to UTF-8 if required
318 10
                            if ($attributeName === 'charset') {
319
                                $this->setInputEncoding($attributeValue);
320 10
                            }
321 2
                        }
322
                        $this->processDomElement($child, $sheet, $row, $column, $cellContent);
323
324 10
                        break;
325
                    case 'title':
326 10
                        $this->processDomElement($child, $sheet, $row, $column, $cellContent);
327 23
                        $sheet->setTitle($cellContent, true, false);
328 10
                        $cellContent = '';
329 10
330 10
                        break;
331
                    case 'span':
332 10
                    case 'div':
333 23
                    case 'font':
334 23
                    case 'i':
335 23
                    case 'em':
336 23
                    case 'strong':
337 23
                    case 'b':
338 23
                        if (isset($attributeArray['class']) && $attributeArray['class'] === 'comment') {
339 23
                            $sheet->getComment($column . $row)
340 6
                                ->getText()
341 6
                                ->createTextRun($child->textContent);
342 6
343 6
                            break;
344
                        }
345 6
346
                        if ($cellContent > '') {
347
                            $cellContent .= ' ';
348
                        }
349
                        $this->processDomElement($child, $sheet, $row, $column, $cellContent);
350
                        if ($cellContent > '') {
351
                            $cellContent .= ' ';
352
                        }
353
354
                        if (isset($this->formats[$child->nodeName])) {
355
                            $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
356
                        }
357
358
                        break;
359
                    case 'hr':
360
                        $this->flushCell($sheet, $column, $row, $cellContent);
361 23
                        ++$row;
362
                        if (isset($this->formats[$child->nodeName])) {
363
                            $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
364
                        } else {
365
                            $cellContent = '----------';
366
                            $this->flushCell($sheet, $column, $row, $cellContent);
367
                        }
368
                        ++$row;
369
                        // Add a break after a horizontal rule, simply by allowing the code to dropthru
370
                        // no break
371
                    case 'br':
372
                        if ($this->tableLevel > 0) {
373 23
                            //    If we're inside a table, replace with a \n and set the cell to wrap
374 3
                            $cellContent .= "\n";
375
                            $sheet->getStyle($column . $row)->getAlignment()->setWrapText(true);
376 3
                        } else {
377 3
                            //    Otherwise flush our existing content and move the row cursor on
378
                            $this->flushCell($sheet, $column, $row, $cellContent);
379
                            ++$row;
380
                        }
381
382
                        break;
383
                    case 'a':
384 3
                        foreach ($attributeArray as $attributeName => $attributeValue) {
385 23
                            switch ($attributeName) {
386 6
                                case 'href':
387
                                    $sheet->getCell($column . $row)->getHyperlink()->setUrl($attributeValue);
388 6
                                    if (isset($this->formats[$child->nodeName])) {
389
                                        $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
390
                                    }
391
392
                                    break;
393
                                case 'class':
394
                                    if ($attributeValue === 'comment-indicator') {
395 6
                                        break; // Ignore - it's just a red square.
396 6
                                    }
397 6
                            }
398
                        }
399
                        $cellContent .= ' ';
400
                        $this->processDomElement($child, $sheet, $row, $column, $cellContent);
401 6
402 6
                        break;
403
                    case 'h1':
404 6
                    case 'h2':
405 23
                    case 'h3':
406 23
                    case 'h4':
407 23
                    case 'h5':
408 23
                    case 'h6':
409 23
                    case 'ol':
410 23
                    case 'ul':
411 23
                    case 'p':
412 23
                        if ($this->tableLevel > 0) {
413 23
                            //    If we're inside a table, replace with a \n
414
                            $cellContent .= "\n";
415
                            $this->processDomElement($child, $sheet, $row, $column, $cellContent);
416
                        } else {
417
                            if ($cellContent > '') {
418
                                $this->flushCell($sheet, $column, $row, $cellContent);
419
                                ++$row;
420
                            }
421
                            $this->processDomElement($child, $sheet, $row, $column, $cellContent);
422
                            $this->flushCell($sheet, $column, $row, $cellContent);
423
424
                            if (isset($this->formats[$child->nodeName])) {
425
                                $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
426
                            }
427
428
                            ++$row;
429
                            $column = 'A';
430
                        }
431
432
                        break;
433
                    case 'li':
434
                        if ($this->tableLevel > 0) {
435 23
                            //    If we're inside a table, replace with a \n
436
                            $cellContent .= "\n";
437
                            $this->processDomElement($child, $sheet, $row, $column, $cellContent);
438
                        } else {
439
                            if ($cellContent > '') {
440
                                $this->flushCell($sheet, $column, $row, $cellContent);
441
                            }
442
                            ++$row;
443
                            $this->processDomElement($child, $sheet, $row, $column, $cellContent);
444
                            $this->flushCell($sheet, $column, $row, $cellContent);
445
                            $column = 'A';
446
                        }
447
448
                        break;
449
                    case 'img':
450
                        $this->insertImage($sheet, $column, $row, $attributeArray);
451 23
452 1
                        break;
453
                    case 'table':
454 1
                        $this->flushCell($sheet, $column, $row, $cellContent);
455 23
                        $column = $this->setTableStartColumn($column);
456 23
                        if ($this->tableLevel > 1) {
457 23
                            --$row;
458 23
                        }
459
                        $this->processDomElement($child, $sheet, $row, $column, $cellContent);
460
                        $column = $this->releaseTableStartColumn();
461 23
                        if ($this->tableLevel > 1) {
462 23
                            ++$column;
463 23
                        } else {
464
                            ++$row;
465
                        }
466 23
467
                        break;
468
                    case 'thead':
469 23
                    case 'tbody':
470 23
                        $this->processDomElement($child, $sheet, $row, $column, $cellContent);
471 23
472 9
                        break;
473
                    case 'tr':
474 9
                        $column = $this->getTableStartColumn();
475 23
                        $cellContent = '';
476 23
                        $this->processDomElement($child, $sheet, $row, $column, $cellContent);
477 23
478 23
                        if (isset($attributeArray['height'])) {
479
                            $sheet->getRowDimension($row)->setRowHeight($attributeArray['height']);
480 23
                        }
481
482
                        ++$row;
483
484 23
                        break;
485
                    case 'th':
486 23
                    case 'td':
487 23
                        $this->processDomElement($child, $sheet, $row, $column, $cellContent);
488 23
489 23
                        while (isset($this->rowspan[$column . $row])) {
490
                            ++$column;
491 23
                        }
492 1
493
                        // apply inline style
494
                        $this->applyInlineStyle($sheet, $row, $column, $attributeArray);
495
496 23
                        $this->flushCell($sheet, $column, $row, $cellContent);
497
498 23
                        if (isset($attributeArray['rowspan'], $attributeArray['colspan'])) {
499
                            //create merging rowspan and colspan
500 23
                            $columnTo = $column;
501
                            for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
502
                                ++$columnTo;
503
                            }
504
                            $range = $column . $row . ':' . $columnTo . ($row + (int) $attributeArray['rowspan'] - 1);
505
                            foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) {
506
                                $this->rowspan[$value] = true;
507
                            }
508
                            $sheet->mergeCells($range);
509
                            $column = $columnTo;
510
                        } elseif (isset($attributeArray['rowspan'])) {
511
                            //create merging rowspan
512 23
                            $range = $column . $row . ':' . $column . ($row + (int) $attributeArray['rowspan'] - 1);
513
                            foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) {
514 1
                                $this->rowspan[$value] = true;
515 1
                            }
516 1
                            $sheet->mergeCells($range);
517
                        } elseif (isset($attributeArray['colspan'])) {
518 1
                            //create merging colspan
519 23
                            $columnTo = $column;
520
                            for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
521 2
                                ++$columnTo;
522 2
                            }
523 2
                            $sheet->mergeCells($column . $row . ':' . $columnTo . $row);
524
                            $column = $columnTo;
525 2
                        } elseif (isset($attributeArray['bgcolor'])) {
526 2
                            $sheet->getStyle($column . $row)->applyFromArray(
527 23
                                [
528
                                    'fill' => [
529
                                        'fillType' => Fill::FILL_SOLID,
530
                                        'color' => ['rgb' => $attributeArray['bgcolor']],
531
                                    ],
532
                                ]
533
                            );
534
                        }
535
536
                        if (isset($attributeArray['width'])) {
537
                            $sheet->getColumnDimension($column)->setWidth($attributeArray['width']);
538 23
                        }
539 1
540
                        if (isset($attributeArray['height'])) {
541
                            $sheet->getRowDimension($row)->setRowHeight($attributeArray['height']);
542 23
                        }
543 1
544
                        if (isset($attributeArray['align'])) {
545
                            $sheet->getStyle($column . $row)->getAlignment()->setHorizontal($attributeArray['align']);
546 23
                        }
547 1
548
                        if (isset($attributeArray['valign'])) {
549
                            $sheet->getStyle($column . $row)->getAlignment()->setVertical($attributeArray['valign']);
550 23
                        }
551 1
552
                        if (isset($attributeArray['data-format'])) {
553
                            $sheet->getStyle($column . $row)->getNumberFormat()->setFormatCode($attributeArray['data-format']);
554 23
                        }
555 1
556
                        ++$column;
557
558 23
                        break;
559
                    case 'body':
560 23
                        $row = 1;
561 23
                        $column = 'A';
562 23
                        $cellContent = '';
563 23
                        $this->tableLevel = 0;
564 23
                        $this->processDomElement($child, $sheet, $row, $column, $cellContent);
565 23
566 23
                        break;
567
                    default:
568 23
                        $this->processDomElement($child, $sheet, $row, $column, $cellContent);
569
                }
570 23
            }
571
        }
572
    }
573
574 23
    /**
575
     * Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
576
     *
577
     * @param string $pFilename
578
     *
579
     * @return Spreadsheet
580
     */
581
    public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
582
    {
583
        // Validate
584 21
        if (!$this->canRead($pFilename)) {
585
            throw new Exception($pFilename . ' is an Invalid HTML file.');
586
        }
587 21
588
        // Create a new DOM object
589
        $dom = new DOMDocument();
590
        // Reload the HTML file into the DOM object
591
        $loaded = $dom->loadHTML(mb_convert_encoding($this->securityScanner->scanFile($pFilename), 'HTML-ENTITIES', 'UTF-8'));
592 21
        if ($loaded === false) {
593
            throw new Exception('Failed to load ' . $pFilename . ' as a DOM Document');
594 21
        }
595 21
596
        return $this->loadDocument($dom, $spreadsheet);
597
    }
598
599 21
    /**
600
     * Spreadsheet from content.
601
     *
602
     * @param string $content
603
     */
604
    public function loadFromString($content, ?Spreadsheet $spreadsheet = null): Spreadsheet
605
    {
606
        //    Create a new DOM object
607
        $dom = new DOMDocument();
608
        //    Reload the HTML file into the DOM object
609
        $loaded = $dom->loadHTML(mb_convert_encoding($this->securityScanner->scan($content), 'HTML-ENTITIES', 'UTF-8'));
610 2
        if ($loaded === false) {
611
            throw new Exception('Failed to load content as a DOM Document');
612
        }
613 2
614
        return $this->loadDocument($dom, $spreadsheet ?? new Spreadsheet());
615 2
    }
616 2
617
    /**
618
     * Loads PhpSpreadsheet from DOMDocument into PhpSpreadsheet instance.
619
     */
620 2
    private function loadDocument(DOMDocument $document, Spreadsheet $spreadsheet): Spreadsheet
621
    {
622
        while ($spreadsheet->getSheetCount() <= $this->sheetIndex) {
623
            $spreadsheet->createSheet();
624
        }
625
        $spreadsheet->setActiveSheetIndex($this->sheetIndex);
626
627
        // Discard white space
628
        $document->preserveWhiteSpace = false;
629
630
        $row = 0;
631 23
        $column = 'A';
632
        $content = '';
633 23
        $this->rowspan = [];
634 1
        $this->processDomElement($document, $spreadsheet->getActiveSheet(), $row, $column, $content);
635
636 23
        // Return
637
        return $spreadsheet;
638
    }
639 23
640
    /**
641 23
     * Get sheet index.
642 23
     *
643 23
     * @return int
644 23
     */
645 23
    public function getSheetIndex()
646
    {
647
        return $this->sheetIndex;
648 23
    }
649
650
    /**
651
     * Set sheet index.
652
     *
653
     * @param int $pValue Sheet index
654
     *
655
     * @return $this
656
     */
657
    public function setSheetIndex($pValue)
658
    {
659
        $this->sheetIndex = $pValue;
660
661
        return $this;
662
    }
663
664
    /**
665
     * Apply inline css inline style.
666
     *
667
     * NOTES :
668 1
     * Currently only intended for td & th element,
669
     * and only takes 'background-color' and 'color'; property with HEX color
670 1
     *
671
     * TODO :
672 1
     * - Implement to other propertie, such as border
673
     *
674
     * @param Worksheet $sheet
675
     * @param int $row
676
     * @param string $column
677
     * @param array $attributeArray
678
     */
679
    private function applyInlineStyle(&$sheet, $row, $column, $attributeArray): void
680
    {
681
        if (!isset($attributeArray['style'])) {
682
            return;
683
        }
684
685
        if (isset($attributeArray['rowspan'], $attributeArray['colspan'])) {
686
            $columnTo = $column;
687
            for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
688
                ++$columnTo;
689
            }
690 23
            $range = $column . $row . ':' . $columnTo . ($row + (int) $attributeArray['rowspan'] - 1);
691
            $cellStyle = $sheet->getStyle($range);
692 23
        } elseif (isset($attributeArray['rowspan'])) {
693 20
            $range = $column . $row . ':' . $column . ($row + (int) $attributeArray['rowspan'] - 1);
694
            $cellStyle = $sheet->getStyle($range);
695
        } elseif (isset($attributeArray['colspan'])) {
696 9
            $columnTo = $column;
697
            for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
698
                ++$columnTo;
699 9
            }
700 9
            $range = $column . $row . ':' . $columnTo . $row;
701 9
            $cellStyle = $sheet->getStyle($range);
702 9
        } else {
703 9
            $cellStyle = $sheet->getStyle($column . $row);
704
        }
705 9
706 6
        // add color styles (background & text) from dom element,currently support : td & th, using ONLY inline css style with RGB color
707
        $styles = explode(';', $attributeArray['style']);
708
        foreach ($styles as $st) {
709
            $value = explode(':', $st);
710 9
            $styleName = isset($value[0]) ? trim($value[0]) : null;
711 9
            $styleValue = isset($value[1]) ? trim($value[1]) : null;
712 3
713
            if (!$styleName) {
714 3
                continue;
715
            }
716
717
            switch ($styleName) {
718 3
                case 'background':
719
                case 'background-color':
720 3
                    $styleColor = $this->getStyleColor($styleValue);
721 9
722 3
                    if (!$styleColor) {
723
                        continue 2;
724 3
                    }
725
726
                    $cellStyle->applyFromArray(['fill' => ['fillType' => Fill::FILL_SOLID, 'color' => ['rgb' => $styleColor]]]);
727
728 3
                    break;
729
                case 'color':
730 3
                    $styleColor = $this->getStyleColor($styleValue);
731
732 6
                    if (!$styleColor) {
733 1
                        continue 2;
734
                    }
735 1
736
                    $cellStyle->applyFromArray(['font' => ['color' => ['rgb' => $styleColor]]]);
737 6
738 1
                    break;
739
740 1
                case 'border':
741
                    $this->setBorderStyle($cellStyle, $styleValue, 'allBorders');
742 6
743 1
                    break;
744
745 1
                case 'border-top':
746
                    $this->setBorderStyle($cellStyle, $styleValue, 'top');
747 6
748 1
                    break;
749
750 1
                case 'border-bottom':
751
                    $this->setBorderStyle($cellStyle, $styleValue, 'bottom');
752 6
753 1
                    break;
754
755 1
                case 'border-left':
756
                    $this->setBorderStyle($cellStyle, $styleValue, 'left');
757 5
758 1
                    break;
759 1
760
                case 'border-right':
761
                    $this->setBorderStyle($cellStyle, $styleValue, 'right');
762 1
763
                    break;
764 5
765 1
                case 'font-size':
766 1
                    $cellStyle->getFont()->setSize(
767
                        (float) $styleValue
768
                    );
769 1
770
                    break;
771 5
772 1
                case 'font-weight':
773 1
                    if ($styleValue === 'bold' || $styleValue >= 500) {
774
                        $cellStyle->getFont()->setBold(true);
775
                    }
776 1
777
                    break;
778 5
779 1
                case 'font-style':
780
                    if ($styleValue === 'italic') {
781 1
                        $cellStyle->getFont()->setItalic(true);
782
                    }
783 5
784
                    break;
785 1
786 1
                case 'font-family':
787
                    $cellStyle->getFont()->setName(str_replace('\'', '', $styleValue));
788 1
789 1
                    break;
790 1
791
                case 'text-decoration':
792 1
                    switch ($styleValue) {
793
                        case 'underline':
794
                            $cellStyle->getFont()->setUnderline(Font::UNDERLINE_SINGLE);
795 1
796
                            break;
797 4
                        case 'line-through':
798 1
                            $cellStyle->getFont()->setStrikethrough(true);
799
800 1
                            break;
801
                    }
802 4
803 2
                    break;
804
805 2
                case 'text-align':
806
                    $cellStyle->getAlignment()->setHorizontal($styleValue);
807 4
808 1
                    break;
809 1
810
                case 'vertical-align':
811
                    $cellStyle->getAlignment()->setVertical($styleValue);
812 1
813
                    break;
814 3
815 1
                case 'width':
816 1
                    $sheet->getColumnDimension($column)->setWidth(
817
                        str_replace('px', '', $styleValue)
818
                    );
819 1
820
                    break;
821 2
822 1
                case 'height':
823 1
                    $sheet->getRowDimension($row)->setRowHeight(
824
                        str_replace('px', '', $styleValue)
825
                    );
826 1
827
                    break;
828 2
829 2
                case 'word-wrap':
830 2
                    $cellStyle->getAlignment()->setWrapText(
831
                        $styleValue === 'break-word'
832
                    );
833 2
834
                    break;
835
836 9
                case 'text-indent':
837
                    $cellStyle->getAlignment()->setIndent(
838
                        (int) str_replace(['px'], '', $styleValue)
839
                    );
840
841
                    break;
842
            }
843
        }
844
    }
845 4
846
    /**
847 4
     * Check if has #, so we can get clean hex.
848 4
     *
849
     * @param $value
850
     *
851
     * @return null|string
852
     */
853
    public function getStyleColor($value)
854
    {
855
        if (strpos($value, '#') === 0) {
856
            return substr($value, 1);
857
        }
858
859
        return null;
860 1
    }
861
862 1
    /**
863
     * @param string    $column
864
     * @param int       $row
865
     */
866 1
    private function insertImage(Worksheet $sheet, $column, $row, array $attributes): void
867 1
    {
868 1
        if (!isset($attributes['src'])) {
869 1
            return;
870
        }
871 1
872 1
        $src = urldecode($attributes['src']);
873 1
        $width = isset($attributes['width']) ? (float) $attributes['width'] : null;
874 1
        $height = isset($attributes['height']) ? (float) $attributes['height'] : null;
875 1
        $name = isset($attributes['alt']) ? (float) $attributes['alt'] : null;
876 1
877 1
        $drawing = new Drawing();
878
        $drawing->setPath($src);
879 1
        $drawing->setWorksheet($sheet);
880
        $drawing->setCoordinates($column . $row);
881
        $drawing->setOffsetX(0);
882
        $drawing->setOffsetY(10);
883 1
        $drawing->setResizeProportional(true);
884
885
        if ($name) {
886
            $drawing->setName($name);
887 1
        }
888
889
        if ($width) {
890
            $drawing->setWidth((int) $width);
891 1
        }
892 1
893
        if ($height) {
894
            $drawing->setHeight((int) $height);
895 1
        }
896 1
897
        $sheet->getColumnDimension($column)->setWidth(
898 1
            $drawing->getWidth() / 6
899
        );
900
901
        $sheet->getRowDimension($row)->setRowHeight(
902
            $drawing->getHeight() * 0.9
903
        );
904
    }
905
906
    /**
907 1
     * Map html border style to PhpSpreadsheet border style.
908
     *
909
     * @param  string $style
910 1
     *
911 1
     * @return null|string
912
     */
913
    public function getBorderStyle($style)
914
    {
915
        switch ($style) {
916
            case 'solid':
917
                return Border::BORDER_THIN;
918
            case 'dashed':
919
                return Border::BORDER_DASHED;
920
            case 'dotted':
921
                return Border::BORDER_DOTTED;
922
            case 'medium':
923
                return Border::BORDER_MEDIUM;
924
            case 'thick':
925
                return Border::BORDER_THICK;
926
            case 'none':
927
                return Border::BORDER_NONE;
928
            case 'dash-dot':
929
                return Border::BORDER_DASHDOT;
930
            case 'dash-dot-dot':
931
                return Border::BORDER_DASHDOTDOT;
932
            case 'double':
933
                return Border::BORDER_DOUBLE;
934
            case 'hair':
935
                return Border::BORDER_HAIR;
936
            case 'medium-dash-dot':
937
                return Border::BORDER_MEDIUMDASHDOT;
938
            case 'medium-dash-dot-dot':
939
                return Border::BORDER_MEDIUMDASHDOTDOT;
940
            case 'medium-dashed':
941
                return Border::BORDER_MEDIUMDASHED;
942
            case 'slant-dash-dot':
943
                return Border::BORDER_SLANTDASHDOT;
944
        }
945
946
        return null;
947
    }
948 1
949
    /**
950 1
     * @param string $styleValue
951
     * @param string $type
952 1
     */
953
    private function setBorderStyle(Style $cellStyle, $styleValue, $type): void
954
    {
955 1
        if (trim($styleValue) === Border::BORDER_NONE) {
956 1
            $borderStyle = Border::BORDER_NONE;
957
            $color = null;
958
        } else {
959
            [, $borderStyle, $color] = explode(' ', $styleValue);
960 1
        }
961
962
        $cellStyle->applyFromArray([
963
            'borders' => [
964
                $type => [
965
                    'borderStyle' => $this->getBorderStyle($borderStyle),
966
                    'color' => ['rgb' => $this->getStyleColor($color)],
967
                ],
968
            ],
969
        ]);
970
    }
971
}
972