From 73379cdfb1034db66167ae2c01e0b339366b4ddd Mon Sep 17 00:00:00 2001 From: oleibman Date: Fri, 19 Jun 2020 11:34:02 -0700 Subject: [PATCH] Improve Coverage for Gnumeric (#1517) * Improve Coverage for Gnumeric I believe that both BaseReader and Gnumeric Reader are now 100% covered. My goal was to use PhpSpreadsheet to load the test file, save it as Xlsx, and visually compare the two, then add a test loaded with assertions. Results were generally pretty good, but there were no tests with assertions. I added a few cells to exercise some previously uncovered code. Code was extensively refactored; logic changes are noted below. Code allowed for specifying document properties in an old format. I considered removing that, but I found the original spec at http://www.jfree.org/jworkbook/download/gnumeric-xml.pdf This allowed me to create an old file, which was not handled correctly because of namespace differences. The code was corrected to allow for this difference. Added support for textRotation. Mapping of fill types was not correct. * PHP7.2 Error One assertion failed under PHP7.2. Apparently there was some change in the handling of SimpleXMLElement between 7.2 and 7.3. Casting to string before use eliminates the problem. * Scrutinizer Recommendations All minor, solved (hopefully) mostly by casts. * One Last Scrutinizer Fix ... I hope. --- samples/templates/GnumericTest.gnumeric | Bin 7823 -> 8064 bytes samples/templates/old.gnumeric | Bin 0 -> 1276 bytes src/PhpSpreadsheet/Reader/BaseReader.php | 23 +- src/PhpSpreadsheet/Reader/Gnumeric.php | 1123 ++++++++--------- tests/PhpSpreadsheetTests/IOFactoryTest.php | 1 + .../Reader/Gnumeric/GnumericFilter.php | 14 + .../Reader/Gnumeric/GnumericInfoTest.php | 47 + .../Reader/Gnumeric/GnumericLoadTest.php | 162 +++ .../Reader/Gnumeric/GnumericStylesTest.php | 269 ++++ 9 files changed, 1060 insertions(+), 579 deletions(-) create mode 100644 samples/templates/old.gnumeric create mode 100644 tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericFilter.php create mode 100644 tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericInfoTest.php create mode 100644 tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericLoadTest.php create mode 100644 tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericStylesTest.php diff --git a/samples/templates/GnumericTest.gnumeric b/samples/templates/GnumericTest.gnumeric index ea2fac379e51e707060c8c8d5314899c60bad287..0493e762360924353d0d60a1709528637db0224b 100644 GIT binary patch literal 8064 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 literal 0 HcmV?d00001 diff --git a/src/PhpSpreadsheet/Reader/BaseReader.php b/src/PhpSpreadsheet/Reader/BaseReader.php index 77a6421b..eb0e3ba2 100644 --- a/src/PhpSpreadsheet/Reader/BaseReader.php +++ b/src/PhpSpreadsheet/Reader/BaseReader.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader; +use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException; use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; use PhpOffice\PhpSpreadsheet\Shared\File; @@ -133,11 +134,7 @@ abstract class BaseReader implements IReader public function getSecurityScanner() { - if (property_exists($this, 'securityScanner')) { - return $this->securityScanner; - } - - return null; + return $this->securityScanner; } /** @@ -147,12 +144,18 @@ abstract class BaseReader implements IReader */ protected function openFile($pFilename): void { - File::assertFile($pFilename); + if ($pFilename) { + File::assertFile($pFilename); - // Open file - $this->fileHandle = fopen($pFilename, 'rb'); - if ($this->fileHandle === false) { - throw new Exception('Could not open file ' . $pFilename . ' for reading.'); + // Open file + $fileHandle = fopen($pFilename, 'rb'); + } else { + $fileHandle = false; + } + if ($fileHandle !== false) { + $this->fileHandle = $fileHandle; + } else { + throw new ReaderException('Could not open file ' . $pFilename . ' for reading.'); } } } diff --git a/src/PhpSpreadsheet/Reader/Gnumeric.php b/src/PhpSpreadsheet/Reader/Gnumeric.php index f9768029..81096730 100644 --- a/src/PhpSpreadsheet/Reader/Gnumeric.php +++ b/src/PhpSpreadsheet/Reader/Gnumeric.php @@ -18,6 +18,7 @@ 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 @@ -29,8 +30,23 @@ class Gnumeric extends BaseReader */ private $expressions = []; + /** + * Spreadsheet shared across all functions. + * + * @var Spreadsheet + */ + private $spreadsheet; + private $referenceHelper; + /** + * Namespace shared across all functions. + * It is 'gnm', except for really old sheets which use 'gmr'. + * + * @var string + */ + private $gnm = 'gnm'; + /** * Create a new Gnumeric. */ @@ -53,18 +69,22 @@ class Gnumeric extends BaseReader 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, 'rb'); + $data = fread($fh, 2); + fclose($fh); } - // Read signature data (first 3 bytes) - $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 + { + return 1 === preg_match("/^(gnm|gmr):$field$/", $name); + } + /** * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object. * @@ -82,10 +102,10 @@ class Gnumeric extends BaseReader $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; } @@ -111,7 +131,7 @@ class Gnumeric extends BaseReader $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', @@ -121,18 +141,20 @@ class Gnumeric extends BaseReader ]; while ($xml->read()) { - if ($xml->name == 'gnm:Name' && $xml->nodeType == XMLReader::ELEMENT) { - $xml->read(); // Move onto the value node - $tmpInfo['worksheetName'] = (string) $xml->value; - } elseif ($xml->name == 'gnm:MaxCol' && $xml->nodeType == XMLReader::ELEMENT) { - $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) { - $xml->read(); // Move onto the value node - $tmpInfo['totalRows'] = (int) $xml->value + 1; + if ($xml->nodeType == XMLReader::ELEMENT) { + if (self::matchXml($xml->name, 'Name')) { + $xml->read(); // Move onto the value node + $tmpInfo['worksheetName'] = (string) $xml->value; + } 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 (self::matchXml($xml->name, 'MaxRow')) { + $xml->read(); // Move onto the value node + $tmpInfo['totalRows'] = (int) $xml->value + 1; - break; + break; + } } } $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1); @@ -162,6 +184,283 @@ class Gnumeric extends BaseReader return $data; } + 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, + ], + ]; + + public static function gnumericMappings(): array + { + return self::$mappings; + } + + private function docPropertiesOld(SimpleXMLElement $gnmXML): void + { + $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)); + + 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; + } + } + } + + private function docPropertiesDC(SimpleXMLElement $officePropertyDC): void + { + $docProps = $this->spreadsheet->getProperties(); + foreach ($officePropertyDC as $propertyName => $propertyValue) { + $propertyValue = trim((string) $propertyValue); + switch ($propertyName) { + case 'title': + $docProps->setTitle($propertyValue); + + break; + case 'subject': + $docProps->setSubject($propertyValue); + + break; + case 'creator': + $docProps->setCreator($propertyValue); + $docProps->setLastModifiedBy($propertyValue); + + break; + case 'date': + $creationDate = strtotime($propertyValue); + $docProps->setCreated($creationDate); + $docProps->setModified($creationDate); + + break; + case 'description': + $docProps->setDescription($propertyValue); + + break; + } + } + } + + private function docPropertiesMeta(SimpleXMLElement $officePropertyMeta, array $namespacesMeta): void + { + $docProps = $this->spreadsheet->getProperties(); + foreach ($officePropertyMeta as $propertyName => $propertyValue) { + $attributes = $propertyValue->attributes($namespacesMeta['meta']); + $propertyValue = trim((string) $propertyValue); + switch ($propertyName) { + case 'keyword': + $docProps->setKeywords($propertyValue); + + break; + case 'initial-creator': + $docProps->setCreator($propertyValue); + $docProps->setLastModifiedBy($propertyValue); + + break; + case 'creation-date': + $creationDate = strtotime($propertyValue); + $docProps->setCreated($creationDate); + $docProps->setModified($creationDate); + + break; + case 'user-defined': + [, $attrName] = explode(':', $attributes['name']); + switch ($attrName) { + case 'publisher': + $docProps->setCompany($propertyValue); + + break; + case 'category': + $docProps->setCategory($propertyValue); + + break; + case 'manager': + $docProps->setManager($propertyValue); + + break; + } + + break; + } + } + } + + private function docProperties(SimpleXMLElement $xml, SimpleXMLElement $gnmXML, array $namespacesMeta): void + { + 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']); + } + $this->docPropertiesDC($officePropertyDC); + + $officePropertyMeta = []; + if (isset($namespacesMeta['meta'])) { + $officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']); + } + $this->docPropertiesMeta($officePropertyMeta, $namespacesMeta); + } + } elseif (isset($gnmXML->Summary)) { + $this->docPropertiesOld($gnmXML); + } + } + + private function sheetMargin(string $key, float $marginSize): void + { + switch ($key) { + case 'top': + $this->spreadsheet->getActiveSheet()->getPageMargins()->setTop($marginSize); + + break; + case 'bottom': + $this->spreadsheet->getActiveSheet()->getPageMargins()->setBottom($marginSize); + + break; + case 'left': + $this->spreadsheet->getActiveSheet()->getPageMargins()->setLeft($marginSize); + + break; + case 'right': + $this->spreadsheet->getActiveSheet()->getPageMargins()->setRight($marginSize); + + break; + case 'header': + $this->spreadsheet->getActiveSheet()->getPageMargins()->setHeader($marginSize); + + break; + case 'footer': + $this->spreadsheet->getActiveSheet()->getPageMargins()->setFooter($marginSize); + + break; + } + } + + private function sheetMargins(SimpleXMLElement $sheet): void + { + if (!$this->readDataOnly && isset($sheet->PrintInformation, $sheet->PrintInformation->Margins)) { + foreach ($sheet->PrintInformation->Margins->children($this->gnm, true) as $key => $margin) { + $marginAttributes = $margin->attributes(); + $marginSize = 72 / 100; // Default + switch ($marginAttributes['PrefUnit']) { + case 'mm': + $marginSize = (int) ($marginAttributes['Points']) / 100; + + break; + } + $this->sheetMargin($key, (float) $marginSize); + } + } + } + + 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)); + } + } + } + } + /** * Loads Spreadsheet from file. * @@ -173,6 +472,7 @@ class Gnumeric extends BaseReader { // Create new Spreadsheet $spreadsheet = new Spreadsheet(); + $spreadsheet->removeSheetByIndex(0); // Load into this instance return $this->loadIntoExisting($pFilename, $spreadsheet); @@ -180,143 +480,21 @@ class Gnumeric extends BaseReader /** * Loads from file into Spreadsheet instance. - * - * @param string $pFilename - * - * @return Spreadsheet */ - public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet) + public function loadIntoExisting(string $pFilename, Spreadsheet $spreadsheet): Spreadsheet { + $this->spreadsheet = $spreadsheet; File::assertFile($pFilename); $gFileData = $this->gzfileGetContents($pFilename); - $xml = simplexml_load_string($this->securityScanner->scan($gFileData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions()); + $xml2 = simplexml_load_string($this->securityScanner->scan($gFileData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions()); + $xml = ($xml2 !== false) ? $xml2 : new SimpleXMLElement(''); $namespacesMeta = $xml->getNamespaces(true); + $this->gnm = array_key_exists('gmr', $namespacesMeta) ? 'gmr' : 'gnm'; - $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)); - - break; - case 'creation-date': - $creationDate = strtotime(trim($propertyValue)); - $docProps->setCreated($creationDate); - $docProps->setModified($creationDate); - - break; - case 'user-defined': - [, $attrName] = explode(':', $attributes['name']); - switch ($attrName) { - case 'publisher': - $docProps->setCompany(trim($propertyValue)); - - 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($namespacesMeta[$this->gnm]); + $this->docProperties($xml, $gnmXML, $namespacesMeta); $worksheetID = 0; foreach ($gnmXML->Sheets->Sheet as $sheet) { @@ -328,53 +506,14 @@ class Gnumeric extends BaseReader $maxRow = $maxCol = 0; // Create new Worksheet - $spreadsheet->createSheet(); - $spreadsheet->setActiveSheetIndex($worksheetID); + $this->spreadsheet->createSheet(); + $this->spreadsheet->setActiveSheetIndex($worksheetID); // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula // 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); + $this->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); - - break; - } - } - } - } + $this->sheetMargins($sheet); foreach ($sheet->Cells->Cell as $cell) { $cellAttributes = $cell->attributes(); @@ -420,48 +559,19 @@ class Gnumeric extends BaseReader } $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'; - - 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 + $vtype = (string) $ValueType; + if (array_key_exists($vtype, self::$mappings['dataType'])) { + $type = self::$mappings['dataType'][$vtype]; + } + if ($vtype == '20') { // Boolean + $cell = $cell == 'TRUE'; } } - $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) && @@ -471,306 +581,197 @@ class Gnumeric extends BaseReader $endColumn = ($styleAttributes['endCol'] > $maxCol) ? $maxCol : (int) $styleAttributes['endCol']; $endColumn = Coordinate::stringFromColumnIndex($endColumn + 1); - $endRow = ($styleAttributes['endRow'] > $maxRow) ? $maxRow : $styleAttributes['endRow']; - ++$endRow; + $endRow = 1 + (($styleAttributes['endRow'] > $maxRow) ? $maxRow : $styleAttributes['endRow']); $cellRange = $startColumn . $startRow . ':' . $endColumn . $endRow; $styleAttributes = $styleRegion->Style->attributes(); + $styleArray = []; // 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']; + $formatCode = (string) $styleAttributes['Format']; + if (Date::isDateTimeFormatCode($formatCode)) { + $styleArray['numberFormat']['formatCode'] = $formatCode; + } + if (!$this->readDataOnly) { // If readDataOnly is false, we set all formatting information - if (!$this->readDataOnly) { - switch ($styleAttributes['HAlign']) { - case '1': - $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_GENERAL; + $styleArray['numberFormat']['formatCode'] = $formatCode; - break; - case '2': - $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_LEFT; + self::addStyle2($styleArray, 'alignment', 'horizontal', $styleAttributes['HAlign']); + self::addStyle2($styleArray, 'alignment', 'vertical', $styleAttributes['VAlign']); + $styleArray['alignment']['wrapText'] = $styleAttributes['WrapText'] == '1'; + $styleArray['alignment']['textRotation'] = $this->calcRotation($styleAttributes); + $styleArray['alignment']['shrinkToFit'] = $styleAttributes['ShrinkToFit'] == '1'; + $styleArray['alignment']['indent'] = ((int) ($styleAttributes['Indent']) > 0) ? $styleAttributes['indent'] : 0; - break; - case '4': - $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_RIGHT; + $this->addColors($styleArray, $styleAttributes); - break; - case '8': - $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_CENTER; + $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'; + self::addStyle2($styleArray, 'font', 'underline', $fontAttributes['Underline']); - break; - case '16': - case '64': - $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_CENTER_CONTINUOUS; + switch ($fontAttributes['Script']) { + case '1': + $styleArray['font']['superscript'] = true; - break; - case '32': - $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_JUSTIFY; + break; + case '-1': + $styleArray['font']['subscript'] = true; - 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; - } - } - - $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()); - } - 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; - } - } - if (isset($styleRegion->Style->HyperLink)) { - // TO DO - $hyperlink = $styleRegion->Style->HyperLink->attributes(); - } + break; } - $spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($styleArray); - } - } - } - 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) { - $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; - } - 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); + if (isset($styleRegion->Style->StyleBorder)) { + $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); } - ++$c; - } - } - while ($c <= $maxCol) { - $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth); - ++$c; - } - } - - if ((!$this->readDataOnly) && (isset($sheet->Rows))) { - // Row Heights - $rowAttributes = $sheet->Rows->attributes(); - $defaultHeight = $rowAttributes['DefaultSizePts']; - $r = 0; - - foreach ($sheet->Rows->RowInfo as $rowOverride) { - $rowAttributes = $rowOverride->attributes(); - $row = $rowAttributes['No']; - $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); - if ($hidden) { - $spreadsheet->getActiveSheet()->getRowDimension($r)->setVisible(false); + if (isset($styleRegion->Style->HyperLink)) { + // TO DO + $hyperlink = $styleRegion->Style->HyperLink->attributes(); } } - } - while ($r < $maxRow) { - ++$r; - $spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($defaultHeight); + $this->spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($styleArray); } } - // Handle Merged Cells in this worksheet - if (isset($sheet->MergedRegions)) { - foreach ($sheet->MergedRegions->Merge as $mergeCells) { - if (strpos($mergeCells, ':') !== false) { - $spreadsheet->getActiveSheet()->mergeCells($mergeCells); - } - } - } + $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($srssb->Diagonal)) { + $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes()); + $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_UP; + } elseif (isset($srssb->{'Rev-Diagonal'})) { + $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->{'Rev-Diagonal'}->attributes()); + $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_DOWN; + } + } + + 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()); + } + } + + 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); + } + } + } + } + + private function processColumnLoop(int $c, int $maxCol, SimpleXMLElement $columnOverride, float $defaultWidth): int + { + $columnAttributes = $columnOverride->attributes(); + $column = $columnAttributes['No']; + $columnWidth = ((float) $columnAttributes['Unit']) / 5.4; + $hidden = (isset($columnAttributes['Hidden'])) && ((string) $columnAttributes['Hidden'] == '1'); + $columnCount = (isset($columnAttributes['Count'])) ? $columnAttributes['Count'] : 1; + while ($c < $column) { + $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth); + ++$c; + } + while (($c < ($column + $columnCount)) && ($c <= $maxCol)) { + $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($columnWidth); + if ($hidden) { + $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setVisible(false); + } + ++$c; + } + + return $c; + } + + private function processColumnWidths(SimpleXMLElement $sheet, int $maxCol): void + { + 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) { + $c = $this->processColumnLoop($c, $maxCol, $columnOverride, $defaultWidth); + } + while ($c <= $maxCol) { + $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth); + ++$c; + } + } + } + + private function processRowLoop(int $r, int $maxRow, SimpleXMLElement $rowOverride, float $defaultHeight): int + { + $rowAttributes = $rowOverride->attributes(); + $row = $rowAttributes['No']; + $rowHeight = (float) $rowAttributes['Unit']; + $hidden = (isset($rowAttributes['Hidden'])) && ((string) $rowAttributes['Hidden'] == '1'); + $rowCount = (isset($rowAttributes['Count'])) ? $rowAttributes['Count'] : 1; + while ($r < $row) { + ++$r; + $this->spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($defaultHeight); + } + while (($r < ($row + $rowCount)) && ($r < $maxRow)) { + ++$r; + $this->spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($rowHeight); + if ($hidden) { + $this->spreadsheet->getActiveSheet()->getRowDimension($r)->setVisible(false); + } + } + + return $r; + } + + private function processRowHeights(SimpleXMLElement $sheet, int $maxRow): void + { + if ((!$this->readDataOnly) && (isset($sheet->Rows))) { + // Row Heights + $rowAttributes = $sheet->Rows->attributes(); + $defaultHeight = (float) $rowAttributes['DefaultSizePts']; + $r = 0; + + foreach ($sheet->Rows->RowInfo as $rowOverride) { + $r = $this->processRowLoop($r, $maxRow, $rowOverride, $defaultHeight); + } + // never executed, I can't figure out any circumstances + // under which it would be executed, and, even if + // such exist, I'm not convinced this is needed. + //while ($r < $maxRow) { + // ++$r; + // $this->spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($defaultHeight); + //} + } + } + + private function processDefinedNames(SimpleXMLElement $gnmXML): void + { // Loop through definedNames (global named ranges) if (isset($gnmXML->Names)) { foreach ($gnmXML->Names->Name as $namedRange) { @@ -782,15 +783,37 @@ class Gnumeric extends BaseReader $range = Worksheet::extractSheetTitle($range, true); $range[0] = trim($range[0], "'"); - if ($worksheet = $spreadsheet->getSheetByName($range[0])) { + if ($worksheet = $this->spreadsheet->getSheetByName($range[0])) { $extractedRange = str_replace('$', '', $range[1]); - $spreadsheet->addNamedRange(new NamedRange($name, $worksheet, $extractedRange)); + $this->spreadsheet->addNamedRange(new NamedRange($name, $worksheet, $extractedRange)); } } } + } - // Return - return $spreadsheet; + 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; + } + + private static function addStyle(array &$styleArray, string $key, string $value): void + { + if (array_key_exists($value, self::$mappings[$key])) { + $styleArray[$key] = self::$mappings[$key][$value]; + } + } + + 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]; + } } private static function parseBorderAttributes($borderAttributes) @@ -800,64 +823,7 @@ class Gnumeric extends BaseReader $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; - } + self::addStyle($styleArray, 'borderStyle', $borderAttributes['Style']); return $styleArray; } @@ -879,4 +845,23 @@ class Gnumeric extends BaseReader 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); + } + } } diff --git a/tests/PhpSpreadsheetTests/IOFactoryTest.php b/tests/PhpSpreadsheetTests/IOFactoryTest.php index 983ba35e..906375bd 100644 --- a/tests/PhpSpreadsheetTests/IOFactoryTest.php +++ b/tests/PhpSpreadsheetTests/IOFactoryTest.php @@ -123,6 +123,7 @@ class IOFactoryTest extends TestCase return [ ['samples/templates/26template.xlsx', 'Xlsx', Reader\Xlsx::class], ['samples/templates/GnumericTest.gnumeric', 'Gnumeric', Reader\Gnumeric::class], + ['samples/templates/old.gnumeric', 'Gnumeric', Reader\Gnumeric::class], ['samples/templates/30template.xls', 'Xls', Reader\Xls::class], ['samples/templates/OOCalcTest.ods', 'Ods', Reader\Ods::class], ['samples/templates/SylkTest.slk', 'Slk', Reader\Slk::class], diff --git a/tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericFilter.php b/tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericFilter.php new file mode 100644 index 00000000..0904e2d4 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericFilter.php @@ -0,0 +1,14 @@ +listWorksheetNames($filename); + self::assertCount(2, $names); + self::assertEquals('Sample Data', $names[0]); + self::assertEquals('Report Data', $names[1]); + } + + public function testListInfo(): void + { + $filename = __DIR__ + . '/../../../..' + . '/samples/templates/GnumericTest.gnumeric'; + $reader = new Gnumeric(); + $info = $reader->listWorksheetInfo($filename); + $expected = [ + [ + 'worksheetName' => 'Sample Data', + 'lastColumnLetter' => 'N', + 'lastColumnIndex' => 13, + 'totalRows' => 31, + 'totalColumns' => 14, + ], + [ + 'worksheetName' => 'Report Data', + 'lastColumnLetter' => 'K', + 'lastColumnIndex' => 10, + 'totalRows' => 65535, + 'totalColumns' => 11, + ], + ]; + self::assertEquals($expected, $info); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericLoadTest.php b/tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericLoadTest.php new file mode 100644 index 00000000..e24178e5 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericLoadTest.php @@ -0,0 +1,162 @@ +load($filename); + self::assertEquals(2, $spreadsheet->getSheetCount()); + + $sheet = $spreadsheet->getSheet(1); + self::assertEquals('Report Data', $sheet->getTitle()); + self::assertEquals('BCD', $sheet->getCell('A4')->getValue()); + $props = $spreadsheet->getProperties(); + self::assertEquals('Mark Baker', $props->getCreator()); + + $sheet = $spreadsheet->getSheet(0); + self::assertEquals('Sample Data', $sheet->getTitle()); + self::assertEquals('Test String 1', $sheet->getCell('A1')->getValue()); + self::assertEquals('FFFF0000', $sheet->getCell('A1')->getStyle()->getFont()->getColor()->getARGB()); + self::assertEquals(Font::UNDERLINE_SINGLE, $sheet->getCell('A3')->getStyle()->getFont()->getUnderline()); + self::assertEquals('Test with (") in string', $sheet->getCell('A4')->getValue()); + + self::assertEquals(22269, $sheet->getCell('A10')->getValue()); + self::assertEquals('dd/mm/yyyy', $sheet->getCell('A10')->getStyle()->getNumberFormat()->getFormatCode()); + self::assertEquals('19/12/1960', $sheet->getCell('A10')->getFormattedValue()); + self::assertEquals(1.5, $sheet->getCell('A11')->getValue()); + self::assertEquals('# ?0/??0', $sheet->getCell('A11')->getStyle()->getNumberFormat()->getFormatCode()); + // Same pattern, same value, different display in Gnumeric vs Excel + //self::assertEquals('1 1/2', $sheet->getCell('A11')->getFormattedValue()); + + self::assertEquals('=B1+C1', $sheet->getCell('H1')->getValue()); + self::assertEquals('=E2&F2', $sheet->getCell('J2')->getValue()); + self::assertEquals('=sum(C1:C4)', $sheet->getCell('I5')->getValue()); + + self::assertTrue($sheet->getCell('E1')->getStyle()->getFont()->getBold()); + self::assertTrue($sheet->getCell('E1')->getStyle()->getFont()->getItalic()); + + self::assertFalse($sheet->getCell('E2')->getStyle()->getFont()->getBold()); + self::assertFalse($sheet->getCell('E2')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_NONE, $sheet->getCell('E2')->getStyle()->getFont()->getUnderline()); + self::assertTrue($sheet->getCell('E3')->getStyle()->getFont()->getBold()); + self::assertFalse($sheet->getCell('E3')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_NONE, $sheet->getCell('E3')->getStyle()->getFont()->getUnderline()); + self::assertFalse($sheet->getCell('E4')->getStyle()->getFont()->getBold()); + self::assertTrue($sheet->getCell('E4')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_NONE, $sheet->getCell('E4')->getStyle()->getFont()->getUnderline()); + + self::assertTrue($sheet->getCell('F1')->getStyle()->getFont()->getBold()); + self::assertFalse($sheet->getCell('F1')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_NONE, $sheet->getCell('F1')->getStyle()->getFont()->getUnderline()); + self::assertFalse($sheet->getCell('F2')->getStyle()->getFont()->getBold()); + self::assertFalse($sheet->getCell('F2')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_NONE, $sheet->getCell('F2')->getStyle()->getFont()->getUnderline()); + self::assertTrue($sheet->getCell('F3')->getStyle()->getFont()->getBold()); + self::assertTrue($sheet->getCell('F3')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_NONE, $sheet->getCell('F3')->getStyle()->getFont()->getUnderline()); + self::assertFalse($sheet->getCell('F4')->getStyle()->getFont()->getBold()); + self::assertFalse($sheet->getCell('F4')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_NONE, $sheet->getCell('F4')->getStyle()->getFont()->getUnderline()); + + self::assertEquals(Border::BORDER_MEDIUM, $sheet->getCell('C10')->getStyle()->getBorders()->getTop()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C10')->getStyle()->getBorders()->getRight()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C10')->getStyle()->getBorders()->getBottom()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C10')->getStyle()->getBorders()->getLeft()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C12')->getStyle()->getBorders()->getTop()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C12')->getStyle()->getBorders()->getRight()->getBorderStyle()); + self::assertEquals(Border::BORDER_MEDIUM, $sheet->getCell('C12')->getStyle()->getBorders()->getBottom()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C12')->getStyle()->getBorders()->getLeft()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C14')->getStyle()->getBorders()->getTop()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C14')->getStyle()->getBorders()->getRight()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C14')->getStyle()->getBorders()->getBottom()->getBorderStyle()); + self::assertEquals(Border::BORDER_MEDIUM, $sheet->getCell('C14')->getStyle()->getBorders()->getLeft()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C16')->getStyle()->getBorders()->getTop()->getBorderStyle()); + self::assertEquals(Border::BORDER_MEDIUM, $sheet->getCell('C16')->getStyle()->getBorders()->getRight()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C16')->getStyle()->getBorders()->getBottom()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C16')->getStyle()->getBorders()->getLeft()->getBorderStyle()); + self::assertEquals(Border::BORDER_THICK, $sheet->getCell('C18')->getStyle()->getBorders()->getTop()->getBorderStyle()); + self::assertEquals(Color::COLOR_RED, $sheet->getCell('C18')->getStyle()->getBorders()->getTop()->getColor()->getARGB()); + self::assertEquals(Border::BORDER_THICK, $sheet->getCell('C18')->getStyle()->getBorders()->getRight()->getBorderStyle()); + self::assertEquals(Color::COLOR_YELLOW, $sheet->getCell('C18')->getStyle()->getBorders()->getRight()->getColor()->getARGB()); + self::assertEquals(Border::BORDER_THICK, $sheet->getCell('C18')->getStyle()->getBorders()->getRight()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C18')->getStyle()->getBorders()->getBottom()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C18')->getStyle()->getBorders()->getLeft()->getBorderStyle()); + + self::assertEquals(Fill::FILL_PATTERN_DARKHORIZONTAL, $sheet->getCell('K19')->getStyle()->getFill()->getFillType()); + self::assertEquals('FF00CCFF', $sheet->getCell('K19')->getStyle()->getFill()->getEndColor()->getARGB()); + self::assertEquals(Color::COLOR_BLUE, $sheet->getCell('K19')->getStyle()->getFill()->getStartColor()->getARGB()); + self::assertEquals(Fill::FILL_PATTERN_GRAY0625, $sheet->getCell('L19')->getStyle()->getFill()->getFillType()); + self::assertEquals(Color::COLOR_RED, $sheet->getCell('L19')->getStyle()->getFill()->getEndColor()->getARGB()); + self::assertEquals(Color::COLOR_YELLOW, $sheet->getCell('L19')->getStyle()->getFill()->getStartColor()->getARGB()); + self::assertEquals(Fill::FILL_SOLID, $sheet->getCell('K3')->getStyle()->getFill()->getFillType()); + self::assertEquals(Color::COLOR_RED, $sheet->getCell('K3')->getStyle()->getFill()->getStartColor()->getARGB()); + + self::assertEquals(45, $sheet->getCell('E22')->getStyle()->getAlignment()->getTextRotation()); + self::assertEquals(-90, $sheet->getCell('G22')->getStyle()->getAlignment()->getTextRotation()); + self::assertEquals(Border::BORDER_DOUBLE, $sheet->getCell('N13')->getStyle()->getBorders()->getBottom()->getBorderStyle()); + + self::assertEquals(Borders::DIAGONAL_BOTH, $sheet->getCell('E18')->getStyle()->getBorders()->getDiagonalDirection()); + self::assertEquals(Borders::DIAGONAL_DOWN, $sheet->getCell('I18')->getStyle()->getBorders()->getDiagonalDirection()); + self::assertEquals(Borders::DIAGONAL_UP, $sheet->getCell('J18')->getStyle()->getBorders()->getDiagonalDirection()); + self::assertEquals(Font::UNDERLINE_DOUBLE, $sheet->getCell('A24')->getStyle()->getFont()->getUnderline()); + self::assertTrue($sheet->getCell('B23')->getStyle()->getFont()->getSubScript()); + self::assertTrue($sheet->getCell('B24')->getStyle()->getFont()->getSuperScript()); + self::assertFalse($sheet->getRowDimension(30)->getVisible()); + } + + public function testLoadFilter(): void + { + $filename = __DIR__ + . '/../../../..' + . '/samples/templates/GnumericTest.gnumeric'; + $reader = new Gnumeric(); + $filter = new GnumericFilter(); + $reader->setReadFilter($filter); + $spreadsheet = $reader->load($filename); + self::assertEquals(2, $spreadsheet->getSheetCount()); + $sheet = $spreadsheet->getSheet(1); + self::assertEquals('Report Data', $sheet->getTitle()); + self::assertEquals('', $sheet->getCell('A4')->getValue()); + $props = $spreadsheet->getProperties(); + self::assertEquals('Mark Baker', $props->getCreator()); + } + + public function testLoadOld(): void + { + $filename = __DIR__ + . '/../../../..' + . '/samples/templates/old.gnumeric'; + $reader = new Gnumeric(); + $spreadsheet = $reader->load($filename); + $props = $spreadsheet->getProperties(); + self::assertEquals('David Gilbert', $props->getCreator()); + } + + public function testLoadSelectedSheets(): void + { + $filename = __DIR__ + . '/../../../..' + . '/samples/templates/GnumericTest.gnumeric'; + $reader = new Gnumeric(); + $reader->setLoadSheetsOnly(['Unknown Sheet', 'Report Data']); + $spreadsheet = $reader->load($filename); + self::assertEquals(1, $spreadsheet->getSheetCount()); + $sheet = $spreadsheet->getSheet(0); + self::assertEquals('Report Data', $sheet->getTitle()); + self::assertEquals('Third Heading', $sheet->getCell('C2')->getValue()); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericStylesTest.php b/tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericStylesTest.php new file mode 100644 index 00000000..fd8d7800 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericStylesTest.php @@ -0,0 +1,269 @@ + $val) { + $covered[$key] = 0; + } + $tests = $this->providerBorderStyle(); + foreach ($tests as $test) { + $covered[$test[0]] = 1; + } + foreach ($covered as $key => $val) { + self::assertEquals(1, $val, "Borderstyle $key not tested"); + } + } + + /** + * @dataProvider providerfillType + */ + public function testFillType(string $style, string $expectedResult): void + { + $styles = Gnumeric::gnumericMappings(); + $borders = $styles['fillType']; + self::assertEquals($expectedResult, $borders[$style]); + } + + public function testFillTypeCoverage(): void + { + $styles = Gnumeric::gnumericMappings(); + $expected = $styles['fillType']; + $covered = []; + foreach ($expected as $key => $val) { + $covered[$key] = 0; + } + $tests = $this->providerfillType(); + foreach ($tests as $test) { + $covered[$test[0]] = 1; + } + foreach ($covered as $key => $val) { + self::assertEquals(1, $val, "fillType $key not tested"); + } + } + + /** + * @dataProvider providerHorizontal + */ + public function testHorizontal(string $style, string $expectedResult): void + { + $styles = Gnumeric::gnumericMappings(); + $borders = $styles['horizontal']; + self::assertEquals($expectedResult, $borders[$style]); + } + + public function testHorizontalCoverage(): void + { + $styles = Gnumeric::gnumericMappings(); + $expected = $styles['horizontal']; + $covered = []; + foreach ($expected as $key => $val) { + $covered[$key] = 0; + } + $tests = $this->providerHorizontal(); + foreach ($tests as $test) { + $covered[$test[0]] = 1; + } + foreach ($covered as $key => $val) { + self::assertEquals(1, $val, "horizontal $key not tested"); + } + } + + /** + * @dataProvider providerunderline + */ + public function testUnderline(string $style, string $expectedResult): void + { + $styles = Gnumeric::gnumericMappings(); + $borders = $styles['underline']; + self::assertEquals($expectedResult, $borders[$style]); + } + + public function testUnderlineCoverage(): void + { + $styles = Gnumeric::gnumericMappings(); + $expected = $styles['underline']; + $covered = []; + foreach ($expected as $key => $val) { + $covered[$key] = 0; + } + $tests = $this->providerUnderline(); + foreach ($tests as $test) { + $covered[$test[0]] = 1; + } + foreach ($covered as $key => $val) { + self::assertEquals(1, $val, "underline $key not tested"); + } + } + + /** + * @dataProvider providerVertical + */ + public function testVertical(string $style, string $expectedResult): void + { + $styles = Gnumeric::gnumericMappings(); + $borders = $styles['vertical']; + self::assertEquals($expectedResult, $borders[$style]); + } + + public function testVerticalCoverage(): void + { + $styles = Gnumeric::gnumericMappings(); + $expected = $styles['vertical']; + $covered = []; + foreach ($expected as $key => $val) { + $covered[$key] = 0; + } + $tests = $this->providerVertical(); + foreach ($tests as $test) { + $covered[$test[0]] = 1; + } + foreach ($covered as $key => $val) { + self::assertEquals(1, $val, "vertical $key not tested"); + } + } + + /** + * @dataProvider providerDataType + */ + public function testDataType(string $style, string $expectedResult): void + { + $styles = Gnumeric::gnumericMappings(); + $borders = $styles['dataType']; + self::assertEquals($expectedResult, $borders[$style]); + } + + public function testDataTypeCoverage(): void + { + $styles = Gnumeric::gnumericMappings(); + $expected = $styles['dataType']; + self::assertArrayNotHasKey('70', $expected); + self::assertArrayNotHasKey('80', $expected); + $covered = []; + foreach ($expected as $key => $val) { + $covered[$key] = 0; + } + $tests = $this->providerDataType(); + foreach ($tests as $test) { + $covered[$test[0]] = 1; + } + foreach ($covered as $key => $val) { + self::assertEquals(1, $val, "dataType $key not tested"); + } + } + + public function providerBorderStyle(): array + { + return [ + ['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], + ]; + } + + public function providerFillType(): array + { + return [ + ['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], + ['8', Fill::FILL_PATTERN_DARKVERTICAL], + ['9', Fill::FILL_PATTERN_DARKDOWN], + ['10', Fill::FILL_PATTERN_DARKUP], + ['11', Fill::FILL_PATTERN_DARKGRID], + ['12', Fill::FILL_PATTERN_DARKTRELLIS], + ['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], + ['18', Fill::FILL_PATTERN_LIGHTTRELLIS], + ]; + } + + public function providerHorizontal(): array + { + return [ + ['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], + ]; + } + + public function providerUnderline(): array + { + return [ + ['1', Font::UNDERLINE_SINGLE], + ['2', Font::UNDERLINE_DOUBLE], + ['3', Font::UNDERLINE_SINGLEACCOUNTING], + ['4', Font::UNDERLINE_DOUBLEACCOUNTING], + ]; + } + + public function providerVertical(): array + { + return [ + ['1', Alignment::VERTICAL_TOP], + ['2', Alignment::VERTICAL_BOTTOM], + ['4', Alignment::VERTICAL_CENTER], + ['8', Alignment::VERTICAL_JUSTIFY], + ]; + } + + public function providerDataType(): array + { + return [ + ['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 + ]; + } +}