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