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\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 {
> private const UOM_CONVERSION_POINTS_TO_CENTIMETERS = 0.03527777778; /** >
* Shared Expressions. * * @var array */ private $expressions = [];
> /** private $referenceHelper; > * Spreadsheet shared across all functions. > * /** > * @var Spreadsheet * Create a new Gnumeric. > */ */ > private $spreadsheet; public function __construct() >
{
> * Namespace shared across all functions. parent::__construct(); > * It is 'gnm', except for really old sheets which use 'gmr'. $this->referenceHelper = ReferenceHelper::getInstance(); > * $this->securityScanner = XmlScanner::getInstance($this); > * @var string } > */ > private $gnm = 'gnm'; /** > * Can the current IReader read the file? > /**
* * @param string $pFilename *
< * @throws Exception < *
* @return bool */ public function canRead($pFilename) { File::assertFile($pFilename); // Check if gzlib functions are available
< if (!function_exists('gzread')) { < throw new Exception('gzlib library is not enabled'); < } <
> $data = ''; > if (function_exists('gzread')) {
// Read signature data (first 3 bytes)
< $fh = fopen($pFilename, 'r');
> $fh = fopen($pFilename, 'rb');
$data = fread($fh, 2); fclose($fh);
> }
return $data == chr(0x1F) . chr(0x8B); }
> private static function matchXml(string $name, string $field): bool /** > { * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object. > return 1 === preg_match("/^(gnm|gmr):$field$/", $name); * > } * @param string $pFilename >
* * @return array */ public function listWorksheetNames($pFilename) { File::assertFile($pFilename); $xml = new XMLReader(); $xml->xml($this->securityScanner->scanFile('compress.zlib://' . realpath($pFilename)), null, Settings::getLibXmlLoaderOptions()); $xml->setParserProperty(2, true); $worksheetNames = []; while ($xml->read()) {
< if ($xml->name == 'gnm:SheetName' && $xml->nodeType == XMLReader::ELEMENT) {
> if (self::matchXml($xml->name, 'SheetName') && $xml->nodeType == XMLReader::ELEMENT) {
$xml->read(); // Move onto the value node $worksheetNames[] = (string) $xml->value;
< } elseif ($xml->name == 'gnm:Sheets') {
> } elseif (self::matchXml($xml->name, '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 * * @return array */ public function listWorksheetInfo($pFilename) { File::assertFile($pFilename); $xml = new XMLReader(); $xml->xml($this->securityScanner->scanFile('compress.zlib://' . realpath($pFilename)), null, Settings::getLibXmlLoaderOptions()); $xml->setParserProperty(2, true); $worksheetInfo = []; while ($xml->read()) {
< if ($xml->name == 'gnm:Sheet' && $xml->nodeType == XMLReader::ELEMENT) {
> if (self::matchXml($xml->name, 'Sheet') && $xml->nodeType == XMLReader::ELEMENT) {
$tmpInfo = [ 'worksheetName' => '', 'lastColumnLetter' => 'A', 'lastColumnIndex' => 0, 'totalRows' => 0, 'totalColumns' => 0, ]; while ($xml->read()) {
< if ($xml->name == 'gnm:Name' && $xml->nodeType == XMLReader::ELEMENT) {
> if ($xml->nodeType == XMLReader::ELEMENT) { > if (self::matchXml($xml->name, '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->name, '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->name, '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; }
< /** < * Loads Spreadsheet from file. < * < * @param string $pFilename < * < * @throws Exception < * < * @return Spreadsheet < */ < public function load($pFilename) < { < // Create new Spreadsheet < $spreadsheet = new Spreadsheet();
> private static $mappings = [ > 'borderStyle' => [ > '0' => Border::BORDER_NONE, > '1' => Border::BORDER_THIN, > '2' => Border::BORDER_MEDIUM, > '3' => Border::BORDER_SLANTDASHDOT, > '4' => Border::BORDER_DASHED, > '5' => Border::BORDER_THICK, > '6' => Border::BORDER_DOUBLE, > '7' => Border::BORDER_DOTTED, > '8' => Border::BORDER_MEDIUMDASHED, > '9' => Border::BORDER_DASHDOT, > '10' => Border::BORDER_MEDIUMDASHDOT, > '11' => Border::BORDER_DASHDOTDOT, > '12' => Border::BORDER_MEDIUMDASHDOTDOT, > '13' => Border::BORDER_MEDIUMDASHDOTDOT, > ], > 'dataType' => [ > '10' => DataType::TYPE_NULL, > '20' => DataType::TYPE_BOOL, > '30' => DataType::TYPE_NUMERIC, // Integer doesn't exist in Excel > '40' => DataType::TYPE_NUMERIC, // Float > '50' => DataType::TYPE_ERROR, > '60' => DataType::TYPE_STRING, > //'70': // Cell Range > //'80': // Array > ], > 'fillType' => [ > '1' => Fill::FILL_SOLID, > '2' => Fill::FILL_PATTERN_DARKGRAY, > '3' => Fill::FILL_PATTERN_MEDIUMGRAY, > '4' => Fill::FILL_PATTERN_LIGHTGRAY, > '5' => Fill::FILL_PATTERN_GRAY125, > '6' => Fill::FILL_PATTERN_GRAY0625, > '7' => Fill::FILL_PATTERN_DARKHORIZONTAL, // horizontal stripe > '8' => Fill::FILL_PATTERN_DARKVERTICAL, // vertical stripe > '9' => Fill::FILL_PATTERN_DARKDOWN, // diagonal stripe > '10' => Fill::FILL_PATTERN_DARKUP, // reverse diagonal stripe > '11' => Fill::FILL_PATTERN_DARKGRID, // diagoanl crosshatch > '12' => Fill::FILL_PATTERN_DARKTRELLIS, // thick diagonal crosshatch > '13' => Fill::FILL_PATTERN_LIGHTHORIZONTAL, > '14' => Fill::FILL_PATTERN_LIGHTVERTICAL, > '15' => Fill::FILL_PATTERN_LIGHTUP, > '16' => Fill::FILL_PATTERN_LIGHTDOWN, > '17' => Fill::FILL_PATTERN_LIGHTGRID, // thin horizontal crosshatch > '18' => Fill::FILL_PATTERN_LIGHTTRELLIS, // thin diagonal crosshatch > ], > 'horizontal' => [ > '1' => Alignment::HORIZONTAL_GENERAL, > '2' => Alignment::HORIZONTAL_LEFT, > '4' => Alignment::HORIZONTAL_RIGHT, > '8' => Alignment::HORIZONTAL_CENTER, > '16' => Alignment::HORIZONTAL_CENTER_CONTINUOUS, > '32' => Alignment::HORIZONTAL_JUSTIFY, > '64' => Alignment::HORIZONTAL_CENTER_CONTINUOUS, > ], > 'underline' => [ > '1' => Font::UNDERLINE_SINGLE, > '2' => Font::UNDERLINE_DOUBLE, > '3' => Font::UNDERLINE_SINGLEACCOUNTING, > '4' => Font::UNDERLINE_DOUBLEACCOUNTING, > ], > 'vertical' => [ > '1' => Alignment::VERTICAL_TOP, > '2' => Alignment::VERTICAL_BOTTOM, > '4' => Alignment::VERTICAL_CENTER, > '8' => Alignment::VERTICAL_JUSTIFY, > ], > ];
< // Load into this instance < return $this->loadIntoExisting($pFilename, $spreadsheet);
> public static function gnumericMappings(): array > { > return self::$mappings;
}
< /** < * Loads from file into Spreadsheet instance. < * < * @param string $pFilename < * @param Spreadsheet $spreadsheet < * < * @throws Exception < * < * @return Spreadsheet < */ < public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
> private function docPropertiesOld(SimpleXMLElement $gnmXML): void
{
< File::assertFile($pFilename);
> $docProps = $this->spreadsheet->getProperties(); > foreach ($gnmXML->Summary->Item as $summaryItem) { > $propertyName = $summaryItem->name; > $propertyValue = $summaryItem->{'val-string'}; > switch ($propertyName) { > case 'title': > $docProps->setTitle(trim($propertyValue));
< $gFileData = $this->gzfileGetContents($pFilename);
> break; > case 'comments': > $docProps->setDescription(trim($propertyValue));
< $xml = simplexml_load_string($this->securityScanner->scan($gFileData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions()); < $namespacesMeta = $xml->getNamespaces(true);
> break; > case 'keywords': > $docProps->setKeywords(trim($propertyValue));
< $gnmXML = $xml->children($namespacesMeta['gnm']);
> break; > case 'category': > $docProps->setCategory(trim($propertyValue));
< $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;
> break; > case 'manager': > $docProps->setManager(trim($propertyValue));
< foreach ($officeDocMetaXML as $officePropertyData) { < $officePropertyDC = []; < if (isset($namespacesMeta['dc'])) { < $officePropertyDC = $officePropertyData->children($namespacesMeta['dc']);
> break; > case 'author': > $docProps->setCreator(trim($propertyValue)); > $docProps->setLastModifiedBy(trim($propertyValue)); > > break; > case 'company': > $docProps->setCompany(trim($propertyValue)); > > break; > }
}
> } foreach ($officePropertyDC as $propertyName => $propertyValue) { > $propertyValue = (string) $propertyValue; > private function docPropertiesDC(SimpleXMLElement $officePropertyDC): void switch ($propertyName) { > { case 'title': > $docProps = $this->spreadsheet->getProperties();
< $propertyValue = (string) $propertyValue;
> $propertyValue = trim((string) $propertyValue);
< $docProps->setTitle(trim($propertyValue));
> $docProps->setTitle($propertyValue);
break; case 'subject':
< $docProps->setSubject(trim($propertyValue));
> $docProps->setSubject($propertyValue);
break; case 'creator':
< $docProps->setCreator(trim($propertyValue)); < $docProps->setLastModifiedBy(trim($propertyValue));
> $docProps->setCreator($propertyValue); > $docProps->setLastModifiedBy($propertyValue);
break; case 'date':
< $creationDate = strtotime(trim($propertyValue));
> $creationDate = strtotime($propertyValue);
$docProps->setCreated($creationDate); $docProps->setModified($creationDate); break; case 'description':
< $docProps->setDescription(trim($propertyValue));
> $docProps->setDescription($propertyValue);
break; } }
< $officePropertyMeta = []; < if (isset($namespacesMeta['meta'])) { < $officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']);
}
> foreach ($officePropertyMeta as $propertyName => $propertyValue) { > private function docPropertiesMeta(SimpleXMLElement $officePropertyMeta, array $namespacesMeta): void $attributes = $propertyValue->attributes($namespacesMeta['meta']); > { $propertyValue = (string) $propertyValue; > $docProps = $this->spreadsheet->getProperties();
< $propertyValue = (string) $propertyValue;
> $propertyValue = trim((string) $propertyValue);
case 'keyword':
< $docProps->setKeywords(trim($propertyValue));
> $docProps->setKeywords($propertyValue);
break; case 'initial-creator':
< $docProps->setCreator(trim($propertyValue)); < $docProps->setLastModifiedBy(trim($propertyValue));
> $docProps->setCreator($propertyValue); > $docProps->setLastModifiedBy($propertyValue);
break; case 'creation-date':
< $creationDate = strtotime(trim($propertyValue));
> $creationDate = strtotime($propertyValue);
$docProps->setCreated($creationDate); $docProps->setModified($creationDate); break; case 'user-defined': [, $attrName] = explode(':', $attributes['name']); switch ($attrName) { case 'publisher':
< $docProps->setCompany(trim($propertyValue));
> $docProps->setCompany($propertyValue);
break; case 'category':
< $docProps->setCategory(trim($propertyValue));
> $docProps->setCategory($propertyValue);
break; case 'manager':
< $docProps->setManager(trim($propertyValue));
> $docProps->setManager($propertyValue);
break; } break; } } }
> } elseif (isset($gnmXML->Summary)) { > private function docProperties(SimpleXMLElement $xml, SimpleXMLElement $gnmXML, array $namespacesMeta): void foreach ($gnmXML->Summary->Item as $summaryItem) { > { $propertyName = $summaryItem->name; > if (isset($namespacesMeta['office'])) { $propertyValue = $summaryItem->{'val-string'}; > $officeXML = $xml->children($namespacesMeta['office']); switch ($propertyName) { > $officeDocXML = $officeXML->{'document-meta'}; case 'title': > $officeDocMetaXML = $officeDocXML->meta; $docProps->setTitle(trim($propertyValue)); > > foreach ($officeDocMetaXML as $officePropertyData) { break; > $officePropertyDC = []; case 'comments': > if (isset($namespacesMeta['dc'])) { $docProps->setDescription(trim($propertyValue)); > $officePropertyDC = $officePropertyData->children($namespacesMeta['dc']); > } break; > $this->docPropertiesDC($officePropertyDC); case 'keywords': > $docProps->setKeywords(trim($propertyValue)); > $officePropertyMeta = []; > if (isset($namespacesMeta['meta'])) { break; > $officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']); case 'category': > } $docProps->setCategory(trim($propertyValue)); > $this->docPropertiesMeta($officePropertyMeta, $namespacesMeta); > }
< foreach ($gnmXML->Summary->Item as $summaryItem) { < $propertyName = $summaryItem->name; < $propertyValue = $summaryItem->{'val-string'}; < switch ($propertyName) { < case 'title': < $docProps->setTitle(trim($propertyValue));
> $this->docPropertiesOld($gnmXML); > } > }
< break; < case 'comments': < $docProps->setDescription(trim($propertyValue));
> private function processComments(SimpleXMLElement $sheet): void > { > if ((!$this->readDataOnly) && (isset($sheet->Objects))) { > foreach ($sheet->Objects->children($this->gnm, true) as $key => $comment) { > $commentAttributes = $comment->attributes(); > // Only comment objects are handled at the moment > if ($commentAttributes->Text) { > $this->spreadsheet->getActiveSheet()->getComment((string) $commentAttributes->ObjectBound)->setAuthor((string) $commentAttributes->Author)->setText($this->parseRichText((string) $commentAttributes->Text)); > } > } > } > }
< break; < case 'keywords': < $docProps->setKeywords(trim($propertyValue));
> /** > * Loads Spreadsheet from file. > * > * @param string $pFilename > * > * @return Spreadsheet > */ > public function load($pFilename) > { > // Create new Spreadsheet > $spreadsheet = new Spreadsheet(); > $spreadsheet->removeSheetByIndex(0);
< break; < case 'category': < $docProps->setCategory(trim($propertyValue));
> // Load into this instance > return $this->loadIntoExisting($pFilename, $spreadsheet); > }
< break; < case 'manager': < $docProps->setManager(trim($propertyValue));
> /** > * Loads from file into Spreadsheet instance. > */ > public function loadIntoExisting(string $pFilename, Spreadsheet $spreadsheet): Spreadsheet > { > $this->spreadsheet = $spreadsheet; > File::assertFile($pFilename);
< break; < case 'author': < $docProps->setCreator(trim($propertyValue)); < $docProps->setLastModifiedBy(trim($propertyValue));
> $gFileData = $this->gzfileGetContents($pFilename);
< break; < case 'company': < $docProps->setCompany(trim($propertyValue));
> $xml2 = simplexml_load_string($this->securityScanner->scan($gFileData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions()); > $xml = ($xml2 !== false) ? $xml2 : new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><root></root>'); > $namespacesMeta = $xml->getNamespaces(true); > $this->gnm = array_key_exists('gmr', $namespacesMeta) ? 'gmr' : 'gnm';
< break; < } < } < }
> $gnmXML = $xml->children($namespacesMeta[$this->gnm]); > $this->docProperties($xml, $gnmXML, $namespacesMeta);
< $spreadsheet->createSheet(); < $spreadsheet->setActiveSheetIndex($worksheetID);
> $this->spreadsheet->createSheet(); > $this->spreadsheet->setActiveSheetIndex($worksheetID);
// cells... during the load, all formulae should be correct, and we're simply bringing the worksheet // 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; < < break; < } < 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);
> $this->spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
< break; < } < } < }
> if (!$this->readDataOnly) { > (new PageSetup($this->spreadsheet, $this->gnm)) > ->printInformation($sheet) > ->sheetMargins($sheet);
} foreach ($sheet->Cells->Cell as $cell) { $cellAttributes = $cell->attributes(); $row = (int) $cellAttributes->Row + 1; $column = (int) $cellAttributes->Col; if ($row > $maxRow) { $maxRow = $row; } if ($column > $maxCol) { $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]; $cell = $this->referenceHelper->updateFormulaReferences( $expression['formula'], 'A1', $cellAttributes->Col - $expression['column'], $cellAttributes->Row - $expression['row'], $worksheetName ); } $type = DataType::TYPE_FORMULA; } else {
< switch ($ValueType) { < case '10': // NULL < $type = DataType::TYPE_NULL; < < break; < case '20': // Boolean < $type = DataType::TYPE_BOOL;
> $vtype = (string) $ValueType; > if (array_key_exists($vtype, self::$mappings['dataType'])) { > $type = self::$mappings['dataType'][$vtype]; > } > if ($vtype == '20') { // Boolean
$cell = $cell == 'TRUE';
< < 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; < < break; < case '50': // Error < $type = DataType::TYPE_ERROR; < < break; < case '60': // String < $type = DataType::TYPE_STRING; < < break; < case '70': // Cell Range < case '80': // Array
} }
< $spreadsheet->getActiveSheet()->getCell($column . $row)->setValueExplicit($cell, $type);
> $this->spreadsheet->getActiveSheet()->getCell($column . $row)->setValueExplicit((string) $cell, $type);
}
< 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)); < } < } < }
> $this->processComments($sheet); >
foreach ($sheet->Styles->StyleRegion as $styleRegion) { $styleAttributes = $styleRegion->attributes();
< if (($styleAttributes['startRow'] <= $maxRow) && < ($styleAttributes['startCol'] <= $maxCol)) {
> 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;
> > $endRow = 1 + (($styleAttributes['endRow'] > $maxRow) ? $maxRow : (int) $styleAttributes['endRow']);
$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; < < break; < case '16': < case '64': < $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_CENTER_CONTINUOUS; < < break; < case '32': < $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_JUSTIFY; < < 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;
> // We still set the number format mask for date/time values, even if readDataOnly is true > $formatCode = (string) $styleAttributes['Format']; > if (Date::isDateTimeFormatCode($formatCode)) { > $styleArray['numberFormat']['formatCode'] = $formatCode;
}
> if (!$this->readDataOnly) { > // If readDataOnly is false, we set all formatting information $styleArray['alignment']['wrapText'] = $styleAttributes['WrapText'] == '1'; > $styleArray['numberFormat']['formatCode'] = $formatCode;
$styleArray['alignment']['shrinkToFit'] = $styleAttributes['ShrinkToFit'] == '1';
> self::addStyle2($styleArray, 'alignment', 'horizontal', $styleAttributes['HAlign']); $styleArray['alignment']['indent'] = ((int) ($styleAttributes['Indent']) > 0) ? $styleAttributes['indent'] : 0; > self::addStyle2($styleArray, 'alignment', 'vertical', $styleAttributes['VAlign']);
> $styleArray['alignment']['textRotation'] = $this->calcRotation($styleAttributes);
< $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; < } < }
> $this->addColors($styleArray, $styleAttributes);
$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;
> self::addStyle2($styleArray, 'font', 'underline', $fontAttributes['Underline']);
< 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());
> $srssb = $styleRegion->Style->StyleBorder; > $this->addBorderStyle($srssb, $styleArray, 'top'); > $this->addBorderStyle($srssb, $styleArray, 'bottom'); > $this->addBorderStyle($srssb, $styleArray, 'left'); > $this->addBorderStyle($srssb, $styleArray, 'right'); > $this->addBorderDiagonal($srssb, $styleArray); > } > if (isset($styleRegion->Style->HyperLink)) { > // TO DO > $hyperlink = $styleRegion->Style->HyperLink->attributes();
}
< 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());
> $this->spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($styleArray);
}
< 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());
> > $this->processColumnWidths($sheet, $maxCol); > $this->processRowHeights($sheet, $maxRow); > $this->processMergedCells($sheet); > > ++$worksheetID; > } > > $this->processDefinedNames($gnmXML); > > // Return > return $this->spreadsheet; > } > > private function addBorderDiagonal(SimpleXMLElement $srssb, array &$styleArray): void > { > if (isset($srssb->Diagonal, $srssb->{'Rev-Diagonal'})) { > $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes());
$styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_BOTH;
< } elseif (isset($styleRegion->Style->StyleBorder->Diagonal)) { < $styleArray['borders']['diagonal'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Diagonal->attributes());
> } elseif (isset($srssb->Diagonal)) { > $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->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());
> } elseif (isset($srssb->{'Rev-Diagonal'})) { > $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->{'Rev-Diagonal'}->attributes());
$styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_DOWN; } }
< if (isset($styleRegion->Style->HyperLink)) { < // TO DO < $hyperlink = $styleRegion->Style->HyperLink->attributes();
> > private function addBorderStyle(SimpleXMLElement $srssb, array &$styleArray, string $direction): void > { > $ucDirection = ucfirst($direction); > if (isset($srssb->$ucDirection)) { > $styleArray['borders'][$direction] = self::parseBorderAttributes($srssb->$ucDirection->attributes());
} }
< $spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($styleArray);
> > private function processMergedCells(SimpleXMLElement $sheet): void > { > // Handle Merged Cells in this worksheet > if (isset($sheet->MergedRegions)) { > foreach ($sheet->MergedRegions->Merge as $mergeCells) { > if (strpos($mergeCells, ':') !== false) { > $this->spreadsheet->getActiveSheet()->mergeCells($mergeCells); > }
} } }
< if ((!$this->readDataOnly) && (isset($sheet->Cols))) { < // Column Widths < $columnAttributes = $sheet->Cols->attributes(); < $defaultWidth = $columnAttributes['DefaultSizePts'] / 5.4; < $c = 0; < foreach ($sheet->Cols->ColInfo as $columnOverride) {
> private function processColumnLoop(int $c, int $maxCol, SimpleXMLElement $columnOverride, float $defaultWidth): int > {
$columnAttributes = $columnOverride->attributes(); $column = $columnAttributes['No'];
< $columnWidth = $columnAttributes['Unit'] / 5.4; < $hidden = (isset($columnAttributes['Hidden'])) && ($columnAttributes['Hidden'] == '1');
> $columnWidth = ((float) $columnAttributes['Unit']) / 5.4; > $hidden = (isset($columnAttributes['Hidden'])) && ((string) $columnAttributes['Hidden'] == '1');
$columnCount = (isset($columnAttributes['Count'])) ? $columnAttributes['Count'] : 1; while ($c < $column) {
< $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth);
> $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth);
++$c; } while (($c < ($column + $columnCount)) && ($c <= $maxCol)) {
< $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($columnWidth);
> $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($columnWidth);
if ($hidden) {
< $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setVisible(false);
> $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setVisible(false);
} ++$c; }
> } > return $c; while ($c <= $maxCol) { > } $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth); > ++$c; > private function processColumnWidths(SimpleXMLElement $sheet, int $maxCol): void } > { } > if ((!$this->readDataOnly) && (isset($sheet->Cols))) { > // Column Widths if ((!$this->readDataOnly) && (isset($sheet->Rows))) { > $columnAttributes = $sheet->Cols->attributes(); // Row Heights > $defaultWidth = $columnAttributes['DefaultSizePts'] / 5.4; $rowAttributes = $sheet->Rows->attributes(); > $c = 0; $defaultHeight = $rowAttributes['DefaultSizePts']; > foreach ($sheet->Cols->ColInfo as $columnOverride) { $r = 0; > $c = $this->processColumnLoop($c, $maxCol, $columnOverride, $defaultWidth);
< $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth);
> $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth);
foreach ($sheet->Rows->RowInfo as $rowOverride) {
> }
< if ((!$this->readDataOnly) && (isset($sheet->Rows))) { < // Row Heights < $rowAttributes = $sheet->Rows->attributes(); < $defaultHeight = $rowAttributes['DefaultSizePts']; < $r = 0; < < foreach ($sheet->Rows->RowInfo as $rowOverride) {
> private function processRowLoop(int $r, int $maxRow, SimpleXMLElement $rowOverride, float $defaultHeight): int > {
< $rowHeight = $rowAttributes['Unit']; < $hidden = (isset($rowAttributes['Hidden'])) && ($rowAttributes['Hidden'] == '1');
> $rowHeight = (float) $rowAttributes['Unit']; > $hidden = (isset($rowAttributes['Hidden'])) && ((string) $rowAttributes['Hidden'] == '1');
< $spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($defaultHeight);
> $this->spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($defaultHeight);
++$r;
< $spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($rowHeight);
> $this->spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($rowHeight);
if ($hidden) {
< $spreadsheet->getActiveSheet()->getRowDimension($r)->setVisible(false); < } < } < } < while ($r < $maxRow) { < ++$r; < $spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($defaultHeight);
> $this->spreadsheet->getActiveSheet()->getRowDimension($r)->setVisible(false);
} }
< // Handle Merged Cells in this worksheet < if (isset($sheet->MergedRegions)) { < foreach ($sheet->MergedRegions->Merge as $mergeCells) { < if (strpos($mergeCells, ':') !== false) { < $spreadsheet->getActiveSheet()->mergeCells($mergeCells);
> return $r;
}
> } > private function processRowHeights(SimpleXMLElement $sheet, int $maxRow): void } > { > if ((!$this->readDataOnly) && (isset($sheet->Rows))) { ++$worksheetID; > // Row Heights } > $rowAttributes = $sheet->Rows->attributes(); > $defaultHeight = (float) $rowAttributes['DefaultSizePts']; // Loop through definedNames (global named ranges) > $r = 0; if (isset($gnmXML->Names)) { > foreach ($gnmXML->Names->Name as $namedRange) { > foreach ($sheet->Rows->RowInfo as $rowOverride) { $name = (string) $namedRange->name; > $r = $this->processRowLoop($r, $maxRow, $rowOverride, $defaultHeight);
$range = (string) $namedRange->value;
> // never executed, I can't figure out any circumstances if (stripos($range, '#REF!') !== false) { > // under which it would be executed, and, even if continue; > // such exist, I'm not convinced this is needed. } > //while ($r < $maxRow) { > // ++$r; $range = Worksheet::extractSheetTitle($range, true); > // $this->spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($defaultHeight); $range[0] = trim($range[0], "'"); > //}
< < ++$worksheetID;
$spreadsheet->addNamedRange(new NamedRange($name, $worksheet, $extractedRange));
> private function processDefinedNames(SimpleXMLElement $gnmXML): void } > {
< foreach ($gnmXML->Names->Name as $namedRange) { < $name = (string) $namedRange->name; < $range = (string) $namedRange->value; < if (stripos($range, '#REF!') !== false) {
> 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)); > } > } > } > } > > private function calcRotation(SimpleXMLElement $styleAttributes): int > { > $rotation = (int) $styleAttributes->Rotation; > if ($rotation >= 270 && $rotation <= 360) { > $rotation -= 360; > } > $rotation = (abs($rotation) > 90) ? 0 : $rotation; > > return $rotation;
$styleArray = [];
> if (isset($borderAttributes['Color'])) { > private static function addStyle(array &$styleArray, string $key, string $value): void $styleArray['color']['rgb'] = self::parseGnumericColour($borderAttributes['Color']); > { } > if (array_key_exists($value, self::$mappings[$key])) { > $styleArray[$key] = self::$mappings[$key][$value];
< // Return < return $spreadsheet;
> private static function addStyle2(array &$styleArray, string $key1, string $key, string $value): void > { > if (array_key_exists($value, self::$mappings[$key])) { > $styleArray[$key1][$key] = self::$mappings[$key][$value]; > }
< 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; < }
> self::addStyle($styleArray, 'borderStyle', $borderAttributes['Style']);
} private function parseRichText($is) { $value = new RichText(); $value->createText($is); return $value; } 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); return $gnmR . $gnmG . $gnmB;
> } } > } > private function addColors(array &$styleArray, SimpleXMLElement $styleAttributes): void > { > $RGB = self::parseGnumericColour($styleAttributes['Fore']); > $styleArray['font']['color']['rgb'] = $RGB; > $RGB = self::parseGnumericColour($styleAttributes['Back']); > $shade = (string) $styleAttributes['Shade']; > if (($RGB != '000000') || ($shade != '0')) { > $RGB2 = self::parseGnumericColour($styleAttributes['PatternColor']); > if ($shade == '1') { > $styleArray['fill']['startColor']['rgb'] = $RGB; > $styleArray['fill']['endColor']['rgb'] = $RGB2; > } else { > $styleArray['fill']['endColor']['rgb'] = $RGB; > $styleArray['fill']['startColor']['rgb'] = $RGB2; > } > self::addStyle2($styleArray, 'fill', 'fillType', $shade); > }