diff --git a/samples/templates/GnumericTest.gnumeric b/samples/templates/GnumericTest.gnumeric index ea2fac37..0493e762 100644 Binary files a/samples/templates/GnumericTest.gnumeric and b/samples/templates/GnumericTest.gnumeric differ diff --git a/samples/templates/old.gnumeric b/samples/templates/old.gnumeric new file mode 100644 index 00000000..36b84902 Binary files /dev/null and b/samples/templates/old.gnumeric differ 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 + ]; + } +}