<?php
namespace PhpOffice\PhpSpreadsheet\Reader;
use DOMDocument;
use DOMElement;
use DOMNode;
use DOMText;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
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
];
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)
{
// Check if file exists
try {
$this->openFile($pFilename);
} 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()
{
fseek($this->fileHandle, 0);
return fread($this->fileHandle, self::TEST_SAMPLE_SIZE);
}
private function readEnding()
{
$meta = stream_get_meta_data($this->fileHandle);
$filename = $meta['uri'];
$size = 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);
}
private static function startsWithTag($data)
{
return '<' === substr(trim($data), 0, 1);
}
private static function endsWithTag($data)
{
return '>' === substr(trim($data), -1, 1);
}
private static function containsTags($data)
{
return strlen($data) !== strlen(strip_tags($data));
}
/**
* Loads Spreadsheet from file.
*
* @param string $pFilename
*
< * @throws Exception
< *
* @return Spreadsheet
*/
public function load($pFilename)
{
// Create new Spreadsheet
$spreadsheet = new Spreadsheet();
// Load into this instance
return $this->loadIntoExisting($pFilename, $spreadsheet);
}
/**
* Set input encoding.
*
> * @deprecated no use is made of this property
* @param string $pValue Input encoding, eg: 'ANSI'
> *
*
< * @return Html
> * @return $this
> *
> * @codeCoverageIgnore
*/
public function setInputEncoding($pValue)
{
$this->inputEncoding = $pValue;
return $this;
}
/**
* Get input encoding.
*
> * @deprecated no use is made of this property
* @return string
> *
*/
> *
public function getInputEncoding()
> * @codeCoverageIgnore
{
return $this->inputEncoding;
}
// Data Array used for testing only, should write to Spreadsheet object on completion of tests
protected $dataArray = [];
protected $tableLevel = 0;
protected $nestedColumn = ['A'];
protected function setTableStartColumn($column)
{
if ($this->tableLevel == 0) {
$column = 'A';
}
++$this->tableLevel;
$this->nestedColumn[$this->tableLevel] = $column;
return $this->nestedColumn[$this->tableLevel];
}
protected function getTableStartColumn()
{
return $this->nestedColumn[$this->tableLevel];
}
protected function releaseTableStartColumn()
{
--$this->tableLevel;
return array_pop($this->nestedColumn);
}
< protected function flushCell(Worksheet $sheet, $column, $row, &$cellContent)
> protected function flushCell(Worksheet $sheet, $column, $row, &$cellContent): void
{
if (is_string($cellContent)) {
// Simple String content
if (trim($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) '';
}
< /**
< * @param DOMNode $element
< * @param Worksheet $sheet
< * @param int $row
< * @param string $column
< * @param string $cellContent
< */
< protected function processDomElement(DOMNode $element, Worksheet $sheet, &$row, &$column, &$cellContent)
> private function processDomElementBody(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child): void
{
< foreach ($element->childNodes as $child) {
< if ($child instanceof DOMText) {
< $domText = 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) {
$attributeArray = [];
foreach ($child->attributes as $attribute) {
$attributeArray[$attribute->name] = $attribute->value;
}
< switch ($child->nodeName) {
< case 'meta':
< foreach ($attributeArray as $attributeName => $attributeValue) {
< // Extract character set, so we can convert to UTF-8 if required
< if ($attributeName === 'charset') {
< $this->setInputEncoding($attributeValue);
> 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);
}
}
< $this->processDomElement($child, $sheet, $row, $column, $cellContent);
< break;
< case 'title':
> 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);
$cellContent = '';
> } else {
> $this->processDomElementSpanEtc($sheet, $row, $column, $cellContent, $child, $attributeArray);
break;
> }
case 'span':
> }
< break;
< case 'span':
< case 'div':
< case 'font':
< case 'i':
< case 'em':
< case 'strong':
< case 'b':
> private static $spanEtc = ['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)) {
->getText()
->createTextRun($child->textContent);
<
< break;
< }
<
< if ($cellContent > '') {
< $cellContent .= ' ';
}
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
< if ($cellContent > '') {
< $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);
break;
> }
case 'hr':
> }
< break;
< case 'hr':
> private function processDomElementHr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
> {
> if ($child->nodeName === 'hr') {
if (isset($this->formats[$child->nodeName])) {
$sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
< } else {
< $cellContent = '----------';
< $this->flushCell($sheet, $column, $row, $cellContent);
}
++$row;
< // Add a break after a horizontal rule, simply by allowing the code to dropthru
< // no break
< case 'br':
> }
> // 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);
break;
> }
case 'a':
> }
< break;
< case 'a':
> private function processDomElementA(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
> {
> if ($child->nodeName === 'a') {
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.
}
}
}
< $cellContent .= ' ';
> // no idea why this should be needed
> //$cellContent .= ' ';
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
> } else {
> $this->processDomElementH1Etc($sheet, $row, $column, $cellContent, $child, $attributeArray);
break;
> }
case 'h1':
> }
< break;
< case 'h1':
< case 'h2':
< case 'h3':
< case 'h4':
< case 'h5':
< case 'h6':
< case 'ol':
< case 'ul':
< case 'p':
> private static $h1Etc = ['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)) {
< $cellContent .= "\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);
break;
> }
case 'li':
> }
< break;
< case 'li':
> private function processDomElementLi(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
> {
> if ($child->nodeName === 'li') {
< $cellContent .= "\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);
break;
> }
case 'img':
> }
< break;
< case 'img':
> private function processDomElementImg(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
> {
> if ($child->nodeName === 'img') {
break;
> } else {
case 'table':
> $this->processDomElementTable($sheet, $row, $column, $cellContent, $child, $attributeArray);
$this->flushCell($sheet, $column, $row, $cellContent);
> }
$column = $this->setTableStartColumn($column);
> }
< break;
< case 'table':
> private function processDomElementTable(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
> {
> if ($child->nodeName === 'table') {
}
$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);
break;
> }
case 'thead':
> }
< break;
< case 'thead':
< case 'tbody':
< $this->processDomElement($child, $sheet, $row, $column, $cellContent);
<
< break;
< case 'tr':
> private function processDomElementTr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
> {
> if ($child->nodeName === 'tr') {
$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);
break;
> }
case 'th':
> }
< break;
< case 'th':
< case 'td':
> private function processDomElementThTdOther(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
> {
> if ($child->nodeName !== 'td' && $child->nodeName !== 'th') {
while (isset($this->rowspan[$column . $row])) {
> } else {
++$column;
> $this->processDomElementThTd($sheet, $row, $column, $cellContent, $child, $attributeArray);
}
> }
> }
// apply inline style
> private function processDomElementBgcolor(Worksheet $sheet, int $row, string $column, array $attributeArray): void
$this->applyInlineStyle($sheet, $row, $column, $attributeArray);
> {
> if (isset($attributeArray['bgcolor'])) {
$this->flushCell($sheet, $column, $row, $cellContent);
> $sheet->getStyle("$column$row")->applyFromArray(
> [
if (isset($attributeArray['rowspan'], $attributeArray['colspan'])) {
> 'fill' => [
//create merging rowspan and colspan
> 'fillType' => Fill::FILL_SOLID,
$columnTo = $column;
> 'color' => ['rgb' => $this->getStyleColor($attributeArray['bgcolor'])],
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;
>
}
> private function processDomElementWidth(Worksheet $sheet, string $column, array $attributeArray): void
$sheet->mergeCells($range);
> {
$column = $columnTo;
> if (isset($attributeArray['width'])) {
} elseif (isset($attributeArray['rowspan'])) {
> $sheet->getColumnDimension($column)->setWidth($attributeArray['width']);
//create merging rowspan
> }
$range = $column . $row . ':' . $column . ($row + (int) $attributeArray['rowspan'] - 1);
> }
foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) {
>
$this->rowspan[$value] = true;
> private function processDomElementHeight(Worksheet $sheet, int $row, array $attributeArray): void
}
> {
$sheet->mergeCells($range);
> if (isset($attributeArray['height'])) {
} elseif (isset($attributeArray['colspan'])) {
> $sheet->getRowDimension($row)->setRowHeight($attributeArray['height']);
//create merging colspan
> }
$columnTo = $column;
> }
for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
>
++$columnTo;
> private function processDomElementAlign(Worksheet $sheet, int $row, string $column, array $attributeArray): void
}
> {
$sheet->mergeCells($column . $row . ':' . $columnTo . $row);
> if (isset($attributeArray['align'])) {
$column = $columnTo;
> $sheet->getStyle($column . $row)->getAlignment()->setHorizontal($attributeArray['align']);
} elseif (isset($attributeArray['bgcolor'])) {
> }
$sheet->getStyle($column . $row)->applyFromArray(
> }
[
>
'fill' => [
> private function processDomElementVAlign(Worksheet $sheet, int $row, string $column, array $attributeArray): void
'fillType' => Fill::FILL_SOLID,
> {
'color' => ['rgb' => $attributeArray['bgcolor']],
> 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['width'])) {
> {
$sheet->getColumnDimension($column)->setWidth($attributeArray['width']);
> if (isset($attributeArray['data-format'])) {
}
> $sheet->getStyle($column . $row)->getNumberFormat()->setFormatCode($attributeArray['data-format']);
> }
if (isset($attributeArray['height'])) {
> }
$sheet->getRowDimension($row)->setRowHeight($attributeArray['height']);
>
}
> private function processDomElementThTd(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
> {
if (isset($attributeArray['align'])) {
> $this->processDomElement($child, $sheet, $row, $column, $cellContent);
$sheet->getStyle($column . $row)->getAlignment()->setHorizontal($attributeArray['align']);
> $this->processDomElementBgcolor($sheet, $row, $column, $attributeArray);
}
> $this->processDomElementWidth($sheet, $column, $attributeArray);
> $this->processDomElementHeight($sheet, $row, $attributeArray);
if (isset($attributeArray['valign'])) {
> $this->processDomElementAlign($sheet, $row, $column, $attributeArray);
$sheet->getStyle($column . $row)->getAlignment()->setVertical($attributeArray['valign']);
> $this->processDomElementVAlign($sheet, $row, $column, $attributeArray);
}
> $this->processDomElementDataFormat($sheet, $row, $column, $attributeArray);
>
< } elseif (isset($attributeArray['bgcolor'])) {
< $sheet->getStyle($column . $row)->applyFromArray(
< [
< 'fill' => [
< 'fillType' => Fill::FILL_SOLID,
< 'color' => ['rgb' => $attributeArray['bgcolor']],
< ],
< ]
< );
< }
<
< if (isset($attributeArray['width'])) {
< $sheet->getColumnDimension($column)->setWidth($attributeArray['width']);
< }
<
< if (isset($attributeArray['height'])) {
< $sheet->getRowDimension($row)->setRowHeight($attributeArray['height']);
< }
<
< if (isset($attributeArray['align'])) {
< $sheet->getStyle($column . $row)->getAlignment()->setHorizontal($attributeArray['align']);
< }
<
< if (isset($attributeArray['valign'])) {
< $sheet->getStyle($column . $row)->getAlignment()->setVertical($attributeArray['valign']);
< }
<
< if (isset($attributeArray['data-format'])) {
< $sheet->getStyle($column . $row)->getNumberFormat()->setFormatCode($attributeArray['data-format']);
*
> }
< break;
< case 'body':
< $row = 1;
< $column = 'A';
< $cellContent = '';
< $this->tableLevel = 0;
< $this->processDomElement($child, $sheet, $row, $column, $cellContent);
<
< break;
< default:
< $this->processDomElement($child, $sheet, $row, $column, $cellContent);
> 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));
> if (is_string($cellContent)) {
> // simply append the text if the cell content is a plain text string
> $cellContent .= $domText;
// Reload the HTML file into the DOM object
> // but if we have a rich text run instead, we need to append it correctly
$loaded = $dom->loadHTML(mb_convert_encoding($this->securityScanner->scanFile($pFilename), 'HTML-ENTITIES', 'UTF-8'));
> // TODO
if ($loaded === false) {
> } elseif ($child instanceof DOMElement) {
throw new Exception('Failed to load ' . $pFilename . ' as a DOM Document');
> $this->processDomElementBody($sheet, $row, $column, $cellContent, $child);
< * @param Spreadsheet $spreadsheet
< *
< * @throws Exception
}
> try {
> } catch (Throwable $e) {
/**
> $loaded = false;
* Spreadsheet from content.
> }
*
* @param string $content
< * @param null|Spreadsheet $spreadsheet
< *
< * @return Spreadsheet
*/
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'));
> } catch (Throwable $e) {
if ($loaded === false) {
> $loaded = false;
throw new Exception('Failed to load content as a DOM Document');
> }
}
return $this->loadDocument($dom, $spreadsheet ?? new Spreadsheet());
}
/**
* Loads PhpSpreadsheet from DOMDocument into PhpSpreadsheet instance.
< *
< * @param DOMDocument $document
< * @param Spreadsheet $spreadsheet
< *
< * @throws \PhpOffice\PhpSpreadsheet\Exception
< *
< * @return Spreadsheet
*/
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
*
< * @return HTML
> * @return $this
*/
public function setSheetIndex($pValue)
{
$this->sheetIndex = $pValue;
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)
> private function applyInlineStyle(&$sheet, $row, $column, $attributeArray): void
{
if (!isset($attributeArray['style'])) {
return;
}
> if (isset($attributeArray['rowspan'], $attributeArray['colspan'])) {
$cellStyle = $sheet->getStyle($column . $row);
> $columnTo = $column;
> for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
// add color styles (background & text) from dom element,currently support : td & th, using ONLY inline css style with RGB color
> ++$columnTo;
$styles = explode(';', $attributeArray['style']);
> }
foreach ($styles as $st) {
> $range = $column . $row . ':' . $columnTo . ($row + (int) $attributeArray['rowspan'] - 1);
$value = explode(':', $st);
> $cellStyle = $sheet->getStyle($range);
$styleName = isset($value[0]) ? trim($value[0]) : null;
> } elseif (isset($attributeArray['rowspan'])) {
$styleValue = isset($value[1]) ? trim($value[1]) : null;
> $range = $column . $row . ':' . $column . ($row + (int) $attributeArray['rowspan'] - 1);
> $cellStyle = $sheet->getStyle($range);
if (!$styleName) {
> } elseif (isset($attributeArray['colspan'])) {
continue;
> $columnTo = $column;
}
> for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
> ++$columnTo;
switch ($styleName) {
> }
case 'background':
> $range = $column . $row . ':' . $columnTo . $row;
case 'background-color':
> $cellStyle = $sheet->getStyle($range);
$styleColor = $this->getStyleColor($styleValue);
> } else {
> }
if (!$styleColor) {
continue 2;
}
$cellStyle->applyFromArray(['fill' => ['fillType' => Fill::FILL_SOLID, 'color' => ['rgb' => $styleColor]]]);
break;
case 'color':
$styleColor = $this->getStyleColor($styleValue);
if (!$styleColor) {
continue 2;
}
$cellStyle->applyFromArray(['font' => ['color' => ['rgb' => $styleColor]]]);
break;
case 'border':
$this->setBorderStyle($cellStyle, $styleValue, 'allBorders');
break;
case 'border-top':
$this->setBorderStyle($cellStyle, $styleValue, 'top');
break;
case 'border-bottom':
$this->setBorderStyle($cellStyle, $styleValue, 'bottom');
break;
case 'border-left':
$this->setBorderStyle($cellStyle, $styleValue, 'left');
break;
case 'border-right':
$this->setBorderStyle($cellStyle, $styleValue, '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));
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);
break;
case 'vertical-align':
$cellStyle->getAlignment()->setVertical($styleValue);
break;
case 'width':
$sheet->getColumnDimension($column)->setWidth(
str_replace('px', '', $styleValue)
);
break;
case 'height':
$sheet->getRowDimension($row)->setRowHeight(
str_replace('px', '', $styleValue)
);
break;
case 'word-wrap':
$cellStyle->getAlignment()->setWrapText(
$styleValue === 'break-word'
);
break;
case 'text-indent':
$cellStyle->getAlignment()->setIndent(
(int) str_replace(['px'], '', $styleValue)
);
break;
}
}
}
/**
* Check if has #, so we can get clean hex.
*
* @param $value
*
* @return null|string
*/
public function getStyleColor($value)
{
if (strpos($value, '#') === 0) {
return substr($value, 1);
}
< return null;
> return \PhpOffice\PhpSpreadsheet\Helper\Html::colourNameLookup((string) $value);
}
/**
< * @param Worksheet $sheet
* @param string $column
* @param int $row
< * @param array $attributes
< *
< * @throws \PhpOffice\PhpSpreadsheet\Exception
*/
< private function insertImage(Worksheet $sheet, $column, $row, array $attributes)
> 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 = isset($attributes['alt']) ? (float) $attributes['alt'] : 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 = [
/**
> 'dash-dot' => Border::BORDER_DASHDOT,
* Map html border style to PhpSpreadsheet border style.
> 'dash-dot-dot' => Border::BORDER_DASHDOTDOT,
*
> 'dashed' => Border::BORDER_DASHED,
* @param string $style
> 'dotted' => Border::BORDER_DOTTED,
*
> 'double' => Border::BORDER_DOUBLE,
* @return null|string
> 'hair' => Border::BORDER_HAIR,
*/
> 'medium' => Border::BORDER_MEDIUM,
public function getBorderStyle($style)
> 'medium-dashed' => Border::BORDER_MEDIUMDASHED,
{
> 'medium-dash-dot' => Border::BORDER_MEDIUMDASHDOT,
switch ($style) {
> 'medium-dash-dot-dot' => Border::BORDER_MEDIUMDASHDOTDOT,
case 'solid':
> 'none' => Border::BORDER_NONE,
return Border::BORDER_THIN;
> 'slant-dash-dot' => Border::BORDER_SLANTDASHDOT,
case 'dashed':
> 'solid' => Border::BORDER_THIN,
return Border::BORDER_DASHED;
> 'thick' => Border::BORDER_THICK,
case 'dotted':
> ];
return Border::BORDER_DOTTED;
>
case 'medium':
> public static function getBorderMappings(): array
return Border::BORDER_MEDIUM;
> {
case 'thick':
> return self::$borderMappings;
return Border::BORDER_THICK;
> }
case 'none':
>
< switch ($style) {
< case 'solid':
< return Border::BORDER_THIN;
< case 'dashed':
< return Border::BORDER_DASHED;
< case 'dotted':
< return Border::BORDER_DOTTED;
< case 'medium':
< return Border::BORDER_MEDIUM;
< case 'thick':
< return Border::BORDER_THICK;
< case 'none':
< return Border::BORDER_NONE;
< case 'dash-dot':
< return Border::BORDER_DASHDOT;
< case 'dash-dot-dot':
< return Border::BORDER_DASHDOTDOT;
< case 'double':
< return Border::BORDER_DOUBLE;
< case 'hair':
< return Border::BORDER_HAIR;
< case 'medium-dash-dot':
< return Border::BORDER_MEDIUMDASHDOT;
< case 'medium-dash-dot-dot':
< return Border::BORDER_MEDIUMDASHDOTDOT;
< case 'medium-dashed':
< return Border::BORDER_MEDIUMDASHED;
< case 'slant-dash-dot':
< return Border::BORDER_SLANTDASHDOT;
< }
<
< return null;
> return (array_key_exists($style, self::$borderMappings)) ? self::$borderMappings[$style] : null;
< * @param Style $cellStyle
< private function setBorderStyle(Style $cellStyle, $styleValue, $type)
> private function setBorderStyle(Style $cellStyle, $styleValue, $type): void
'borderStyle' => $this->getBorderStyle($borderStyle),
> if (trim($styleValue) === Border::BORDER_NONE) {
'color' => ['rgb' => $this->getStyleColor($color)],
> $borderStyle = Border::BORDER_NONE;
],
> $color = null;
],
> } else {
]);
> }
}
}