Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
<?php

namespace PhpOffice\PhpSpreadsheet\Reader;

use DOMDocument;
use DOMElement;
use DOMNode;
use DOMText;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
> use PhpOffice\PhpSpreadsheet\Helper\Dimension as CssDimension;
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\Border; use PhpOffice\PhpSpreadsheet\Style\Color; use PhpOffice\PhpSpreadsheet\Style\Fill; use PhpOffice\PhpSpreadsheet\Style\Font; use PhpOffice\PhpSpreadsheet\Style\Style; use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use Throwable;
< /** PhpSpreadsheet root directory */
class Html extends BaseReader { /** * Sample size to read to determine if it's HTML or not. */ const TEST_SAMPLE_SIZE = 2048; /** * Input encoding. * * @var string */ protected $inputEncoding = 'ANSI'; /** * Sheet index to read. * * @var int */ protected $sheetIndex = 0; /** * Formats. * * @var array */ protected $formats = [ 'h1' => [ 'font' => [ 'bold' => true, 'size' => 24, ], ], // Bold, 24pt 'h2' => [ 'font' => [ 'bold' => true, 'size' => 18, ], ], // Bold, 18pt 'h3' => [ 'font' => [ 'bold' => true, 'size' => 13.5, ], ], // Bold, 13.5pt 'h4' => [ 'font' => [ 'bold' => true, 'size' => 12, ], ], // Bold, 12pt 'h5' => [ 'font' => [ 'bold' => true, 'size' => 10, ], ], // Bold, 10pt 'h6' => [ 'font' => [ 'bold' => true, 'size' => 7.5, ], ], // Bold, 7.5pt 'a' => [ 'font' => [ 'underline' => true, 'color' => [ 'argb' => Color::COLOR_BLUE, ], ], ], // Blue underlined 'hr' => [ 'borders' => [ 'bottom' => [ 'borderStyle' => Border::BORDER_THIN, 'color' => [ Color::COLOR_BLACK, ], ], ], ], // Bottom border 'strong' => [ 'font' => [ 'bold' => true, ], ], // Bold 'b' => [ 'font' => [ 'bold' => true, ], ], // Bold 'i' => [ 'font' => [ 'italic' => true, ], ], // Italic 'em' => [ 'font' => [ 'italic' => true, ], ], // Italic ];
> /** @var array */
protected $rowspan = []; /** * Create a new HTML Reader instance. */ public function __construct() { parent::__construct(); $this->securityScanner = XmlScanner::getInstance($this); } /** * Validate that the current file is an HTML file.
< * < * @param string $pFilename < * < * @return bool
*/
< public function canRead($pFilename)
> public function canRead(string $filename): bool
{ // Check if file exists try {
< $this->openFile($pFilename);
> $this->openFile($filename);
} catch (Exception $e) { return false; } $beginning = $this->readBeginning(); $startWithTag = self::startsWithTag($beginning); $containsTags = self::containsTags($beginning); $endsWithTag = self::endsWithTag($this->readEnding()); fclose($this->fileHandle); return $startWithTag && $containsTags && $endsWithTag; }
< private function readBeginning()
> private function readBeginning(): string
{ fseek($this->fileHandle, 0);
< return fread($this->fileHandle, self::TEST_SAMPLE_SIZE);
> return (string) fread($this->fileHandle, self::TEST_SAMPLE_SIZE);
}
< private function readEnding()
> private function readEnding(): string
{ $meta = stream_get_meta_data($this->fileHandle); $filename = $meta['uri'];
< $size = filesize($filename);
> $size = (int) filesize($filename);
if ($size === 0) { return ''; } $blockSize = self::TEST_SAMPLE_SIZE; if ($size < $blockSize) { $blockSize = $size; } fseek($this->fileHandle, $size - $blockSize);
< return fread($this->fileHandle, $blockSize);
> return (string) fread($this->fileHandle, $blockSize);
}
< private static function startsWithTag($data)
> private static function startsWithTag(string $data): bool
{ return '<' === substr(trim($data), 0, 1); }
< private static function endsWithTag($data)
> private static function endsWithTag(string $data): bool
{ return '>' === substr(trim($data), -1, 1); }
< private static function containsTags($data)
> private static function containsTags(string $data): bool
{ return strlen($data) !== strlen(strip_tags($data)); } /** * Loads Spreadsheet from file.
< * < * @param string $pFilename < * < * @return Spreadsheet
*/
< public function load($pFilename)
> public function loadSpreadsheetFromFile(string $filename): Spreadsheet
{ // Create new Spreadsheet $spreadsheet = new Spreadsheet(); // Load into this instance
< return $this->loadIntoExisting($pFilename, $spreadsheet);
> return $this->loadIntoExisting($filename, $spreadsheet);
} /** * Set input encoding. *
< * @deprecated no use is made of this property < * < * @param string $pValue Input encoding, eg: 'ANSI'
> * @param string $inputEncoding Input encoding, eg: 'ANSI'
* * @return $this * * @codeCoverageIgnore
> * */ > * @deprecated no use is made of this property
< public function setInputEncoding($pValue)
> public function setInputEncoding($inputEncoding)
{
< $this->inputEncoding = $pValue;
> $this->inputEncoding = $inputEncoding;
return $this; } /** * Get input encoding. *
< * @deprecated no use is made of this property < *
* @return string * * @codeCoverageIgnore
> * */ > * @deprecated no use is made of this property
public function getInputEncoding() { return $this->inputEncoding; } // Data Array used for testing only, should write to Spreadsheet object on completion of tests
> protected $dataArray = []; > /** @var array */
> /** @var int */
protected $tableLevel = 0;
> /** @var array */
protected $nestedColumn = ['A'];
< protected function setTableStartColumn($column)
> protected function setTableStartColumn(string $column): string
{ if ($this->tableLevel == 0) { $column = 'A'; } ++$this->tableLevel; $this->nestedColumn[$this->tableLevel] = $column; return $this->nestedColumn[$this->tableLevel]; }
< protected function getTableStartColumn()
> protected function getTableStartColumn(): string
{ return $this->nestedColumn[$this->tableLevel]; }
< protected function releaseTableStartColumn()
> protected function releaseTableStartColumn(): string
{ --$this->tableLevel; return array_pop($this->nestedColumn); }
> /** protected function flushCell(Worksheet $sheet, $column, $row, &$cellContent): void > * Flush cell. { > * if (is_string($cellContent)) { > * @param string $column // Simple String content > * @param int|string $row if (trim($cellContent) > '') { > * @param mixed $cellContent // Only actually write it if there's content in the string > */
// Write to worksheet to be done here... // ... we return the cell so we can mess about with styles more easily $sheet->setCellValue($column . $row, $cellContent); $this->dataArray[$row][$column] = $cellContent; } } else { // We have a Rich Text run // TODO $this->dataArray[$row][$column] = 'RICH TEXT: ' . $cellContent; } $cellContent = (string) ''; } private function processDomElementBody(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child): void { $attributeArray = [];
< foreach ($child->attributes as $attribute) {
> foreach (($child->attributes ?? []) as $attribute) {
$attributeArray[$attribute->name] = $attribute->value; } if ($child->nodeName === 'body') { $row = 1; $column = 'A'; $cellContent = ''; $this->tableLevel = 0; $this->processDomElement($child, $sheet, $row, $column, $cellContent); } else { $this->processDomElementTitle($sheet, $row, $column, $cellContent, $child, $attributeArray); } } private function processDomElementTitle(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'title') { $this->processDomElement($child, $sheet, $row, $column, $cellContent);
< $sheet->setTitle($cellContent, true, false);
> $sheet->setTitle($cellContent, true, true);
$cellContent = ''; } else { $this->processDomElementSpanEtc($sheet, $row, $column, $cellContent, $child, $attributeArray); } }
< private static $spanEtc = ['span', 'div', 'font', 'i', 'em', 'strong', 'b'];
> private const SPAN_ETC = ['span', 'div', 'font', 'i', 'em', 'strong', 'b'];
private function processDomElementSpanEtc(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void {
< if (in_array($child->nodeName, self::$spanEtc)) {
> if (in_array((string) $child->nodeName, self::SPAN_ETC, true)) {
if (isset($attributeArray['class']) && $attributeArray['class'] === 'comment') { $sheet->getComment($column . $row) ->getText() ->createTextRun($child->textContent);
< }
> } else {
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
> }
if (isset($this->formats[$child->nodeName])) { $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); } } else { $this->processDomElementHr($sheet, $row, $column, $cellContent, $child, $attributeArray); } } private function processDomElementHr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'hr') { $this->flushCell($sheet, $column, $row, $cellContent); ++$row; if (isset($this->formats[$child->nodeName])) { $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); } ++$row; } // fall through to br $this->processDomElementBr($sheet, $row, $column, $cellContent, $child, $attributeArray); } private function processDomElementBr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'br' || $child->nodeName === 'hr') { if ($this->tableLevel > 0) { // If we're inside a table, replace with a \n and set the cell to wrap $cellContent .= "\n"; $sheet->getStyle($column . $row)->getAlignment()->setWrapText(true); } else { // Otherwise flush our existing content and move the row cursor on $this->flushCell($sheet, $column, $row, $cellContent); ++$row; } } else { $this->processDomElementA($sheet, $row, $column, $cellContent, $child, $attributeArray); } } private function processDomElementA(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'a') { foreach ($attributeArray as $attributeName => $attributeValue) { switch ($attributeName) { case 'href': $sheet->getCell($column . $row)->getHyperlink()->setUrl($attributeValue); if (isset($this->formats[$child->nodeName])) { $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); } break; case 'class': if ($attributeValue === 'comment-indicator') { break; // Ignore - it's just a red square. } } } // no idea why this should be needed //$cellContent .= ' '; $this->processDomElement($child, $sheet, $row, $column, $cellContent); } else { $this->processDomElementH1Etc($sheet, $row, $column, $cellContent, $child, $attributeArray); } }
< private static $h1Etc = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'p'];
> private const H1_ETC = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'p'];
private function processDomElementH1Etc(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void {
< if (in_array($child->nodeName, self::$h1Etc)) {
> if (in_array((string) $child->nodeName, self::H1_ETC, true)) {
if ($this->tableLevel > 0) { // If we're inside a table, replace with a \n $cellContent .= $cellContent ? "\n" : ''; $sheet->getStyle($column . $row)->getAlignment()->setWrapText(true); $this->processDomElement($child, $sheet, $row, $column, $cellContent); } else { if ($cellContent > '') { $this->flushCell($sheet, $column, $row, $cellContent); ++$row; } $this->processDomElement($child, $sheet, $row, $column, $cellContent); $this->flushCell($sheet, $column, $row, $cellContent); if (isset($this->formats[$child->nodeName])) { $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); } ++$row; $column = 'A'; } } else { $this->processDomElementLi($sheet, $row, $column, $cellContent, $child, $attributeArray); } } private function processDomElementLi(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'li') { if ($this->tableLevel > 0) { // If we're inside a table, replace with a \n $cellContent .= $cellContent ? "\n" : ''; $this->processDomElement($child, $sheet, $row, $column, $cellContent); } else { if ($cellContent > '') { $this->flushCell($sheet, $column, $row, $cellContent); } ++$row; $this->processDomElement($child, $sheet, $row, $column, $cellContent); $this->flushCell($sheet, $column, $row, $cellContent); $column = 'A'; } } else { $this->processDomElementImg($sheet, $row, $column, $cellContent, $child, $attributeArray); } } private function processDomElementImg(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'img') { $this->insertImage($sheet, $column, $row, $attributeArray); } else { $this->processDomElementTable($sheet, $row, $column, $cellContent, $child, $attributeArray); } } private function processDomElementTable(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'table') { $this->flushCell($sheet, $column, $row, $cellContent); $column = $this->setTableStartColumn($column);
< if ($this->tableLevel > 1) {
> if ($this->tableLevel > 1 && $row > 1) {
--$row; } $this->processDomElement($child, $sheet, $row, $column, $cellContent); $column = $this->releaseTableStartColumn(); if ($this->tableLevel > 1) { ++$column; } else { ++$row; } } else { $this->processDomElementTr($sheet, $row, $column, $cellContent, $child, $attributeArray); } } private function processDomElementTr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'tr') { $column = $this->getTableStartColumn(); $cellContent = ''; $this->processDomElement($child, $sheet, $row, $column, $cellContent); if (isset($attributeArray['height'])) { $sheet->getRowDimension($row)->setRowHeight($attributeArray['height']); } ++$row; } else { $this->processDomElementThTdOther($sheet, $row, $column, $cellContent, $child, $attributeArray); } } private function processDomElementThTdOther(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName !== 'td' && $child->nodeName !== 'th') { $this->processDomElement($child, $sheet, $row, $column, $cellContent); } else { $this->processDomElementThTd($sheet, $row, $column, $cellContent, $child, $attributeArray); } } private function processDomElementBgcolor(Worksheet $sheet, int $row, string $column, array $attributeArray): void { if (isset($attributeArray['bgcolor'])) { $sheet->getStyle("$column$row")->applyFromArray( [ 'fill' => [ 'fillType' => Fill::FILL_SOLID, 'color' => ['rgb' => $this->getStyleColor($attributeArray['bgcolor'])], ], ] ); } } private function processDomElementWidth(Worksheet $sheet, string $column, array $attributeArray): void { if (isset($attributeArray['width'])) {
< $sheet->getColumnDimension($column)->setWidth($attributeArray['width']);
> $sheet->getColumnDimension($column)->setWidth((new CssDimension($attributeArray['width']))->width());
} } private function processDomElementHeight(Worksheet $sheet, int $row, array $attributeArray): void { if (isset($attributeArray['height'])) {
< $sheet->getRowDimension($row)->setRowHeight($attributeArray['height']);
> $sheet->getRowDimension($row)->setRowHeight((new CssDimension($attributeArray['height']))->height());
} } private function processDomElementAlign(Worksheet $sheet, int $row, string $column, array $attributeArray): void { if (isset($attributeArray['align'])) { $sheet->getStyle($column . $row)->getAlignment()->setHorizontal($attributeArray['align']); } } private function processDomElementVAlign(Worksheet $sheet, int $row, string $column, array $attributeArray): void { if (isset($attributeArray['valign'])) { $sheet->getStyle($column . $row)->getAlignment()->setVertical($attributeArray['valign']); } } private function processDomElementDataFormat(Worksheet $sheet, int $row, string $column, array $attributeArray): void { if (isset($attributeArray['data-format'])) { $sheet->getStyle($column . $row)->getNumberFormat()->setFormatCode($attributeArray['data-format']); } } private function processDomElementThTd(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { while (isset($this->rowspan[$column . $row])) { ++$column; } $this->processDomElement($child, $sheet, $row, $column, $cellContent); // apply inline style $this->applyInlineStyle($sheet, $row, $column, $attributeArray); $this->flushCell($sheet, $column, $row, $cellContent); $this->processDomElementBgcolor($sheet, $row, $column, $attributeArray); $this->processDomElementWidth($sheet, $column, $attributeArray); $this->processDomElementHeight($sheet, $row, $attributeArray); $this->processDomElementAlign($sheet, $row, $column, $attributeArray); $this->processDomElementVAlign($sheet, $row, $column, $attributeArray); $this->processDomElementDataFormat($sheet, $row, $column, $attributeArray); if (isset($attributeArray['rowspan'], $attributeArray['colspan'])) { //create merging rowspan and colspan $columnTo = $column; for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { ++$columnTo; } $range = $column . $row . ':' . $columnTo . ($row + (int) $attributeArray['rowspan'] - 1); foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) { $this->rowspan[$value] = true; } $sheet->mergeCells($range); $column = $columnTo; } elseif (isset($attributeArray['rowspan'])) { //create merging rowspan $range = $column . $row . ':' . $column . ($row + (int) $attributeArray['rowspan'] - 1); foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) { $this->rowspan[$value] = true; } $sheet->mergeCells($range); } elseif (isset($attributeArray['colspan'])) { //create merging colspan $columnTo = $column; for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { ++$columnTo; } $sheet->mergeCells($column . $row . ':' . $columnTo . $row); $column = $columnTo; } ++$column; } protected function processDomElement(DOMNode $element, Worksheet $sheet, int &$row, string &$column, string &$cellContent): void { foreach ($element->childNodes as $child) { if ($child instanceof DOMText) {
< $domText = preg_replace('/\s+/u', ' ', trim($child->nodeValue));
> $domText = (string) preg_replace('/\s+/u', ' ', trim($child->nodeValue ?? ''));
if (is_string($cellContent)) { // simply append the text if the cell content is a plain text string $cellContent .= $domText; } // but if we have a rich text run instead, we need to append it correctly // TODO } elseif ($child instanceof DOMElement) { $this->processDomElementBody($sheet, $row, $column, $cellContent, $child); } } } /** * Loads PhpSpreadsheet from file into PhpSpreadsheet instance. *
< * @param string $pFilename
> * @param string $filename
* * @return Spreadsheet */
< public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
> public function loadIntoExisting($filename, Spreadsheet $spreadsheet)
{ // Validate
< if (!$this->canRead($pFilename)) { < throw new Exception($pFilename . ' is an Invalid HTML file.');
> if (!$this->canRead($filename)) { > throw new Exception($filename . ' is an Invalid HTML file.');
} // Create a new DOM object $dom = new DOMDocument(); // Reload the HTML file into the DOM object try {
< $loaded = $dom->loadHTML(mb_convert_encoding($this->securityScanner->scanFile($pFilename), 'HTML-ENTITIES', 'UTF-8'));
> $convert = $this->getSecurityScannerOrThrow()->scanFile($filename); > $lowend = "\u{80}"; > $highend = "\u{10ffff}"; > $regexp = "/[$lowend-$highend]/u"; > /** @var callable */ > $callback = [self::class, 'replaceNonAscii']; > $convert = preg_replace_callback($regexp, $callback, $convert); > $loaded = ($convert === null) ? false : $dom->loadHTML($convert);
} catch (Throwable $e) { $loaded = false; } if ($loaded === false) {
< throw new Exception('Failed to load ' . $pFilename . ' as a DOM Document');
> throw new Exception('Failed to load ' . $filename . ' as a DOM Document', 0, $e ?? null);
} return $this->loadDocument($dom, $spreadsheet); }
> private static function replaceNonAscii(array $matches): string /** > { * Spreadsheet from content. > return '&#' . mb_ord($matches[0], 'UTF-8') . ';'; * > } * @param string $content >
*/ public function loadFromString($content, ?Spreadsheet $spreadsheet = null): Spreadsheet { // Create a new DOM object $dom = new DOMDocument(); // Reload the HTML file into the DOM object try {
< $loaded = $dom->loadHTML(mb_convert_encoding($this->securityScanner->scan($content), 'HTML-ENTITIES', 'UTF-8'));
> $convert = $this->getSecurityScannerOrThrow()->scan($content); > $lowend = "\u{80}"; > $highend = "\u{10ffff}"; > $regexp = "/[$lowend-$highend]/u"; > /** @var callable */ > $callback = [self::class, 'replaceNonAscii']; > $convert = preg_replace_callback($regexp, $callback, $convert); > $loaded = ($convert === null) ? false : $dom->loadHTML($convert);
} catch (Throwable $e) { $loaded = false; } if ($loaded === false) {
< throw new Exception('Failed to load content as a DOM Document');
> throw new Exception('Failed to load content as a DOM Document', 0, $e ?? null);
} return $this->loadDocument($dom, $spreadsheet ?? new Spreadsheet()); } /** * Loads PhpSpreadsheet from DOMDocument into PhpSpreadsheet instance. */ private function loadDocument(DOMDocument $document, Spreadsheet $spreadsheet): Spreadsheet { while ($spreadsheet->getSheetCount() <= $this->sheetIndex) { $spreadsheet->createSheet(); } $spreadsheet->setActiveSheetIndex($this->sheetIndex); // Discard white space $document->preserveWhiteSpace = false; $row = 0; $column = 'A'; $content = ''; $this->rowspan = []; $this->processDomElement($document, $spreadsheet->getActiveSheet(), $row, $column, $content); // Return return $spreadsheet; } /** * Get sheet index. * * @return int */ public function getSheetIndex() { return $this->sheetIndex; } /** * Set sheet index. *
< * @param int $pValue Sheet index
> * @param int $sheetIndex Sheet index
* * @return $this */
< public function setSheetIndex($pValue)
> public function setSheetIndex($sheetIndex)
{
< $this->sheetIndex = $pValue;
> $this->sheetIndex = $sheetIndex;
return $this; } /** * Apply inline css inline style. * * NOTES : * Currently only intended for td & th element, * and only takes 'background-color' and 'color'; property with HEX color * * TODO : * - Implement to other propertie, such as border *
< * @param Worksheet $sheet
* @param int $row * @param string $column * @param array $attributeArray */
< private function applyInlineStyle(&$sheet, $row, $column, $attributeArray): void
> private function applyInlineStyle(Worksheet &$sheet, $row, $column, $attributeArray): void
{ if (!isset($attributeArray['style'])) { return; } if (isset($attributeArray['rowspan'], $attributeArray['colspan'])) { $columnTo = $column; for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { ++$columnTo; } $range = $column . $row . ':' . $columnTo . ($row + (int) $attributeArray['rowspan'] - 1); $cellStyle = $sheet->getStyle($range); } elseif (isset($attributeArray['rowspan'])) { $range = $column . $row . ':' . $column . ($row + (int) $attributeArray['rowspan'] - 1); $cellStyle = $sheet->getStyle($range); } elseif (isset($attributeArray['colspan'])) { $columnTo = $column; for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { ++$columnTo; } $range = $column . $row . ':' . $columnTo . $row; $cellStyle = $sheet->getStyle($range); } else { $cellStyle = $sheet->getStyle($column . $row); } // add color styles (background & text) from dom element,currently support : td & th, using ONLY inline css style with RGB color $styles = explode(';', $attributeArray['style']); foreach ($styles as $st) { $value = explode(':', $st); $styleName = isset($value[0]) ? trim($value[0]) : null; $styleValue = isset($value[1]) ? trim($value[1]) : null;
> $styleValueString = (string) $styleValue;
if (!$styleName) { continue; } switch ($styleName) { case 'background': case 'background-color':
< $styleColor = $this->getStyleColor($styleValue);
> $styleColor = $this->getStyleColor($styleValueString);
if (!$styleColor) { continue 2; } $cellStyle->applyFromArray(['fill' => ['fillType' => Fill::FILL_SOLID, 'color' => ['rgb' => $styleColor]]]); break; case 'color':
< $styleColor = $this->getStyleColor($styleValue);
> $styleColor = $this->getStyleColor($styleValueString);
if (!$styleColor) { continue 2; } $cellStyle->applyFromArray(['font' => ['color' => ['rgb' => $styleColor]]]); break; case 'border':
< $this->setBorderStyle($cellStyle, $styleValue, 'allBorders');
> $this->setBorderStyle($cellStyle, $styleValueString, 'allBorders');
break; case 'border-top':
< $this->setBorderStyle($cellStyle, $styleValue, 'top');
> $this->setBorderStyle($cellStyle, $styleValueString, 'top');
break; case 'border-bottom':
< $this->setBorderStyle($cellStyle, $styleValue, 'bottom');
> $this->setBorderStyle($cellStyle, $styleValueString, 'bottom');
break; case 'border-left':
< $this->setBorderStyle($cellStyle, $styleValue, 'left');
> $this->setBorderStyle($cellStyle, $styleValueString, 'left');
break; case 'border-right':
< $this->setBorderStyle($cellStyle, $styleValue, 'right');
> $this->setBorderStyle($cellStyle, $styleValueString, 'right');
break; case 'font-size': $cellStyle->getFont()->setSize( (float) $styleValue ); break; case 'font-weight': if ($styleValue === 'bold' || $styleValue >= 500) { $cellStyle->getFont()->setBold(true); } break; case 'font-style': if ($styleValue === 'italic') { $cellStyle->getFont()->setItalic(true); } break; case 'font-family':
< $cellStyle->getFont()->setName(str_replace('\'', '', $styleValue));
> $cellStyle->getFont()->setName(str_replace('\'', '', $styleValueString));
break; case 'text-decoration': switch ($styleValue) { case 'underline': $cellStyle->getFont()->setUnderline(Font::UNDERLINE_SINGLE); break; case 'line-through': $cellStyle->getFont()->setStrikethrough(true); break; } break; case 'text-align':
< $cellStyle->getAlignment()->setHorizontal($styleValue);
> $cellStyle->getAlignment()->setHorizontal($styleValueString);
break; case 'vertical-align':
< $cellStyle->getAlignment()->setVertical($styleValue);
> $cellStyle->getAlignment()->setVertical($styleValueString);
break; case 'width': $sheet->getColumnDimension($column)->setWidth(
< str_replace('px', '', $styleValue)
> (new CssDimension($styleValue ?? ''))->width()
); break; case 'height': $sheet->getRowDimension($row)->setRowHeight(
< str_replace('px', '', $styleValue)
> (new CssDimension($styleValue ?? ''))->height()
); break; case 'word-wrap': $cellStyle->getAlignment()->setWrapText( $styleValue === 'break-word' ); break; case 'text-indent': $cellStyle->getAlignment()->setIndent(
< (int) str_replace(['px'], '', $styleValue)
> (int) str_replace(['px'], '', $styleValueString)
); break; } } } /** * Check if has #, so we can get clean hex. *
< * @param $value
> * @param mixed $value
* * @return null|string */ public function getStyleColor($value) {
> $value = (string) $value;
if (strpos($value, '#') === 0) { return substr($value, 1); }
< return \PhpOffice\PhpSpreadsheet\Helper\Html::colourNameLookup((string) $value);
> return \PhpOffice\PhpSpreadsheet\Helper\Html::colourNameLookup($value);
} /** * @param string $column * @param int $row */ private function insertImage(Worksheet $sheet, $column, $row, array $attributes): void { if (!isset($attributes['src'])) { return; } $src = urldecode($attributes['src']); $width = isset($attributes['width']) ? (float) $attributes['width'] : null; $height = isset($attributes['height']) ? (float) $attributes['height'] : null; $name = $attributes['alt'] ?? null; $drawing = new Drawing(); $drawing->setPath($src); $drawing->setWorksheet($sheet); $drawing->setCoordinates($column . $row); $drawing->setOffsetX(0); $drawing->setOffsetY(10); $drawing->setResizeProportional(true); if ($name) { $drawing->setName($name); } if ($width) { $drawing->setWidth((int) $width); } if ($height) { $drawing->setHeight((int) $height); } $sheet->getColumnDimension($column)->setWidth( $drawing->getWidth() / 6 ); $sheet->getRowDimension($row)->setRowHeight( $drawing->getHeight() * 0.9 ); }
< private static $borderMappings = [
> private const BORDER_MAPPINGS = [
'dash-dot' => Border::BORDER_DASHDOT, 'dash-dot-dot' => Border::BORDER_DASHDOTDOT, 'dashed' => Border::BORDER_DASHED, 'dotted' => Border::BORDER_DOTTED, 'double' => Border::BORDER_DOUBLE, 'hair' => Border::BORDER_HAIR, 'medium' => Border::BORDER_MEDIUM, 'medium-dashed' => Border::BORDER_MEDIUMDASHED, 'medium-dash-dot' => Border::BORDER_MEDIUMDASHDOT, 'medium-dash-dot-dot' => Border::BORDER_MEDIUMDASHDOTDOT, 'none' => Border::BORDER_NONE, 'slant-dash-dot' => Border::BORDER_SLANTDASHDOT, 'solid' => Border::BORDER_THIN, 'thick' => Border::BORDER_THICK, ]; public static function getBorderMappings(): array {
< return self::$borderMappings;
> return self::BORDER_MAPPINGS;
} /** * Map html border style to PhpSpreadsheet border style. * * @param string $style * * @return null|string */ public function getBorderStyle($style) {
< return (array_key_exists($style, self::$borderMappings)) ? self::$borderMappings[$style] : null;
> return self::BORDER_MAPPINGS[$style] ?? null;
} /** * @param string $styleValue * @param string $type */ private function setBorderStyle(Style $cellStyle, $styleValue, $type): void { if (trim($styleValue) === Border::BORDER_NONE) { $borderStyle = Border::BORDER_NONE; $color = null; } else {
< [, $borderStyle, $color] = explode(' ', $styleValue);
> $borderArray = explode(' ', $styleValue); > $borderCount = count($borderArray); > if ($borderCount >= 3) { > $borderStyle = $borderArray[1]; > $color = $borderArray[2]; > } else { > $borderStyle = $borderArray[0]; > $color = $borderArray[1] ?? null; > }
} $cellStyle->applyFromArray([ 'borders' => [ $type => [ 'borderStyle' => $this->getBorderStyle($borderStyle), 'color' => ['rgb' => $this->getStyleColor($color)], ], ], ]); } }