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.
<?php

namespace PhpOffice\PhpSpreadsheet\Reader;

use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
< use PhpOffice\PhpSpreadsheet\NamedRange;
> use PhpOffice\PhpSpreadsheet\DefinedName; > use PhpOffice\PhpSpreadsheet\Reader\Gnumeric\PageSetup; > use PhpOffice\PhpSpreadsheet\Reader\Gnumeric\Properties; > use PhpOffice\PhpSpreadsheet\Reader\Gnumeric\Styles;
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; use PhpOffice\PhpSpreadsheet\ReferenceHelper; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Settings;
< use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Spreadsheet;
< use PhpOffice\PhpSpreadsheet\Style\Alignment; < use PhpOffice\PhpSpreadsheet\Style\Border; < use PhpOffice\PhpSpreadsheet\Style\Borders; < use PhpOffice\PhpSpreadsheet\Style\Fill; < use PhpOffice\PhpSpreadsheet\Style\Font;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
> use SimpleXMLElement;
use XMLReader; class Gnumeric extends BaseReader {
> const NAMESPACE_GNM = 'http://www.gnumeric.org/v10.dtd'; // gmr in old sheets /** > * Shared Expressions. > const NAMESPACE_XSI = 'http://www.w3.org/2001/XMLSchema-instance'; * > * @var array > const NAMESPACE_OFFICE = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'; */ > private $expressions = []; > const NAMESPACE_XLINK = 'http://www.w3.org/1999/xlink'; > private $referenceHelper; > const NAMESPACE_DC = 'http://purl.org/dc/elements/1.1/'; > /** > const NAMESPACE_META = 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'; * Create a new Gnumeric. > */ > const NAMESPACE_OOO = 'http://openoffice.org/2004/office'; public function __construct() >
{
> /** parent::__construct(); > * Spreadsheet shared across all functions. $this->referenceHelper = ReferenceHelper::getInstance(); > * $this->securityScanner = XmlScanner::getInstance($this); > * @var Spreadsheet } > */ > private $spreadsheet; /** > * Can the current IReader read the file? > /** @var ReferenceHelper */
*
> /** @var array */ * @param string $pFilename > public static $mappings = [ * > 'dataType' => [ * @throws Exception > '10' => DataType::TYPE_NULL, * > '20' => DataType::TYPE_BOOL, * @return bool > '30' => DataType::TYPE_NUMERIC, // Integer doesn't exist in Excel */ > '40' => DataType::TYPE_NUMERIC, // Float public function canRead($pFilename) > '50' => DataType::TYPE_ERROR, { > '60' => DataType::TYPE_STRING, File::assertFile($pFilename); > //'70': // Cell Range > //'80': // Array // Check if gzlib functions are available > ], if (!function_exists('gzread')) { > ]; throw new Exception('gzlib library is not enabled'); >
< * < * @param string $pFilename < * < * @throws Exception < * < * @return bool
< public function canRead($pFilename)
> public function canRead(string $filename): bool
< File::assertFile($pFilename); <
< if (!function_exists('gzread')) { < throw new Exception('gzlib library is not enabled'); < } <
> if (File::testFileNoThrow($filename) && function_exists('gzread')) {
< $fh = fopen($pFilename, 'r');
> $fh = fopen($filename, 'rb'); > if ($fh !== false) {
*
> } * @return array > }
< return $data == chr(0x1F) . chr(0x8B);
> return isset($data) && $data === chr(0x1F) . chr(0x8B); > } > > private static function matchXml(XMLReader $xml, string $expectedLocalName): bool > { > return $xml->namespaceURI === self::NAMESPACE_GNM > && $xml->localName === $expectedLocalName > && $xml->nodeType === XMLReader::ELEMENT;
< * @param string $pFilename
> * @param string $filename
< public function listWorksheetNames($pFilename)
> public function listWorksheetNames($filename)
< File::assertFile($pFilename);
> File::assertFile($filename);
$xml = new XMLReader();
< $xml->xml($this->securityScanner->scanFile('compress.zlib://' . realpath($pFilename)), null, Settings::getLibXmlLoaderOptions());
> $xml->xml($this->getSecurityScannerOrThrow()->scanFile('compress.zlib://' . realpath($filename)), null, Settings::getLibXmlLoaderOptions());
$xml->setParserProperty(2, true); $worksheetNames = []; while ($xml->read()) {
< if ($xml->name == 'gnm:SheetName' && $xml->nodeType == XMLReader::ELEMENT) {
> if (self::matchXml($xml, 'SheetName')) {
$xml->read(); // Move onto the value node $worksheetNames[] = (string) $xml->value;
< } elseif ($xml->name == 'gnm:Sheets') {
> } elseif (self::matchXml($xml, 'Sheets')) {
// break out of the loop once we've got our sheet names rather than parse the entire file break; } } return $worksheetNames; } /** * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). *
< * @param string $pFilename
> * @param string $filename
* * @return array */
< public function listWorksheetInfo($pFilename)
> public function listWorksheetInfo($filename)
{
< File::assertFile($pFilename);
> File::assertFile($filename);
$xml = new XMLReader();
< $xml->xml($this->securityScanner->scanFile('compress.zlib://' . realpath($pFilename)), null, Settings::getLibXmlLoaderOptions());
> $xml->xml($this->getSecurityScannerOrThrow()->scanFile('compress.zlib://' . realpath($filename)), null, Settings::getLibXmlLoaderOptions());
$xml->setParserProperty(2, true); $worksheetInfo = []; while ($xml->read()) {
< if ($xml->name == 'gnm:Sheet' && $xml->nodeType == XMLReader::ELEMENT) {
> if (self::matchXml($xml, 'Sheet')) {
$tmpInfo = [ 'worksheetName' => '', 'lastColumnLetter' => 'A', 'lastColumnIndex' => 0, 'totalRows' => 0, 'totalColumns' => 0, ]; while ($xml->read()) {
< if ($xml->name == 'gnm:Name' && $xml->nodeType == XMLReader::ELEMENT) {
> if (self::matchXml($xml, 'Name')) {
$xml->read(); // Move onto the value node $tmpInfo['worksheetName'] = (string) $xml->value;
< } elseif ($xml->name == 'gnm:MaxCol' && $xml->nodeType == XMLReader::ELEMENT) {
> } elseif (self::matchXml($xml, 'MaxCol')) {
$xml->read(); // Move onto the value node $tmpInfo['lastColumnIndex'] = (int) $xml->value; $tmpInfo['totalColumns'] = (int) $xml->value + 1;
< } elseif ($xml->name == 'gnm:MaxRow' && $xml->nodeType == XMLReader::ELEMENT) {
> } elseif (self::matchXml($xml, 'MaxRow')) {
$xml->read(); // Move onto the value node $tmpInfo['totalRows'] = (int) $xml->value + 1; break; } } $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1); $worksheetInfo[] = $tmpInfo; } } return $worksheetInfo; } /** * @param string $filename * * @return string */ private function gzfileGetContents($filename) { $file = @gzopen($filename, 'rb'); $data = ''; if ($file !== false) { while (!gzeof($file)) { $data .= gzread($file, 1024); } gzclose($file); } return $data; }
> public static function gnumericMappings(): array /** > { * Loads Spreadsheet from file. > return array_merge(self::$mappings, Styles::$mappings); * > } * @param string $pFilename > * > private function processComments(SimpleXMLElement $sheet): void * @throws Exception > { * > if ((!$this->readDataOnly) && (isset($sheet->Objects))) { * @return Spreadsheet > foreach ($sheet->Objects->children(self::NAMESPACE_GNM) as $key => $comment) { */ > $commentAttributes = $comment->attributes(); public function load($pFilename) > // Only comment objects are handled at the moment { > if ($commentAttributes && $commentAttributes->Text) { // Create new Spreadsheet > $this->spreadsheet->getActiveSheet()->getComment((string) $commentAttributes->ObjectBound) $spreadsheet = new Spreadsheet(); > ->setAuthor((string) $commentAttributes->Author) > ->setText($this->parseRichText((string) $commentAttributes->Text)); // Load into this instance > } return $this->loadIntoExisting($pFilename, $spreadsheet); > } } > } > } /** > * Loads from file into Spreadsheet instance. > /** * > * @param mixed $value * @param string $pFilename > */ * @param Spreadsheet $spreadsheet > private static function testSimpleXml($value): SimpleXMLElement * > { * @throws Exception > return ($value instanceof SimpleXMLElement) ? $value : new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><root></root>'); * > } * @return Spreadsheet >
< * < * @param string $pFilename < * < * @throws Exception < * < * @return Spreadsheet
< public function load($pFilename)
> protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
$xml = simplexml_load_string($this->securityScanner->scan($gFileData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions());
> $spreadsheet->removeSheetByIndex(0);
< return $this->loadIntoExisting($pFilename, $spreadsheet);
> return $this->loadIntoExisting($filename, $spreadsheet);
< * < * @param string $pFilename < * @param Spreadsheet $spreadsheet < * < * @throws Exception < * < * @return Spreadsheet
< public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
> public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Spreadsheet
< File::assertFile($pFilename); < < $gFileData = $this->gzfileGetContents($pFilename); < < $xml = simplexml_load_string($this->securityScanner->scan($gFileData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions()); < $namespacesMeta = $xml->getNamespaces(true); < < $gnmXML = $xml->children($namespacesMeta['gnm']); < < $docProps = $spreadsheet->getProperties(); < // Document Properties are held differently, depending on the version of Gnumeric < if (isset($namespacesMeta['office'])) { < $officeXML = $xml->children($namespacesMeta['office']); < $officeDocXML = $officeXML->{'document-meta'}; < $officeDocMetaXML = $officeDocXML->meta; < < foreach ($officeDocMetaXML as $officePropertyData) { < $officePropertyDC = []; < if (isset($namespacesMeta['dc'])) { < $officePropertyDC = $officePropertyData->children($namespacesMeta['dc']); < } < foreach ($officePropertyDC as $propertyName => $propertyValue) { < $propertyValue = (string) $propertyValue; < switch ($propertyName) { < case 'title': < $docProps->setTitle(trim($propertyValue)); < < break; < case 'subject': < $docProps->setSubject(trim($propertyValue)); < < break; < case 'creator': < $docProps->setCreator(trim($propertyValue)); < $docProps->setLastModifiedBy(trim($propertyValue)); < < break; < case 'date': < $creationDate = strtotime(trim($propertyValue)); < $docProps->setCreated($creationDate); < $docProps->setModified($creationDate); < < break; < case 'description': < $docProps->setDescription(trim($propertyValue)); < < break; < } < } < $officePropertyMeta = []; < if (isset($namespacesMeta['meta'])) { < $officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']); < } < foreach ($officePropertyMeta as $propertyName => $propertyValue) { < $attributes = $propertyValue->attributes($namespacesMeta['meta']); < $propertyValue = (string) $propertyValue; < switch ($propertyName) { < case 'keyword': < $docProps->setKeywords(trim($propertyValue)); < < break; < case 'initial-creator': < $docProps->setCreator(trim($propertyValue)); < $docProps->setLastModifiedBy(trim($propertyValue));
> $this->spreadsheet = $spreadsheet; > File::assertFile($filename);
< break; < case 'creation-date': < $creationDate = strtotime(trim($propertyValue)); < $docProps->setCreated($creationDate); < $docProps->setModified($creationDate);
> $gFileData = $this->gzfileGetContents($filename);
< break; < case 'user-defined': < [, $attrName] = explode(':', $attributes['name']); < switch ($attrName) { < case 'publisher': < $docProps->setCompany(trim($propertyValue));
> $xml2 = simplexml_load_string($this->getSecurityScannerOrThrow()->scan($gFileData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions()); > $xml = self::testSimpleXml($xml2);
< break; < case 'category': < $docProps->setCategory(trim($propertyValue)); < < break; < case 'manager': < $docProps->setManager(trim($propertyValue)); < < break; < } < < break; < } < } < } < } elseif (isset($gnmXML->Summary)) { < foreach ($gnmXML->Summary->Item as $summaryItem) { < $propertyName = $summaryItem->name; < $propertyValue = $summaryItem->{'val-string'}; < switch ($propertyName) { < case 'title': < $docProps->setTitle(trim($propertyValue)); < < break; < case 'comments': < $docProps->setDescription(trim($propertyValue)); < < break; < case 'keywords': < $docProps->setKeywords(trim($propertyValue)); < < break; < case 'category': < $docProps->setCategory(trim($propertyValue)); < < break; < case 'manager': < $docProps->setManager(trim($propertyValue)); < < break; < case 'author': < $docProps->setCreator(trim($propertyValue)); < $docProps->setLastModifiedBy(trim($propertyValue)); < < break; < case 'company': < $docProps->setCompany(trim($propertyValue)); < < break; < } < } < }
> $gnmXML = $xml->children(self::NAMESPACE_GNM); > (new Properties($this->spreadsheet))->readProperties($xml, $gnmXML);
< foreach ($gnmXML->Sheets->Sheet as $sheet) {
> foreach ($gnmXML->Sheets->Sheet as $sheetOrNull) { > $sheet = self::testSimpleXml($sheetOrNull);
< if ((isset($this->loadSheetsOnly)) && (!in_array($worksheetName, $this->loadSheetsOnly))) {
> if (is_array($this->loadSheetsOnly) && !in_array($worksheetName, $this->loadSheetsOnly, true)) {
< $spreadsheet->createSheet(); < $spreadsheet->setActiveSheetIndex($worksheetID);
> $this->spreadsheet->createSheet(); > $this->spreadsheet->setActiveSheetIndex($worksheetID);
// name in line with the formula, not the reverse
< $spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false); < < if ((!$this->readDataOnly) && (isset($sheet->PrintInformation))) { < if (isset($sheet->PrintInformation->Margins)) { < foreach ($sheet->PrintInformation->Margins->children('gnm', true) as $key => $margin) { < $marginAttributes = $margin->attributes(); < $marginSize = 72 / 100; // Default < switch ($marginAttributes['PrefUnit']) { < case 'mm': < $marginSize = (int) ($marginAttributes['Points']) / 100;
> $this->spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
< break;
> $visibility = $sheet->attributes()['Visibility'] ?? 'GNM_SHEET_VISIBILITY_VISIBLE'; > if ((string) $visibility !== 'GNM_SHEET_VISIBILITY_VISIBLE') { > $this->spreadsheet->getActiveSheet()->setSheetState(Worksheet::SHEETSTATE_HIDDEN);
}
< switch ($key) { < case 'top': < $spreadsheet->getActiveSheet()->getPageMargins()->setTop($marginSize); < < break; < case 'bottom': < $spreadsheet->getActiveSheet()->getPageMargins()->setBottom($marginSize); < < break; < case 'left': < $spreadsheet->getActiveSheet()->getPageMargins()->setLeft($marginSize); < < break; < case 'right': < $spreadsheet->getActiveSheet()->getPageMargins()->setRight($marginSize); < < break; < case 'header': < $spreadsheet->getActiveSheet()->getPageMargins()->setHeader($marginSize); < < break; < case 'footer': < $spreadsheet->getActiveSheet()->getPageMargins()->setFooter($marginSize);
< break; < } < } < }
> if (!$this->readDataOnly) { > (new PageSetup($this->spreadsheet)) > ->printInformation($sheet) > ->sheetMargins($sheet);
}
< foreach ($sheet->Cells->Cell as $cell) { < $cellAttributes = $cell->attributes();
> foreach ($sheet->Cells->Cell as $cellOrNull) { > $cell = self::testSimpleXml($cellOrNull); > $cellAttributes = self::testSimpleXml($cell->attributes());
$row = (int) $cellAttributes->Row + 1; $column = (int) $cellAttributes->Col;
< if ($row > $maxRow) { < $maxRow = $row; < } < if ($column > $maxCol) { < $maxCol = $column; < }
> $maxRow = max($maxRow, $row); > $maxCol = max($maxCol, $column);
$column = Coordinate::stringFromColumnIndex($column + 1); // Read cell? if ($this->getReadFilter() !== null) { if (!$this->getReadFilter()->readCell($column, $row, $worksheetName)) { continue; } }
< $ValueType = $cellAttributes->ValueType; < $ExprID = (string) $cellAttributes->ExprID; < $type = DataType::TYPE_FORMULA; < if ($ExprID > '') { < if (((string) $cell) > '') { < $this->expressions[$ExprID] = [ < 'column' => $cellAttributes->Col, < 'row' => $cellAttributes->Row, < 'formula' => (string) $cell, < ]; < } else { < $expression = $this->expressions[$ExprID];
> $this->loadCell($cell, $worksheetName, $cellAttributes, $column, $row); > }
< $cell = $this->referenceHelper->updateFormulaReferences( < $expression['formula'], < 'A1', < $cellAttributes->Col - $expression['column'], < $cellAttributes->Row - $expression['row'], < $worksheetName < );
> if ($sheet->Styles !== null) { > (new Styles($this->spreadsheet, $this->readDataOnly))->read($sheet, $maxRow, $maxCol);
}
< $type = DataType::TYPE_FORMULA; < } else { < switch ($ValueType) { < case '10': // NULL < $type = DataType::TYPE_NULL;
< break; < case '20': // Boolean < $type = DataType::TYPE_BOOL; < $cell = $cell == 'TRUE';
> $this->processComments($sheet); > $this->processColumnWidths($sheet, $maxCol); > $this->processRowHeights($sheet, $maxRow); > $this->processMergedCells($sheet); > $this->processAutofilter($sheet);
< break; < case '30': // Integer < $cell = (int) $cell; < // Excel 2007+ doesn't differentiate between integer and float, so set the value and dropthru to the next (numeric) case < // no break < case '40': // Float < $type = DataType::TYPE_NUMERIC;
> $this->setSelectedCells($sheet); > ++$worksheetID; > }
< break; < case '50': // Error < $type = DataType::TYPE_ERROR;
> $this->processDefinedNames($gnmXML);
< break; < case '60': // String < $type = DataType::TYPE_STRING;
> $this->setSelectedSheet($gnmXML);
< break; < case '70': // Cell Range < case '80': // Array < } < } < $spreadsheet->getActiveSheet()->getCell($column . $row)->setValueExplicit($cell, $type);
> // Return > return $this->spreadsheet;
}
< if ((!$this->readDataOnly) && (isset($sheet->Objects))) { < foreach ($sheet->Objects->children('gnm', true) as $key => $comment) { < $commentAttributes = $comment->attributes(); < // Only comment objects are handled at the moment < if ($commentAttributes->Text) { < $spreadsheet->getActiveSheet()->getComment((string) $commentAttributes->ObjectBound)->setAuthor((string) $commentAttributes->Author)->setText($this->parseRichText((string) $commentAttributes->Text)); < }
> private function setSelectedSheet(SimpleXMLElement $gnmXML): void > { > if (isset($gnmXML->UIData)) { > $attributes = self::testSimpleXml($gnmXML->UIData->attributes()); > $selectedSheet = (int) $attributes['SelectedTab']; > $this->spreadsheet->setActiveSheetIndex($selectedSheet);
} }
< foreach ($sheet->Styles->StyleRegion as $styleRegion) { < $styleAttributes = $styleRegion->attributes(); < if (($styleAttributes['startRow'] <= $maxRow) && < ($styleAttributes['startCol'] <= $maxCol)) { < $startColumn = Coordinate::stringFromColumnIndex((int) $styleAttributes['startCol'] + 1); < $startRow = $styleAttributes['startRow'] + 1; < < $endColumn = ($styleAttributes['endCol'] > $maxCol) ? $maxCol : (int) $styleAttributes['endCol']; < $endColumn = Coordinate::stringFromColumnIndex($endColumn + 1); < $endRow = ($styleAttributes['endRow'] > $maxRow) ? $maxRow : $styleAttributes['endRow']; < $endRow += 1; < $cellRange = $startColumn . $startRow . ':' . $endColumn . $endRow; < < $styleAttributes = $styleRegion->Style->attributes(); < < // We still set the number format mask for date/time values, even if readDataOnly is true < if ((!$this->readDataOnly) || < (Date::isDateTimeFormatCode((string) $styleAttributes['Format']))) { < $styleArray = []; < $styleArray['numberFormat']['formatCode'] = (string) $styleAttributes['Format']; < // If readDataOnly is false, we set all formatting information < if (!$this->readDataOnly) { < switch ($styleAttributes['HAlign']) { < case '1': < $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_GENERAL; < < break; < case '2': < $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_LEFT; < < break; < case '4': < $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_RIGHT;
< break; < case '8': < $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_CENTER;
> private function setSelectedCells(?SimpleXMLElement $sheet): void > { > if ($sheet !== null && isset($sheet->Selections)) { > foreach ($sheet->Selections as $selection) { > $startCol = (int) ($selection->StartCol ?? 0); > $startRow = (int) ($selection->StartRow ?? 0) + 1; > $endCol = (int) ($selection->EndCol ?? $startCol); > $endRow = (int) ($selection->endRow ?? 0) + 1;
< break; < case '16': < case '64': < $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_CENTER_CONTINUOUS;
> $startColumn = Coordinate::stringFromColumnIndex($startCol + 1); > $endColumn = Coordinate::stringFromColumnIndex($endCol + 1);
< break; < case '32': < $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_JUSTIFY;
> $startCell = "{$startColumn}{$startRow}"; > $endCell = "{$endColumn}{$endRow}"; > $selectedRange = $startCell . (($endCell !== $startCell) ? ':' . $endCell : ''); > $this->spreadsheet->getActiveSheet()->setSelectedCell($selectedRange);
break; }
< < switch ($styleAttributes['VAlign']) { < case '1': < $styleArray['alignment']['vertical'] = Alignment::VERTICAL_TOP; < < break; < case '2': < $styleArray['alignment']['vertical'] = Alignment::VERTICAL_BOTTOM; < < break; < case '4': < $styleArray['alignment']['vertical'] = Alignment::VERTICAL_CENTER; < < break; < case '8': < $styleArray['alignment']['vertical'] = Alignment::VERTICAL_JUSTIFY; < < break;
> }
}
< $styleArray['alignment']['wrapText'] = $styleAttributes['WrapText'] == '1'; < $styleArray['alignment']['shrinkToFit'] = $styleAttributes['ShrinkToFit'] == '1'; < $styleArray['alignment']['indent'] = ((int) ($styleAttributes['Indent']) > 0) ? $styleAttributes['indent'] : 0; < < $RGB = self::parseGnumericColour($styleAttributes['Fore']); < $styleArray['font']['color']['rgb'] = $RGB; < $RGB = self::parseGnumericColour($styleAttributes['Back']); < $shade = $styleAttributes['Shade']; < if (($RGB != '000000') || ($shade != '0')) { < $styleArray['fill']['color']['rgb'] = $styleArray['fill']['startColor']['rgb'] = $RGB; < $RGB2 = self::parseGnumericColour($styleAttributes['PatternColor']); < $styleArray['fill']['endColor']['rgb'] = $RGB2; < switch ($shade) { < case '1': < $styleArray['fill']['fillType'] = Fill::FILL_SOLID; < < break; < case '2': < $styleArray['fill']['fillType'] = Fill::FILL_GRADIENT_LINEAR; < < break; < case '3': < $styleArray['fill']['fillType'] = Fill::FILL_GRADIENT_PATH; < < break; < case '4': < $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKDOWN; < < break; < case '5': < $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKGRAY; < < break; < case '6': < $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKGRID; < < break; < case '7': < $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKHORIZONTAL; < < break; < case '8': < $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKTRELLIS; < < break; < case '9': < $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKUP; < < break; < case '10': < $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKVERTICAL; < < break; < case '11': < $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_GRAY0625; < < break; < case '12': < $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_GRAY125; < < break; < case '13': < $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTDOWN; < < break; < case '14': < $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTGRAY; < < break; < case '15': < $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTGRID; < < break; < case '16': < $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTHORIZONTAL; < < break; < case '17': < $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTTRELLIS; < < break; < case '18': < $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTUP; < < break; < case '19': < $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTVERTICAL; < < break; < case '20': < $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_MEDIUMGRAY; < < break;
> private function processMergedCells(?SimpleXMLElement $sheet): void > { > // Handle Merged Cells in this worksheet > if ($sheet !== null && isset($sheet->MergedRegions)) { > foreach ($sheet->MergedRegions->Merge as $mergeCells) { > if (strpos((string) $mergeCells, ':') !== false) { > $this->spreadsheet->getActiveSheet()->mergeCells($mergeCells, Worksheet::MERGE_CELL_CONTENT_HIDE);
} }
< < $fontAttributes = $styleRegion->Style->Font->attributes(); < $styleArray['font']['name'] = (string) $styleRegion->Style->Font; < $styleArray['font']['size'] = (int) ($fontAttributes['Unit']); < $styleArray['font']['bold'] = $fontAttributes['Bold'] == '1'; < $styleArray['font']['italic'] = $fontAttributes['Italic'] == '1'; < $styleArray['font']['strikethrough'] = $fontAttributes['StrikeThrough'] == '1'; < switch ($fontAttributes['Underline']) { < case '1': < $styleArray['font']['underline'] = Font::UNDERLINE_SINGLE; < < break; < case '2': < $styleArray['font']['underline'] = Font::UNDERLINE_DOUBLE; < < break; < case '3': < $styleArray['font']['underline'] = Font::UNDERLINE_SINGLEACCOUNTING; < < break; < case '4': < $styleArray['font']['underline'] = Font::UNDERLINE_DOUBLEACCOUNTING; < < break; < default: < $styleArray['font']['underline'] = Font::UNDERLINE_NONE; < < break;
}
< switch ($fontAttributes['Script']) { < case '1': < $styleArray['font']['superscript'] = true; < < break; < case '-1': < $styleArray['font']['subscript'] = true; < < break;
}
< if (isset($styleRegion->Style->StyleBorder)) { < if (isset($styleRegion->Style->StyleBorder->Top)) { < $styleArray['borders']['top'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Top->attributes());
> private function processAutofilter(?SimpleXMLElement $sheet): void > { > if ($sheet !== null && isset($sheet->Filters)) { > foreach ($sheet->Filters->Filter as $autofilter) { > if ($autofilter !== null) { > $attributes = $autofilter->attributes(); > if (isset($attributes['Area'])) { > $this->spreadsheet->getActiveSheet()->setAutoFilter((string) $attributes['Area']);
}
< if (isset($styleRegion->Style->StyleBorder->Bottom)) { < $styleArray['borders']['bottom'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Bottom->attributes());
}
< if (isset($styleRegion->Style->StyleBorder->Left)) { < $styleArray['borders']['left'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Left->attributes());
}
< if (isset($styleRegion->Style->StyleBorder->Right)) { < $styleArray['borders']['right'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Right->attributes());
}
< if ((isset($styleRegion->Style->StyleBorder->Diagonal)) && (isset($styleRegion->Style->StyleBorder->{'Rev-Diagonal'}))) { < $styleArray['borders']['diagonal'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Diagonal->attributes()); < $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_BOTH; < } elseif (isset($styleRegion->Style->StyleBorder->Diagonal)) { < $styleArray['borders']['diagonal'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Diagonal->attributes()); < $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_UP; < } elseif (isset($styleRegion->Style->StyleBorder->{'Rev-Diagonal'})) { < $styleArray['borders']['diagonal'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->{'Rev-Diagonal'}->attributes()); < $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_DOWN;
}
> } > private function setColumnWidth(int $whichColumn, float $defaultWidth): void if (isset($styleRegion->Style->HyperLink)) { > { // TO DO > $columnDimension = $this->spreadsheet->getActiveSheet() $hyperlink = $styleRegion->Style->HyperLink->attributes(); > ->getColumnDimension(Coordinate::stringFromColumnIndex($whichColumn + 1)); } > if ($columnDimension !== null) { } > $columnDimension->setWidth($defaultWidth);
< if (isset($styleRegion->Style->HyperLink)) { < // TO DO < $hyperlink = $styleRegion->Style->HyperLink->attributes();
}
> > private function setColumnInvisible(int $whichColumn): void if ((!$this->readDataOnly) && (isset($sheet->Cols))) { > { // Column Widths > $columnDimension = $this->spreadsheet->getActiveSheet() $columnAttributes = $sheet->Cols->attributes(); > ->getColumnDimension(Coordinate::stringFromColumnIndex($whichColumn + 1)); $defaultWidth = $columnAttributes['DefaultSizePts'] / 5.4; > if ($columnDimension !== null) { $c = 0; > $columnDimension->setVisible(false);
< $spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($styleArray);
$columnAttributes = $columnOverride->attributes();
> $column = $columnAttributes['No']; > private function processColumnLoop(int $whichColumn, int $maxCol, ?SimpleXMLElement $columnOverride, float $defaultWidth): int $columnWidth = $columnAttributes['Unit'] / 5.4; > { $hidden = (isset($columnAttributes['Hidden'])) && ($columnAttributes['Hidden'] == '1'); > $columnOverride = self::testSimpleXml($columnOverride); $columnCount = (isset($columnAttributes['Count'])) ? $columnAttributes['Count'] : 1; > $columnAttributes = self::testSimpleXml($columnOverride->attributes()); while ($c < $column) { > $column = $columnAttributes['No']; $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth); > $columnWidth = ((float) $columnAttributes['Unit']) / 5.4; ++$c; > $hidden = (isset($columnAttributes['Hidden'])) && ((string) $columnAttributes['Hidden'] == '1'); } > $columnCount = (int) ($columnAttributes['Count'] ?? 1); while (($c < ($column + $columnCount)) && ($c <= $maxCol)) { > while ($whichColumn < $column) { $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($columnWidth); > $this->setColumnWidth($whichColumn, $defaultWidth); if ($hidden) { > ++$whichColumn;
$spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setVisible(false);
> while (($whichColumn < ($column + $columnCount)) && ($whichColumn <= $maxCol)) { } > $this->setColumnWidth($whichColumn, $columnWidth); ++$c; > if ($hidden) { } > $this->setColumnInvisible($whichColumn); } > } while ($c <= $maxCol) { > ++$whichColumn;
< if ((!$this->readDataOnly) && (isset($sheet->Cols))) {
> return $whichColumn; > } > > private function processColumnWidths(?SimpleXMLElement $sheet, int $maxCol): void > { > if ((!$this->readDataOnly) && $sheet !== null && (isset($sheet->Cols))) {
++$c;
> $defaultWidth = 0;
}
> if ($columnAttributes !== null) {
< $c = 0;
> } > $whichColumn = 0;
< $columnAttributes = $columnOverride->attributes(); < $column = $columnAttributes['No']; < $columnWidth = $columnAttributes['Unit'] / 5.4; < $hidden = (isset($columnAttributes['Hidden'])) && ($columnAttributes['Hidden'] == '1'); < $columnCount = (isset($columnAttributes['Count'])) ? $columnAttributes['Count'] : 1; < while ($c < $column) { < $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth); < ++$c;
> $whichColumn = $this->processColumnLoop($whichColumn, $maxCol, $columnOverride, $defaultWidth);
< while (($c < ($column + $columnCount)) && ($c <= $maxCol)) { < $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($columnWidth); < if ($hidden) { < $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setVisible(false);
> while ($whichColumn <= $maxCol) { > $this->setColumnWidth($whichColumn, $defaultWidth); > ++$whichColumn;
< ++$c;
< while ($c <= $maxCol) { < $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth); < ++$c;
> > private function setRowHeight(int $whichRow, float $defaultHeight): void > { > $rowDimension = $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow); > if ($rowDimension !== null) { > $rowDimension->setRowHeight($defaultHeight);
< if ((!$this->readDataOnly) && (isset($sheet->Rows))) { < // Row Heights < $rowAttributes = $sheet->Rows->attributes(); < $defaultHeight = $rowAttributes['DefaultSizePts']; < $r = 0;
> private function setRowInvisible(int $whichRow): void > { > $rowDimension = $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow); > if ($rowDimension !== null) { > $rowDimension->setVisible(false); > } > }
< foreach ($sheet->Rows->RowInfo as $rowOverride) { < $rowAttributes = $rowOverride->attributes();
> private function processRowLoop(int $whichRow, int $maxRow, ?SimpleXMLElement $rowOverride, float $defaultHeight): int > { > $rowOverride = self::testSimpleXml($rowOverride); > $rowAttributes = self::testSimpleXml($rowOverride->attributes());
< $rowHeight = $rowAttributes['Unit']; < $hidden = (isset($rowAttributes['Hidden'])) && ($rowAttributes['Hidden'] == '1'); < $rowCount = (isset($rowAttributes['Count'])) ? $rowAttributes['Count'] : 1; < while ($r < $row) { < ++$r; < $spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($defaultHeight); < } < while (($r < ($row + $rowCount)) && ($r < $maxRow)) { < ++$r; < $spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($rowHeight);
> $rowHeight = (float) $rowAttributes['Unit']; > $hidden = (isset($rowAttributes['Hidden'])) && ((string) $rowAttributes['Hidden'] == '1'); > $rowCount = (int) ($rowAttributes['Count'] ?? 1); > while ($whichRow < $row) { > ++$whichRow; > $this->setRowHeight($whichRow, $defaultHeight); > } > while (($whichRow < ($row + $rowCount)) && ($whichRow < $maxRow)) { > ++$whichRow; > $this->setRowHeight($whichRow, $rowHeight);
< $spreadsheet->getActiveSheet()->getRowDimension($r)->setVisible(false); < } < }
> $this->setRowInvisible($whichRow);
< while ($r < $maxRow) { < ++$r; < $spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($defaultHeight);
> ++$worksheetID; > return $whichRow;
< // Handle Merged Cells in this worksheet < if (isset($sheet->MergedRegions)) { < foreach ($sheet->MergedRegions->Merge as $mergeCells) { < if (strpos($mergeCells, ':') !== false) { < $spreadsheet->getActiveSheet()->mergeCells($mergeCells);
> private function processRowHeights(?SimpleXMLElement $sheet, int $maxRow): void > { > if ((!$this->readDataOnly) && $sheet !== null && (isset($sheet->Rows))) { > // Row Heights > $defaultHeight = 0; > $rowAttributes = $sheet->Rows->attributes(); > if ($rowAttributes !== null) { > $defaultHeight = (float) $rowAttributes['DefaultSizePts'];
$name = (string) $namedRange->name;
> $whichRow = 0; $range = (string) $namedRange->value; > if (stripos($range, '#REF!') !== false) { > foreach ($sheet->Rows->RowInfo as $rowOverride) { continue; > $whichRow = $this->processRowLoop($whichRow, $maxRow, $rowOverride, $defaultHeight);
}
> // never executed, I can't figure out any circumstances > // under which it would be executed, and, even if $range = Worksheet::extractSheetTitle($range, true); > // such exist, I'm not convinced this is needed. $range[0] = trim($range[0], "'"); > //while ($whichRow < $maxRow) { if ($worksheet = $spreadsheet->getSheetByName($range[0])) { > // ++$whichRow; $extractedRange = str_replace('$', '', $range[1]); > // $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow)->setRowHeight($defaultHeight); $spreadsheet->addNamedRange(new NamedRange($name, $worksheet, $extractedRange)); > //}
< < ++$worksheetID;
}
> private function processDefinedNames(?SimpleXMLElement $gnmXML): void > {
< if (isset($gnmXML->Names)) { < foreach ($gnmXML->Names->Name as $namedRange) { < $name = (string) $namedRange->name; < $range = (string) $namedRange->value; < if (stripos($range, '#REF!') !== false) {
> if ($gnmXML !== null && isset($gnmXML->Names)) { > foreach ($gnmXML->Names->Name as $definedName) { > $name = (string) $definedName->name; > $value = (string) $definedName->value; > if (stripos($value, '#REF!') !== false) {
< $range = Worksheet::extractSheetTitle($range, true); < $range[0] = trim($range[0], "'"); < if ($worksheet = $spreadsheet->getSheetByName($range[0])) { < $extractedRange = str_replace('$', '', $range[1]); < $spreadsheet->addNamedRange(new NamedRange($name, $worksheet, $extractedRange));
> [$worksheetName] = Worksheet::extractSheetTitle($value, true); > $worksheetName = trim($worksheetName, "'"); > $worksheet = $this->spreadsheet->getSheetByName($worksheetName); > // Worksheet might still be null if we're only loading selected sheets rather than the full spreadsheet > if ($worksheet !== null) { > $this->spreadsheet->addDefinedName(DefinedName::createInstance($name, $worksheet, $value));
< < // Return < return $spreadsheet;
< private static function parseBorderAttributes($borderAttributes) < { < $styleArray = []; < if (isset($borderAttributes['Color'])) { < $styleArray['color']['rgb'] = self::parseGnumericColour($borderAttributes['Color']); < } < < switch ($borderAttributes['Style']) { < case '0': < $styleArray['borderStyle'] = Border::BORDER_NONE; < < break; < case '1': < $styleArray['borderStyle'] = Border::BORDER_THIN; < < break; < case '2': < $styleArray['borderStyle'] = Border::BORDER_MEDIUM; < < break; < case '3': < $styleArray['borderStyle'] = Border::BORDER_SLANTDASHDOT; < < break; < case '4': < $styleArray['borderStyle'] = Border::BORDER_DASHED; < < break; < case '5': < $styleArray['borderStyle'] = Border::BORDER_THICK; < < break; < case '6': < $styleArray['borderStyle'] = Border::BORDER_DOUBLE; < < break; < case '7': < $styleArray['borderStyle'] = Border::BORDER_DOTTED; < < break; < case '8': < $styleArray['borderStyle'] = Border::BORDER_MEDIUMDASHED; < < break; < case '9': < $styleArray['borderStyle'] = Border::BORDER_DASHDOT; < < break; < case '10': < $styleArray['borderStyle'] = Border::BORDER_MEDIUMDASHDOT; < < break; < case '11': < $styleArray['borderStyle'] = Border::BORDER_DASHDOTDOT; < < break; < case '12': < $styleArray['borderStyle'] = Border::BORDER_MEDIUMDASHDOTDOT; < < break; < case '13': < $styleArray['borderStyle'] = Border::BORDER_MEDIUMDASHDOTDOT; < < break; < } < < return $styleArray; < } < < private function parseRichText($is)
> private function parseRichText(string $is): RichText
< private static function parseGnumericColour($gnmColour) < { < [$gnmR, $gnmG, $gnmB] = explode(':', $gnmColour); < $gnmR = substr(str_pad($gnmR, 4, '0', STR_PAD_RIGHT), 0, 2); < $gnmG = substr(str_pad($gnmG, 4, '0', STR_PAD_RIGHT), 0, 2); < $gnmB = substr(str_pad($gnmB, 4, '0', STR_PAD_RIGHT), 0, 2);
> private function loadCell( > SimpleXMLElement $cell, > string $worksheetName, > SimpleXMLElement $cellAttributes, > string $column, > int $row > ): void { > $ValueType = $cellAttributes->ValueType; > $ExprID = (string) $cellAttributes->ExprID; > $type = DataType::TYPE_FORMULA; > if ($ExprID > '') { > if (((string) $cell) > '') { > $this->expressions[$ExprID] = [ > 'column' => $cellAttributes->Col, > 'row' => $cellAttributes->Row, > 'formula' => (string) $cell, > ]; > } else { > $expression = $this->expressions[$ExprID]; > > $cell = $this->referenceHelper->updateFormulaReferences( > $expression['formula'], > 'A1', > $cellAttributes->Col - $expression['column'], > $cellAttributes->Row - $expression['row'], > $worksheetName > ); > } > $type = DataType::TYPE_FORMULA; > } else { > $vtype = (string) $ValueType; > if (array_key_exists($vtype, self::$mappings['dataType'])) { > $type = self::$mappings['dataType'][$vtype]; > } > if ($vtype === '20') { // Boolean > $cell = $cell == 'TRUE'; > } > }
< return $gnmR . $gnmG . $gnmB;
> $this->spreadsheet->getActiveSheet()->getCell($column . $row)->setValueExplicit((string) $cell, $type); > if (isset($cellAttributes->ValueFormat)) { > $this->spreadsheet->getActiveSheet()->getCell($column . $row) > ->getStyle()->getNumberFormat() > ->setFormatCode((string) $cellAttributes->ValueFormat); > }
}