Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 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  }