diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 6390e0fc21..f96562615c 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -2825,91 +2825,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Reader/Csv/Delimiter.php - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:\\$referenceHelper has no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Gnumeric.php - - - - message: "#^Parameter \\#1 \\$fp of function fread expects resource, resource\\|false given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Gnumeric.php - - - - message: "#^Parameter \\#1 \\$fp of function fclose expects resource, resource\\|false given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Gnumeric.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:\\$mappings has no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Gnumeric.php - - - - message: "#^Offset 'No' does not exist on SimpleXMLElement\\|null\\.$#" - count: 2 - path: src/PhpSpreadsheet/Reader/Gnumeric.php - - - - message: "#^Offset 'Unit' does not exist on SimpleXMLElement\\|null\\.$#" - count: 2 - path: src/PhpSpreadsheet/Reader/Gnumeric.php - - - - message: "#^Cannot call method setWidth\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" - count: 3 - path: src/PhpSpreadsheet/Reader/Gnumeric.php - - - - message: "#^Cannot call method setVisible\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Gnumeric.php - - - - message: "#^Offset 'DefaultSizePts' does not exist on SimpleXMLElement\\|null\\.$#" - count: 2 - path: src/PhpSpreadsheet/Reader/Gnumeric.php - - - - message: "#^Cannot call method setRowHeight\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" - count: 2 - path: src/PhpSpreadsheet/Reader/Gnumeric.php - - - - message: "#^Cannot call method setVisible\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Gnumeric.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:parseBorderAttributes\\(\\) has no return typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Gnumeric.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:parseBorderAttributes\\(\\) has parameter \\$borderAttributes with no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Gnumeric.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:parseRichText\\(\\) has no return typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Gnumeric.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:parseRichText\\(\\) has parameter \\$is with no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Gnumeric.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:parseGnumericColour\\(\\) has no return typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Gnumeric.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:parseGnumericColour\\(\\) has parameter \\$gnmColour with no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Gnumeric.php - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:\\$rowspan has no typehint specified\\.$#" count: 1 @@ -8325,11 +8240,6 @@ parameters: count: 1 path: tests/PhpSpreadsheetTests/Reader/CsvTest.php - - - message: "#^Cannot call method getVisible\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" - count: 1 - path: tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericLoadTest.php - - message: "#^Cannot call method getRowHeight\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" count: 2 diff --git a/src/PhpSpreadsheet/Reader/Gnumeric.php b/src/PhpSpreadsheet/Reader/Gnumeric.php index d3cdf1b002..7086cc6358 100644 --- a/src/PhpSpreadsheet/Reader/Gnumeric.php +++ b/src/PhpSpreadsheet/Reader/Gnumeric.php @@ -25,7 +25,19 @@ class Gnumeric extends BaseReader { - private const UOM_CONVERSION_POINTS_TO_CENTIMETERS = 0.03527777778; + const NAMESPACE_GNM = 'http://www.gnumeric.org/v10.dtd'; // gmr in old sheets + + const NAMESPACE_XSI = 'http://www.w3.org/2001/XMLSchema-instance'; + + const NAMESPACE_OFFICE = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'; + + const NAMESPACE_XLINK = 'http://www.w3.org/1999/xlink'; + + const NAMESPACE_DC = 'http://purl.org/dc/elements/1.1/'; + + const NAMESPACE_META = 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'; + + const NAMESPACE_OOO = 'http://openoffice.org/2004/office'; /** * Shared Expressions. @@ -41,16 +53,9 @@ class Gnumeric extends BaseReader */ private $spreadsheet; + /** @var ReferenceHelper */ 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. */ @@ -77,8 +82,10 @@ public function canRead($pFilename) if (function_exists('gzread')) { // Read signature data (first 3 bytes) $fh = fopen($pFilename, 'rb'); - $data = fread($fh, 2); - fclose($fh); + if ($fh !== false) { + $data = fread($fh, 2); + fclose($fh); + } } return $data == chr(0x1F) . chr(0x8B); @@ -86,7 +93,7 @@ public function canRead($pFilename) private static function matchXml(string $name, string $field): bool { - return 1 === preg_match("/^(gnm|gmr):$field$/", $name); + return 1 === preg_match("/:$field$/", $name); } /** @@ -188,6 +195,7 @@ private function gzfileGetContents($filename) return $data; } + /** @var array */ private static $mappings = [ 'borderStyle' => [ '0' => Border::BORDER_NONE, @@ -266,7 +274,7 @@ public static function gnumericMappings(): array private function processComments(SimpleXMLElement $sheet): void { if ((!$this->readDataOnly) && (isset($sheet->Objects))) { - foreach ($sheet->Objects->children($this->gnm, true) as $key => $comment) { + foreach ($sheet->Objects->children(self::NAMESPACE_GNM) as $key => $comment) { $commentAttributes = $comment->attributes(); // Only comment objects are handled at the moment if ($commentAttributes->Text) { @@ -276,6 +284,14 @@ private function processComments(SimpleXMLElement $sheet): void } } + /** + * @param mixed $value + */ + private function testSimpleXml($value): SimpleXMLElement + { + return ($value instanceof SimpleXMLElement) ? $value : new SimpleXMLElement(''); + } + /** * Loads Spreadsheet from file. * @@ -304,12 +320,10 @@ public function loadIntoExisting(string $pFilename, Spreadsheet $spreadsheet): S $gFileData = $this->gzfileGetContents($pFilename); $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'; + $xml = self::testSimpleXml($xml2); - $gnmXML = $xml->children($namespacesMeta[$this->gnm]); - (new Properties($this->spreadsheet))->readProperties($xml, $gnmXML, $namespacesMeta); + $gnmXML = $xml->children(self::NAMESPACE_GNM); + (new Properties($this->spreadsheet))->readProperties($xml, $gnmXML); $worksheetID = 0; foreach ($gnmXML->Sheets->Sheet as $sheet) { @@ -329,7 +343,7 @@ public function loadIntoExisting(string $pFilename, Spreadsheet $spreadsheet): S $this->spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false); if (!$this->readDataOnly) { - (new PageSetup($this->spreadsheet, $this->gnm)) + (new PageSetup($this->spreadsheet)) ->printInformation($sheet) ->sheetMargins($sheet); } @@ -510,21 +524,37 @@ private function processMergedCells(SimpleXMLElement $sheet): void } } + private function setColumnWidth(int $c, float $defaultWidth): void + { + $colDim = $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1)); + if ($colDim !== null) { + $colDim->setWidth($defaultWidth); + } + } + + private function setColumnInvisible(int $c): void + { + $colDim = $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1)); + if ($colDim !== null) { + $colDim->setVisible(false); + } + } + private function processColumnLoop(int $c, int $maxCol, SimpleXMLElement $columnOverride, float $defaultWidth): int { - $columnAttributes = $columnOverride->attributes(); + $columnAttributes = self::testSimpleXml($columnOverride->attributes()); $column = $columnAttributes['No']; $columnWidth = ((float) $columnAttributes['Unit']) / 5.4; $hidden = (isset($columnAttributes['Hidden'])) && ((string) $columnAttributes['Hidden'] == '1'); $columnCount = (int) ($columnAttributes['Count'] ?? 1); while ($c < $column) { - $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth); + $this->setColumnWidth($c, $defaultWidth); ++$c; } while (($c < ($column + $columnCount)) && ($c <= $maxCol)) { - $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($columnWidth); + $this->setColumnWidth($c, $columnWidth); if ($hidden) { - $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setVisible(false); + self::setColumnInvisible($c); } ++$c; } @@ -536,35 +566,54 @@ private function processColumnWidths(SimpleXMLElement $sheet, int $maxCol): void { if ((!$this->readDataOnly) && (isset($sheet->Cols))) { // Column Widths + $defaultWidth = 0; $columnAttributes = $sheet->Cols->attributes(); - $defaultWidth = $columnAttributes['DefaultSizePts'] / 5.4; + if ($columnAttributes !== null) { + $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); + $this->setColumnWidth($c, $defaultWidth); ++$c; } } } + private function setRowHeight(int $r, float $defaultHeight): void + { + $rowDim = $this->spreadsheet->getActiveSheet()->getRowDimension($r); + if ($rowDim !== null) { + $rowDim->setRowHeight($defaultHeight); + } + } + + private function setRowInvisible(int $r): void + { + $rowDim = $this->spreadsheet->getActiveSheet()->getRowDimension($r); + if ($rowDim !== null) { + $rowDim->setVisible(false); + } + } + private function processRowLoop(int $r, int $maxRow, SimpleXMLElement $rowOverride, float $defaultHeight): int { - $rowAttributes = $rowOverride->attributes(); + $rowAttributes = self::testSimpleXml($rowOverride->attributes()); $row = $rowAttributes['No']; $rowHeight = (float) $rowAttributes['Unit']; $hidden = (isset($rowAttributes['Hidden'])) && ((string) $rowAttributes['Hidden'] == '1'); $rowCount = (int) ($rowAttributes['Count'] ?? 1); while ($r < $row) { ++$r; - $this->spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($defaultHeight); + $this->setRowHeight($r, $defaultHeight); } while (($r < ($row + $rowCount)) && ($r < $maxRow)) { ++$r; - $this->spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($rowHeight); + $this->setRowHeight($r, $rowHeight); if ($hidden) { - $this->spreadsheet->getActiveSheet()->getRowDimension($r)->setVisible(false); + $this->setRowInvisible($r); } } @@ -575,8 +624,11 @@ private function processRowHeights(SimpleXMLElement $sheet, int $maxRow): void { if ((!$this->readDataOnly) && (isset($sheet->Rows))) { // Row Heights + $defaultHeight = 0; $rowAttributes = $sheet->Rows->attributes(); - $defaultHeight = (float) $rowAttributes['DefaultSizePts']; + if ($rowAttributes !== null) { + $defaultHeight = (float) $rowAttributes['DefaultSizePts']; + } $r = 0; foreach ($sheet->Rows->RowInfo as $rowOverride) { @@ -639,19 +691,21 @@ private static function addStyle2(array &$styleArray, string $key1, string $key, } } - private static function parseBorderAttributes($borderAttributes) + private static function parseBorderAttributes(?SimpleXMLElement $borderAttributes): array { $styleArray = []; - if (isset($borderAttributes['Color'])) { - $styleArray['color']['rgb'] = self::parseGnumericColour($borderAttributes['Color']); - } + if ($borderAttributes !== null) { + if (isset($borderAttributes['Color'])) { + $styleArray['color']['rgb'] = self::parseGnumericColour($borderAttributes['Color']); + } - self::addStyle($styleArray, 'borderStyle', $borderAttributes['Style']); + self::addStyle($styleArray, 'borderStyle', $borderAttributes['Style']); + } return $styleArray; } - private function parseRichText($is) + private function parseRichText(string $is): RichText { $value = new RichText(); $value->createText($is); @@ -659,7 +713,7 @@ private function parseRichText($is) return $value; } - private static function parseGnumericColour($gnmColour) + private static function parseGnumericColour(string $gnmColour): string { [$gnmR, $gnmG, $gnmB] = explode(':', $gnmColour); $gnmR = substr(str_pad($gnmR, 4, '0', STR_PAD_RIGHT), 0, 2); diff --git a/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php b/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php index 0fe7300593..accc27160f 100644 --- a/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php +++ b/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Gnumeric; +use PhpOffice\PhpSpreadsheet\Reader\Gnumeric; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\PageMargins; use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup as WorksheetPageSetup; @@ -14,15 +15,9 @@ class PageSetup */ private $spreadsheet; - /** - * @var string - */ - private $gnm; - - public function __construct(Spreadsheet $spreadsheet, string $gnm) + public function __construct(Spreadsheet $spreadsheet) { $this->spreadsheet = $spreadsheet; - $this->gnm = $gnm; } public function printInformation(SimpleXMLElement $sheet): self @@ -68,7 +63,7 @@ public function sheetMargins(SimpleXMLElement $sheet): self private function buildMarginSet(SimpleXMLElement $sheet, array $marginSet): array { - foreach ($sheet->PrintInformation->Margins->children($this->gnm, true) as $key => $margin) { + foreach ($sheet->PrintInformation->Margins->children(Gnumeric::NAMESPACE_GNM) as $key => $margin) { $marginAttributes = $margin->attributes(); $marginSize = ($marginAttributes['Points']) ?? 72; // Default is 72pt // Convert value in points to inches diff --git a/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php b/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php index 16d9c2e082..1696f06940 100644 --- a/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php +++ b/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Gnumeric; +use PhpOffice\PhpSpreadsheet\Reader\Gnumeric; use PhpOffice\PhpSpreadsheet\Spreadsheet; use SimpleXMLElement; @@ -91,74 +92,72 @@ private function docPropertiesDC(SimpleXMLElement $officePropertyDC): void } } - private function docPropertiesMeta(SimpleXMLElement $officePropertyMeta, array $namespacesMeta): void + private function docPropertiesMeta(SimpleXMLElement $officePropertyMeta): void { $docProps = $this->spreadsheet->getProperties(); foreach ($officePropertyMeta as $propertyName => $propertyValue) { - if ($propertyValue === null) { - continue; + if ($propertyValue !== null) { + $attributes = $propertyValue->attributes(Gnumeric::NAMESPACE_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); + $creationDate = $creationDate === false ? time() : $creationDate; + $docProps->setCreated($creationDate); + $docProps->setModified($creationDate); + + break; + case 'user-defined': + [, $attrName] = explode(':', $attributes['name']); + self::userDefinedProperties($attrName, $propertyValue); + + break; + } } + } + } - $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); - $creationDate = $creationDate === false ? time() : $creationDate; - $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); + private function userDefinedProperties(string $attrName, string $propertyValue): void + { + $docProps = $this->spreadsheet->getProperties(); + switch ($attrName) { + case 'publisher': + $docProps->setCompany($propertyValue); - break; - case 'manager': - $docProps->setManager($propertyValue); + break; + case 'category': + $docProps->setCategory($propertyValue); - break; - } + break; + case 'manager': + $docProps->setManager($propertyValue); - break; - } + break; } } - public function readProperties(SimpleXMLElement $xml, SimpleXMLElement $gnmXML, array $namespacesMeta): void + public function readProperties(SimpleXMLElement $xml, SimpleXMLElement $gnmXML): void { - if (isset($namespacesMeta['office'])) { - $officeXML = $xml->children($namespacesMeta['office']); + $officeXML = $xml->children(Gnumeric::NAMESPACE_OFFICE); + if (!empty($officeXML)) { $officeDocXML = $officeXML->{'document-meta'}; $officeDocMetaXML = $officeDocXML->meta; foreach ($officeDocMetaXML as $officePropertyData) { - $officePropertyDC = []; - if (isset($namespacesMeta['dc'])) { - $officePropertyDC = $officePropertyData->children($namespacesMeta['dc']); - } + $officePropertyDC = $officePropertyData->children(Gnumeric::NAMESPACE_DC); $this->docPropertiesDC($officePropertyDC); - $officePropertyMeta = []; - if (isset($namespacesMeta['meta'])) { - $officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']); - } - $this->docPropertiesMeta($officePropertyMeta, $namespacesMeta); + $officePropertyMeta = $officePropertyData->children(Gnumeric::NAMESPACE_META); + $this->docPropertiesMeta($officePropertyMeta); } } elseif (isset($gnmXML->Summary)) { $this->docPropertiesOld($gnmXML); diff --git a/tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericLoadTest.php b/tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericLoadTest.php index e24178e558..65511153b8 100644 --- a/tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericLoadTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericLoadTest.php @@ -115,7 +115,12 @@ public function testLoad(): void 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()); + $rowDimension = $sheet->getRowDimension(30); + if ($rowDimension === null) { + self::fail('Unable to get RowDimension for row 30)'); + } else { + self::assertFalse($rowDimension->getVisible()); + } } public function testLoadFilter(): void