diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 5e19caeab6..3538d091c1 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -2925,21 +2925,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Reader/Xls/RC4.php - - - message: "#^Cannot access property \\$Relationship on SimpleXMLElement\\|false\\.$#" - count: 12 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot access property \\$sheets on SimpleXMLElement\\|false\\.$#" - count: 6 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot call method registerXPathNamespace\\(\\) on SimpleXMLElement\\|false\\.$#" - count: 4 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToBoolean\\(\\) has no return typehint specified\\.$#" count: 1 @@ -3010,86 +2995,16 @@ parameters: count: 1 path: src/PhpSpreadsheet/Reader/Xlsx.php - - - message: "#^Offset 'name' does not exist on SimpleXMLElement\\|null\\.$#" - count: 2 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot call method xpath\\(\\) on SimpleXMLElement\\|false\\.$#" - count: 4 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - message: "#^Parameter \\#1 \\$is of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:parseRichText\\(\\) expects SimpleXMLElement\\|null, object given\\.$#" count: 1 path: src/PhpSpreadsheet/Reader/Xlsx.php - - - message: "#^Parameter \\#1 \\$styleXml of class PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles constructor expects SimpleXMLElement, SimpleXMLElement\\|false given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot access property \\$workbookPr on SimpleXMLElement\\|false\\.$#" - count: 2 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Parameter \\#2 \\$xmlWorkbook of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readProtection\\(\\) expects SimpleXMLElement, SimpleXMLElement\\|false given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Parameter \\#1 \\$relsWorksheet of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Hyperlinks\\:\\:readHyperlinks\\(\\) expects SimpleXMLElement, SimpleXMLElement\\|false given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot access property \\$authors on SimpleXMLElement\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot access property \\$commentList on SimpleXMLElement\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" - count: 2 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - message: "#^Negated boolean expression is always true\\.$#" count: 1 path: src/PhpSpreadsheet/Reader/Xlsx.php - - - message: "#^Cannot access property \\$drawing on SimpleXMLElement\\|false\\.$#" - count: 2 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot call method children\\(\\) on SimpleXMLElement\\|false\\.$#" - count: 2 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot call method count\\(\\) on SimpleXMLElement\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot call method asXML\\(\\) on SimpleXMLElement\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot access property \\$definedNames on SimpleXMLElement\\|false\\.$#" - count: 4 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" count: 1 @@ -3105,26 +3020,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Reader/Xlsx.php - - - message: "#^Cannot access property \\$bookViews on SimpleXMLElement\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot access property \\$Default on SimpleXMLElement\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot access property \\$Override on SimpleXMLElement\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Parameter \\#1 \\$chartElements of static method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:readChart\\(\\) expects SimpleXMLElement, SimpleXMLElement\\|false given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - message: "#^Cannot call method addChart\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" count: 1 @@ -3265,11 +3160,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Reader/Xlsx.php - - - message: "#^Offset 'id' does not exist on SimpleXMLElement\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readFormControlProperties\\(\\) has parameter \\$dir with no typehint specified\\.$#" count: 1 @@ -3700,66 +3590,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Properties\\:\\:\\$securityScanner has no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Properties\\:\\:\\$docProps has no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Properties\\:\\:extractPropertyData\\(\\) has no return typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Properties\\:\\:extractPropertyData\\(\\) has parameter \\$propertyData with no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Properties\\:\\:readCoreProperties\\(\\) has parameter \\$propertyData with no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php - - - - message: "#^Call to an undefined method object\\:\\:registerXPathNamespace\\(\\)\\.$#" - count: 3 - path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php - - - - message: "#^Call to an undefined method object\\:\\:xpath\\(\\)\\.$#" - count: 9 - path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Properties\\:\\:readExtendedProperties\\(\\) has parameter \\$propertyData with no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Properties\\:\\:readCustomProperties\\(\\) has parameter \\$propertyData with no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php - - - - message: "#^Argument of an invalid type object supplied for foreach, only iterables are supported\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Properties\\:\\:getArrayItem\\(\\) has no return typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Properties\\:\\:getArrayItem\\(\\) has parameter \\$key with no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\SheetViewOptions\\:\\:\\$worksheet has no typehint specified\\.$#" count: 1 @@ -3770,16 +3600,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\SheetViews\\:\\:\\$sheetViewXml has no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\SheetViews\\:\\:\\$worksheet has no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:\\$styles has no typehint specified\\.$#" count: 1 @@ -3810,11 +3630,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php - - - message: "#^Cannot call method count\\(\\) on SimpleXMLElement\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:readStyle\\(\\) has parameter \\$style with no typehint specified\\.$#" count: 1 @@ -3925,11 +3740,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/ReferenceHelper.php - - - message: "#^Parameter \\#1 \\$pCellRange of method PhpOffice\\\\PhpSpreadsheet\\\\ReferenceHelper\\:\\:updateCellReference\\(\\) expects string, string\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/ReferenceHelper.php - - message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|null given\\.$#" count: 1 @@ -5485,11 +5295,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Worksheet/Worksheet.php - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:getFreezePane\\(\\) should return string but returns string\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Worksheet/Worksheet.php - - message: "#^Parameter \\#1 \\$row of method PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Cells\\:\\:removeRow\\(\\) expects string, int given\\.$#" count: 2 diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 50b2a7095f..00d86f00d8 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -13,6 +13,7 @@ use PhpOffice\PhpSpreadsheet\Reader\Xlsx\ConditionalStyles; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\DataValidations; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Hyperlinks; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\PageSetup; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Properties as PropertyReader; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViewOptions; @@ -54,6 +55,11 @@ class Xlsx extends BaseReader */ private static $theme = null; + /** + * @var ZipArchive + */ + private $zip; + /** * Create a new Xlsx Reader instance. */ @@ -76,10 +82,10 @@ public function canRead($pFilename) File::assertFile($pFilename); $result = false; - $zip = new ZipArchive(); + $this->zip = $zip = new ZipArchive(); if ($zip->open($pFilename) === true) { - $workbookBasename = $this->getWorkbookBaseName($zip); + [$workbookBasename] = $this->getWorkbookBaseName(); $result = !empty($workbookBasename); $zip->close(); @@ -88,6 +94,71 @@ public function canRead($pFilename) return $result; } + /** + * @param mixed $value + */ + public static function testSimpleXml($value): SimpleXMLElement + { + return ($value instanceof SimpleXMLElement) ? $value : new SimpleXMLElement(''); + } + + public static function getAttributes(?SimpleXMLElement $value, string $ns = ''): SimpleXMLElement + { + return self::testSimpleXml($value === null ? $value : $value->attributes($ns)); + } + + // Phpstan thinks, correctly, that xpath can return false. + // Scrutinizer thinks it can't. + // Sigh. + private static function xpathNoFalse(SimpleXmlElement $sxml, string $path): array + { + return self::falseToArray($sxml->xpath($path)); + } + + /** + * @param mixed $value + */ + public static function falseToArray($value): array + { + return is_array($value) ? $value : []; + } + + private function loadZip(string $filename, string $ns = ''): SimpleXMLElement + { + $contents = $this->getFromZipArchive($this->zip, $filename); + $rels = simplexml_load_string( + $this->securityScanner->scan($contents), + 'SimpleXMLElement', + Settings::getLibXmlLoaderOptions(), + $ns + ); + + return self::testSimpleXml($rels); + } + + // This function is just to identify cases where I'm not sure + // why empty namespace is required. + private function loadZipNonamespace(string $filename, string $ns): SimpleXMLElement + { + $contents = $this->getFromZipArchive($this->zip, $filename); + $rels = simplexml_load_string( + $this->securityScanner->scan($contents), + 'SimpleXMLElement', + Settings::getLibXmlLoaderOptions(), + ($ns === '' ? $ns : '') + ); + + return self::testSimpleXml($rels); + } + + private const REL_TO_MAIN = [ + Namespaces::PURL_OFFICE_DOCUMENT => Namespaces::PURL_MAIN, + ]; + + private const REL_TO_DRAWING = [ + Namespaces::PURL_RELATIONSHIPS => Namespaces::PURL_DRAWING, + ]; + /** * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object. * @@ -101,28 +172,24 @@ public function listWorksheetNames($pFilename) $worksheetNames = []; - $zip = new ZipArchive(); + $this->zip = $zip = new ZipArchive(); $zip->open($pFilename); // The files we're looking at here are small enough that simpleXML is more efficient than XMLReader - //~ http://schemas.openxmlformats.org/package/2006/relationships"); - $rels = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels')) - ); - foreach ($rels->Relationship as $rel) { - switch ($rel['Type']) { - case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument': - //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" - $xmlWorkbook = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "{$rel['Target']}")) - ); - - if ($xmlWorkbook->sheets) { - foreach ($xmlWorkbook->sheets->sheet as $eleSheet) { - // Check if sheet should be skipped - $worksheetNames[] = (string) $eleSheet['name']; - } + $rels = $this->loadZip('_rels/.rels', Namespaces::RELATIONSHIPS); + foreach ($rels->Relationship as $relx) { + $rel = self::getAttributes($relx); + $relType = (string) $rel['Type']; + $mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN; + if ($mainNS !== '') { + $xmlWorkbook = $this->loadZip($rel['Target'], $mainNS); + + if ($xmlWorkbook->sheets) { + foreach ($xmlWorkbook->sheets->sheet as $eleSheet) { + // Check if sheet should be skipped + $worksheetNames[] = (string) self::getAttributes($eleSheet)['name']; } + } } } @@ -144,57 +211,42 @@ public function listWorksheetInfo($pFilename) $worksheetInfo = []; - $zip = new ZipArchive(); + $this->zip = $zip = new ZipArchive(); $zip->open($pFilename); - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $rels = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels')), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - foreach ($rels->Relationship as $rel) { - if ($rel['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument') { - $dir = dirname($rel['Target']); - - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorkbook = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, "$dir/_rels/" . basename($rel['Target']) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - $relsWorkbook->registerXPathNamespace('rel', 'http://schemas.openxmlformats.org/package/2006/relationships'); + $rels = $this->loadZip('_rels/.rels', Namespaces::RELATIONSHIPS); + foreach ($rels->Relationship as $relx) { + $rel = self::getAttributes($relx); + $relType = (string) $rel['Type']; + $mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN; + if ($mainNS !== '') { + $relTarget = (string) $rel['Target']; + $dir = dirname($relTarget); + $namespace = dirname($relType); + $relsWorkbook = $this->loadZip("$dir/_rels/" . basename($relTarget) . '.rels', ''); $worksheets = []; - foreach ($relsWorkbook->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet') { + foreach ($relsWorkbook->Relationship as $elex) { + $ele = self::getAttributes($elex); + if ((string) $ele['Type'] === "$namespace/worksheet") { $worksheets[(string) $ele['Id']] = $ele['Target']; } } - //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" - $xmlWorkbook = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, "{$rel['Target']}") - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $xmlWorkbook = $this->loadZip($relTarget, $mainNS); if ($xmlWorkbook->sheets) { - $dir = dirname($rel['Target']); + $dir = dirname($relTarget); /** @var SimpleXMLElement $eleSheet */ foreach ($xmlWorkbook->sheets->sheet as $eleSheet) { $tmpInfo = [ - 'worksheetName' => (string) $eleSheet['name'], + 'worksheetName' => (string) self::getAttributes($eleSheet)['name'], 'lastColumnLetter' => 'A', 'lastColumnIndex' => 0, 'totalRows' => 0, 'totalColumns' => 0, ]; - $fileWorksheet = $worksheets[(string) self::getArrayItem($eleSheet->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')]; + $fileWorksheet = $worksheets[(string) self::getArrayItem(self::getAttributes($eleSheet, $namespace), 'id')]; $fileWorksheetPath = strpos($fileWorksheet, '/') === 0 ? substr($fileWorksheet, 1) : "$dir/$fileWorksheet"; $xml = new XMLReader(); @@ -209,13 +261,14 @@ public function listWorksheetInfo($pFilename) $currCells = 0; while ($xml->read()) { - if ($xml->name == 'row' && $xml->nodeType == XMLReader::ELEMENT) { + if ($xml->localName == 'row' && $xml->nodeType == XMLReader::ELEMENT && $xml->namespaceURI === $mainNS) { $row = $xml->getAttribute('r'); $tmpInfo['totalRows'] = $row; $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells); $currCells = 0; - } elseif ($xml->name == 'c' && $xml->nodeType == XMLReader::ELEMENT) { - ++$currCells; + } elseif ($xml->localName == 'c' && $xml->nodeType == XMLReader::ELEMENT && $xml->namespaceURI === $mainNS) { + $cell = $xml->getAttribute('r'); + $currCells = $cell ? max($currCells, Coordinate::indexesFromString($cell)[0]) : ($currCells + 1); } } $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells); @@ -309,10 +362,8 @@ private function getFromZipArchive(ZipArchive $archive, $fileName = '') /** * Loads Spreadsheet from file. - * - * @return Spreadsheet */ - public function load(string $pFilename, int $flags = 0) + public function load(string $pFilename, int $flags = 0): Spreadsheet { File::assertFile($pFilename); $this->processFlags($flags); @@ -326,107 +377,93 @@ public function load(string $pFilename, int $flags = 0) } $unparsedLoadedData = []; - $zip = new ZipArchive(); + $this->zip = $zip = new ZipArchive(); $zip->open($pFilename); // Read the theme first, because we need the colour scheme when reading the styles - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $workbookBasename = $this->getWorkbookBaseName($zip); - $wbRels = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "xl/_rels/${workbookBasename}.rels")), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - foreach ($wbRels->Relationship as $rel) { + [$workbookBasename, $xmlNamespaceBase] = $this->getWorkbookBaseName(); + $wbRels = $this->loadZip("xl/_rels/${workbookBasename}.rels", Namespaces::RELATIONSHIPS); + foreach ($wbRels->Relationship as $relx) { + $rel = self::getAttributes($relx); switch ($rel['Type']) { - case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme': + case "$xmlNamespaceBase/theme": $themeOrderArray = ['lt1', 'dk1', 'lt2', 'dk2']; $themeOrderAdditional = count($themeOrderArray); - - $xmlTheme = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "xl/{$rel['Target']}")), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - if (is_object($xmlTheme)) { - $xmlThemeName = $xmlTheme->attributes(); - $xmlTheme = $xmlTheme->children('http://schemas.openxmlformats.org/drawingml/2006/main'); - $themeName = (string) $xmlThemeName['name']; - - $colourScheme = $xmlTheme->themeElements->clrScheme->attributes(); - $colourSchemeName = (string) $colourScheme['name']; - $colourScheme = $xmlTheme->themeElements->clrScheme->children('http://schemas.openxmlformats.org/drawingml/2006/main'); - - $themeColours = []; - foreach ($colourScheme as $k => $xmlColour) { - $themePos = array_search($k, $themeOrderArray); - if ($themePos === false) { - $themePos = $themeOrderAdditional++; - } - if (isset($xmlColour->sysClr)) { - $xmlColourData = $xmlColour->sysClr->attributes(); - $themeColours[$themePos] = $xmlColourData['lastClr']; - } elseif (isset($xmlColour->srgbClr)) { - $xmlColourData = $xmlColour->srgbClr->attributes(); - $themeColours[$themePos] = $xmlColourData['val']; - } + $drawingNS = self::REL_TO_DRAWING[$xmlNamespaceBase] ?? Namespaces::DRAWINGML; + + $xmlTheme = $this->loadZip("xl/{$rel['Target']}", $drawingNS); + $xmlThemeName = self::getAttributes($xmlTheme); + $xmlTheme = $xmlTheme->children($drawingNS); + $themeName = (string) $xmlThemeName['name']; + + $colourScheme = self::getAttributes($xmlTheme->themeElements->clrScheme); + $colourSchemeName = (string) $colourScheme['name']; + $colourScheme = $xmlTheme->themeElements->clrScheme->children($drawingNS); + + $themeColours = []; + foreach ($colourScheme as $k => $xmlColour) { + $themePos = array_search($k, $themeOrderArray); + if ($themePos === false) { + $themePos = $themeOrderAdditional++; + } + if (isset($xmlColour->sysClr)) { + $xmlColourData = self::getAttributes($xmlColour->sysClr); + $themeColours[$themePos] = $xmlColourData['lastClr']; + } elseif (isset($xmlColour->srgbClr)) { + $xmlColourData = self::getAttributes($xmlColour->srgbClr); + $themeColours[$themePos] = $xmlColourData['val']; } - self::$theme = new Xlsx\Theme($themeName, $colourSchemeName, $themeColours); } + self::$theme = new Xlsx\Theme($themeName, $colourSchemeName, $themeColours); break; } } - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $rels = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels')), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $rels = $this->loadZip('_rels/.rels', Namespaces::RELATIONSHIPS); $propertyReader = new PropertyReader($this->securityScanner, $excel->getProperties()); - foreach ($rels->Relationship as $rel) { - switch ($rel['Type']) { - case 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties': - $propertyReader->readCoreProperties($this->getFromZipArchive($zip, "{$rel['Target']}")); + foreach ($rels->Relationship as $relx) { + $rel = self::getAttributes($relx); + $relType = (string) $rel['Type']; + $mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN; + switch ($relType) { + case Namespaces::CORE_PROPERTIES: + $propertyReader->readCoreProperties($this->getFromZipArchive($zip, $rel['Target'])); break; - case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties': - $propertyReader->readExtendedProperties($this->getFromZipArchive($zip, "{$rel['Target']}")); + case "$xmlNamespaceBase/extended-properties": + $propertyReader->readExtendedProperties($this->getFromZipArchive($zip, $rel['Target'])); break; - case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties': - $propertyReader->readCustomProperties($this->getFromZipArchive($zip, "{$rel['Target']}")); + case "$xmlNamespaceBase/custom-properties": + $propertyReader->readCustomProperties($this->getFromZipArchive($zip, $rel['Target'])); break; //Ribbon - case 'http://schemas.microsoft.com/office/2006/relationships/ui/extensibility': + case Namespaces::EXTENSIBILITY: $customUI = $rel['Target']; if ($customUI !== null) { $this->readRibbon($excel, $customUI, $zip); } break; - case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument': + case "$xmlNamespaceBase/officeDocument": $dir = dirname($rel['Target']); - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorkbook = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/_rels/" . basename($rel['Target']) . '.rels')), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - $relsWorkbook->registerXPathNamespace('rel', 'http://schemas.openxmlformats.org/package/2006/relationships'); + + // Do not specify namespace in next stmt - do it in Xpath + $relsWorkbook = $this->loadZip("$dir/_rels/" . basename($rel['Target']) . '.rels', ''); + $relsWorkbook->registerXPathNamespace('rel', Namespaces::RELATIONSHIPS); $sharedStrings = []; - $xpath = self::getArrayItem($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings']")); + $relType = "rel:Relationship[@Type='" + //. Namespaces::SHARED_STRINGS + . "$xmlNamespaceBase/sharedStrings" + . "']"; + $xpath = self::getArrayItem($relsWorkbook->xpath($relType)); + if ($xpath) { - //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" - $xmlStrings = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$xpath[Target]")), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $xmlStrings = $this->loadZip("$dir/$xpath[Target]", $mainNS); if (isset($xmlStrings->si)) { foreach ($xmlStrings->si as $val) { if (isset($val->t)) { @@ -440,14 +477,16 @@ public function load(string $pFilename, int $flags = 0) $worksheets = []; $macros = $customUI = null; - foreach ($relsWorkbook->Relationship as $ele) { + foreach ($relsWorkbook->Relationship as $elex) { + $ele = self::getAttributes($elex); switch ($ele['Type']) { - case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet': + case Namespaces::WORKSHEET: + case Namespaces::PURL_WORKSHEET: $worksheets[(string) $ele['Id']] = $ele['Target']; break; // a vbaProject ? (: some macros) - case 'http://schemas.microsoft.com/office/2006/relationships/vbaProject': + case Namespaces::VBA: $macros = $ele['Target']; break; @@ -467,25 +506,31 @@ public function load(string $pFilename, int $flags = 0) } } - $xpath = self::getArrayItem($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles']")); - //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" - $xmlStyles = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$xpath[Target]")), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $relType = "rel:Relationship[@Type='" + . "$xmlNamespaceBase/styles" + . "']"; + $xpath = self::getArrayItem(self::xpathNoFalse($relsWorkbook, $relType)); + // I think Nonamespace is okay because I'm using xpath. + $xmlStyles = $this->loadZipNonamespace("$dir/$xpath[Target]", $mainNS); + $xmlStyles->registerXPathNamespace('smm', Namespaces::MAIN); + $fills = self::xpathNoFalse($xmlStyles, 'smm:fills/smm:fill'); + $fonts = self::xpathNoFalse($xmlStyles, 'smm:fonts/smm:font'); + $borders = self::xpathNoFalse($xmlStyles, 'smm:borders/smm:border'); + $xfTags = self::xpathNoFalse($xmlStyles, 'smm:cellXfs/smm:xf'); + $cellXfTags = self::xpathNoFalse($xmlStyles, 'smm:cellStyleXfs/smm:xf'); $styles = []; $cellStyles = []; $numFmts = null; - if ($xmlStyles && $xmlStyles->numFmts[0]) { + if (/*$xmlStyles && */ $xmlStyles->numFmts[0]) { $numFmts = $xmlStyles->numFmts[0]; } if (isset($numFmts) && ($numFmts !== null)) { - $numFmts->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); + $numFmts->registerXPathNamespace('sml', $mainNS); } - if (!$this->readDataOnly && $xmlStyles) { - foreach ($xmlStyles->cellXfs->xf as $xf) { + if (!$this->readDataOnly/* && $xmlStyles*/) { + foreach ($xfTags as $xfTag) { + $xf = self::getAttributes($xfTag); $numFmt = null; if ($xf['numFmtId']) { @@ -512,11 +557,11 @@ public function load(string $pFilename, int $flags = 0) $style = (object) [ 'numFmt' => $numFmt ?? NumberFormat::FORMAT_GENERAL, - 'font' => $xmlStyles->fonts->font[(int) ($xf['fontId'])], - 'fill' => $xmlStyles->fills->fill[(int) ($xf['fillId'])], - 'border' => $xmlStyles->borders->border[(int) ($xf['borderId'])], - 'alignment' => $xf->alignment, - 'protection' => $xf->protection, + 'font' => $fonts[(int) ($xf['fontId'])], + 'fill' => $fills[(int) ($xf['fillId'])], + 'border' => $borders[(int) ($xf['borderId'])], + 'alignment' => $xfTag->alignment, + 'protection' => $xfTag->protection, 'quotePrefix' => $quotePrefix, ]; $styles[] = $style; @@ -527,7 +572,8 @@ public function load(string $pFilename, int $flags = 0) $excel->addCellXf($objStyle); } - foreach ($xmlStyles->cellStyleXfs->xf ?? [] as $xf) { + foreach ($cellXfTags as $xfTag) { + $xf = self::getAttributes($xfTag); $numFmt = NumberFormat::FORMAT_GENERAL; if ($numFmts && $xf['numFmtId']) { $tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]")); @@ -542,11 +588,11 @@ public function load(string $pFilename, int $flags = 0) $cellStyle = (object) [ 'numFmt' => $numFmt, - 'font' => $xmlStyles->fonts->font[(int) ($xf['fontId'])], - 'fill' => $xmlStyles->fills->fill[(int) ($xf['fillId'])], - 'border' => $xmlStyles->borders->border[(int) ($xf['borderId'])], - 'alignment' => $xf->alignment, - 'protection' => $xf->protection, + 'font' => $fonts[(int) ($xf['fontId'])], + 'fill' => $fills[((int) $xf['fillId'])], + 'border' => $borders[(int) ($xf['borderId'])], + 'alignment' => $xfTag->alignment, + 'protection' => $xfTag->protection, 'quotePrefix' => $quotePrefix, ]; $cellStyles[] = $cellStyle; @@ -563,18 +609,15 @@ public function load(string $pFilename, int $flags = 0) $dxfs = $styleReader->dxfs($this->readDataOnly); $styles = $styleReader->styles(); - //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" - $xmlWorkbook = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "{$rel['Target']}")), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $xmlWorkbook = $this->loadZipNoNamespace($rel['Target'], $mainNS); + $xmlWorkbookNS = $this->loadZip($rel['Target'], $mainNS); // Set base date - if ($xmlWorkbook->workbookPr) { + if ($xmlWorkbookNS->workbookPr) { Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - if (isset($xmlWorkbook->workbookPr['date1904'])) { - if (self::boolean((string) $xmlWorkbook->workbookPr['date1904'])) { + $attrs1904 = self::getAttributes($xmlWorkbookNS->workbookPr); + if (isset($attrs1904['date1904'])) { + if (self::boolean((string) $attrs1904['date1904'])) { Date::setExcelCalendar(Date::CALENDAR_MAC_1904); } } @@ -590,13 +633,14 @@ public function load(string $pFilename, int $flags = 0) $charts = $chartDetails = []; - if ($xmlWorkbook->sheets) { + if ($xmlWorkbookNS->sheets) { /** @var SimpleXMLElement $eleSheet */ - foreach ($xmlWorkbook->sheets->sheet as $eleSheet) { + foreach ($xmlWorkbookNS->sheets->sheet as $eleSheet) { + $eleSheetAttr = self::getAttributes($eleSheet); ++$oldSheetId; // Check if sheet should be skipped - if (isset($this->loadSheetsOnly) && !in_array((string) $eleSheet['name'], $this->loadSheetsOnly)) { + if (is_array($this->loadSheetsOnly) && !in_array((string) $eleSheetAttr['name'], $this->loadSheetsOnly)) { ++$countSkippedSheets; $mapSheetId[$oldSheetId] = null; @@ -613,30 +657,25 @@ public function load(string $pFilename, int $flags = 0) // 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 - $docSheet->setTitle((string) $eleSheet['name'], false, false); - $fileWorksheet = $worksheets[(string) self::getArrayItem($eleSheet->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')]; - //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" - $xmlSheet = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$fileWorksheet")), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $docSheet->setTitle((string) $eleSheetAttr['name'], false, false); + $fileWorksheet = $worksheets[(string) self::getArrayItem(self::getAttributes($eleSheet, $xmlNamespaceBase), 'id')]; + $xmlSheet = $this->loadZipNoNamespace("$dir/$fileWorksheet", $mainNS); + $xmlSheetNS = $this->loadZip("$dir/$fileWorksheet", $mainNS); $sharedFormulas = []; - if (isset($eleSheet['state']) && (string) $eleSheet['state'] != '') { - $docSheet->setSheetState((string) $eleSheet['state']); + if (isset($eleSheetAttr['state']) && (string) $eleSheetAttr['state'] != '') { + $docSheet->setSheetState((string) $eleSheetAttr['state']); } - - if ($xmlSheet) { + if ($xmlSheetNS) { + $xmlSheetMain = $xmlSheetNS->children($mainNS); // Setting Conditional Styles adjusts selected cells, so we need to execute this // before reading the sheet view data to get the actual selected cells if (!$this->readDataOnly && $xmlSheet->conditionalFormatting) { (new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->load(); } - - if (isset($xmlSheet->sheetViews, $xmlSheet->sheetViews->sheetView)) { - $sheetViews = new SheetViews($xmlSheet->sheetViews->sheetView, $docSheet); + if (isset($xmlSheetMain->sheetViews, $xmlSheetMain->sheetViews->sheetView)) { + $sheetViews = new SheetViews($xmlSheetMain->sheetViews->sheetView, $docSheet); $sheetViews->load(); } @@ -647,16 +686,17 @@ public function load(string $pFilename, int $flags = 0) ->load($this->getReadFilter(), $this->getReadDataOnly()); } - if ($xmlSheet && $xmlSheet->sheetData && $xmlSheet->sheetData->row) { + if ($xmlSheetNS && $xmlSheetNS->sheetData && $xmlSheetNS->sheetData->row) { $cIndex = 1; // Cell Start from 1 - foreach ($xmlSheet->sheetData->row as $row) { + foreach ($xmlSheetNS->sheetData->row as $row) { $rowIndex = 1; foreach ($row->c as $c) { - $r = (string) $c['r']; + $cAttr = self::getAttributes($c); + $r = (string) $cAttr['r']; if ($r == '') { $r = Coordinate::stringFromColumnIndex($rowIndex) . $cIndex; } - $cellDataType = (string) $c['t']; + $cellDataType = (string) $cAttr['t']; $value = null; $calculatedValue = null; @@ -665,7 +705,7 @@ public function load(string $pFilename, int $flags = 0) $coordinates = Coordinate::coordinateFromString($r); if (!$this->getReadFilter()->readCell($coordinates[0], (int) $coordinates[1], $docSheet->getTitle())) { - if (isset($c->f)) { + if (isset($cAttr->f)) { $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError'); } ++$rowIndex; @@ -756,10 +796,10 @@ public function load(string $pFilename, int $flags = 0) } // Style information? - if ($c['s'] && !$this->readDataOnly) { + if ($cAttr['s'] && !$this->readDataOnly) { // no style index means 0, it seems - $cell->setXfIndex(isset($styles[(int) ($c['s'])]) ? - (int) ($c['s']) : 0); + $cell->setXfIndex(isset($styles[(int) ($cAttr['s'])]) ? + (int) ($cAttr['s']) : 0); } } ++$rowIndex; @@ -819,9 +859,10 @@ public function load(string $pFilename, int $flags = 0) // unparsed sheet AlternateContent if ($xmlSheet && !$this->readDataOnly) { - $mc = $xmlSheet->children('http://schemas.openxmlformats.org/markup-compatibility/2006'); + $mc = $xmlSheet->children(Namespaces::COMPATIBILITY); if ($mc->AlternateContent) { foreach ($mc->AlternateContent as $alternateContent) { + $alternateContent = self::testSimpleXml($alternateContent); $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['AlternateContents'][] = $alternateContent->asXML(); } } @@ -833,20 +874,13 @@ public function load(string $pFilename, int $flags = 0) // Locate hyperlink relations $relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; if ($zip->locateName($relationsFileName)) { - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorksheet = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, $relationsFileName) - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $relsWorksheet = $this->loadZip($relationsFileName, Namespaces::RELATIONSHIPS); $hyperlinkReader->readHyperlinks($relsWorksheet); } // Loop through hyperlinks - if ($xmlSheet && $xmlSheet->hyperlinks) { - $hyperlinkReader->setHyperlinks($xmlSheet->hyperlinks); + if ($xmlSheetNS && $xmlSheetNS->children($mainNS)->hyperlinks) { + $hyperlinkReader->setHyperlinks($xmlSheetNS->children($mainNS)->hyperlinks); } } @@ -855,20 +889,15 @@ public function load(string $pFilename, int $flags = 0) $vmlComments = []; if (!$this->readDataOnly) { // Locate comment relations - if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorksheet = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - foreach ($relsWorksheet->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments') { + $commentRelations = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; + if ($zip->locateName($commentRelations)) { + $relsWorksheet = $this->loadZip($commentRelations, Namespaces::RELATIONSHIPS); + foreach ($relsWorksheet->Relationship as $elex) { + $ele = self::getAttributes($elex); + if ($ele['Type'] == Namespaces::COMMENTS) { $comments[(string) $ele['Id']] = (string) $ele['Target']; } - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing') { + if ($ele['Type'] == Namespaces::VML) { $vmlComments[(string) $ele['Id']] = (string) $ele['Target']; } } @@ -878,27 +907,25 @@ public function load(string $pFilename, int $flags = 0) foreach ($comments as $relName => $relPath) { // Load comments file $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath); - $commentsFile = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, $relPath)), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + // okay to ignore namespace - using xpath + $commentsFile = $this->loadZip($relPath, ''); // Utility variables $authors = []; - - // Loop through authors - foreach ($commentsFile->authors->author as $author) { + $commentsFile->registerXpathNamespace('com', $mainNS); + $authorPath = self::xpathNoFalse($commentsFile, 'com:authors/com:author'); + foreach ($authorPath as $author) { $authors[] = (string) $author; } // Loop through contents - foreach ($commentsFile->commentList->comment as $comment) { + $contentPath = self::xpathNoFalse($commentsFile, 'com:commentList/com:comment'); + foreach ($contentPath as $comment) { $commentModel = $docSheet->getComment((string) $comment['ref']); if (!empty($comment['authorId'])) { - $commentModel->setAuthor($authors[$comment['authorId']]); + $commentModel->setAuthor($authors[(int) $comment['authorId']]); } - $commentModel->setText($this->parseRichText($comment->text)); + $commentModel->setText($this->parseRichText($comment->children($mainNS)->text)); } } @@ -911,20 +938,17 @@ public function load(string $pFilename, int $flags = 0) $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath); try { - $vmlCommentsFile = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, $relPath)), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - $vmlCommentsFile->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml'); + // no namespace okay - processed with Xpath + $vmlCommentsFile = $this->loadZip($relPath, ''); + $vmlCommentsFile->registerXPathNamespace('v', Namespaces::URN_VML); } catch (Throwable $ex) { //Ignore unparsable vmlDrawings. Later they will be moved from $unparsedVmlDrawings to $unparsedLoadedData continue; } - $shapes = $vmlCommentsFile->xpath('//v:shape'); + $shapes = self::xpathNoFalse($vmlCommentsFile, '//v:shape'); foreach ($shapes as $shape) { - $shape->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml'); + $shape->registerXPathNamespace('v', Namespaces::URN_VML); if (isset($shape['style'])) { $style = (string) $shape['style']; @@ -998,62 +1022,44 @@ public function load(string $pFilename, int $flags = 0) // Header/footer images if ($xmlSheet && $xmlSheet->legacyDrawingHF && !$this->readDataOnly) { if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorksheet = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $relsWorksheet = $this->loadZipNoNamespace(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels', Namespaces::RELATIONSHIPS); $vmlRelationship = ''; foreach ($relsWorksheet->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing') { + if ($ele['Type'] == Namespaces::VML) { $vmlRelationship = self::dirAdd("$dir/$fileWorksheet", $ele['Target']); } } if ($vmlRelationship != '') { // Fetch linked images - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsVML = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, dirname($vmlRelationship) . '/_rels/' . basename($vmlRelationship) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $relsVML = $this->loadZipNoNamespace(dirname($vmlRelationship) . '/_rels/' . basename($vmlRelationship) . '.rels', Namespaces::RELATIONSHIPS); $drawings = []; if (isset($relsVML->Relationship)) { foreach ($relsVML->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') { + if ($ele['Type'] == Namespaces::IMAGE) { $drawings[(string) $ele['Id']] = self::dirAdd($vmlRelationship, $ele['Target']); } } } // Fetch VML document - $vmlDrawing = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, $vmlRelationship)), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - $vmlDrawing->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml'); + $vmlDrawing = $this->loadZipNoNamespace($vmlRelationship, ''); + $vmlDrawing->registerXPathNamespace('v', Namespaces::URN_VML); $hfImages = []; - $shapes = $vmlDrawing->xpath('//v:shape'); + $shapes = self::xpathNoFalse($vmlDrawing, '//v:shape'); foreach ($shapes as $idx => $shape) { - $shape->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml'); + $shape->registerXPathNamespace('v', Namespaces::URN_VML); $imageData = $shape->xpath('//v:imagedata'); - if (!$imageData) { + if (empty($imageData)) { continue; } $imageData = $imageData[$idx]; - $imageData = $imageData->attributes('urn:schemas-microsoft-com:office:office'); + $imageData = self::getAttributes($imageData, Namespaces::URN_MSOFFICE); $style = self::toCSSArray((string) $shape['style']); $hfImages[(string) $shape['id']] = new HeaderFooterDrawing(); @@ -1079,18 +1085,15 @@ public function load(string $pFilename, int $flags = 0) } // TODO: Autoshapes from twoCellAnchors! - if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorksheet = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $filename = dirname("$dir/$fileWorksheet") + . '/_rels/' + . basename($fileWorksheet) + . '.rels'; + if ($zip->locateName($filename)) { + $relsWorksheet = $this->loadZipNoNamespace($filename, Namespaces::RELATIONSHIPS); $drawings = []; foreach ($relsWorksheet->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing') { + if ((string) $ele['Type'] === "$xmlNamespaceBase/drawing") { $drawings[(string) $ele['Id']] = self::dirAdd("$dir/$fileWorksheet", $ele['Target']); } } @@ -1098,26 +1101,21 @@ public function load(string $pFilename, int $flags = 0) $unparsedDrawings = []; $fileDrawing = null; foreach ($xmlSheet->drawing as $drawing) { - $drawingRelId = (string) self::getArrayItem($drawing->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id'); + $drawingRelId = (string) self::getArrayItem(self::getAttributes($drawing, $xmlNamespaceBase), 'id'); $fileDrawing = $drawings[$drawingRelId]; - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsDrawing = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, dirname($fileDrawing) . '/_rels/' . basename($fileDrawing) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $filename = dirname($fileDrawing) . '/_rels/' . basename($fileDrawing) . '.rels'; + $relsDrawing = $this->loadZipNoNamespace($filename, $xmlNamespaceBase); $images = []; $hyperlinks = []; if ($relsDrawing && $relsDrawing->Relationship) { foreach ($relsDrawing->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink') { + $eleType = (string) $ele['Type']; + if ($eleType === Namespaces::HYPERLINK) { $hyperlinks[(string) $ele['Id']] = (string) $ele['Target']; } - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') { + if ($eleType === "$xmlNamespaceBase/image") { $images[(string) $ele['Id']] = self::dirAdd($fileDrawing, $ele['Target']); - } elseif ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart') { + } elseif ($eleType === "$xmlNamespaceBase/chart") { if ($this->includeCharts) { $charts[self::dirAdd($fileDrawing, $ele['Target'])] = [ 'id' => (string) $ele['Id'], @@ -1127,30 +1125,26 @@ public function load(string $pFilename, int $flags = 0) } } } - $xmlDrawing = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, $fileDrawing)), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - $xmlDrawingChildren = $xmlDrawing->children('http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing'); + $xmlDrawing = $this->loadZipNoNamespace($fileDrawing, ''); + $xmlDrawingChildren = $xmlDrawing->children(Namespaces::SPREADSHEET_DRAWING); if ($xmlDrawingChildren->oneCellAnchor) { foreach ($xmlDrawingChildren->oneCellAnchor as $oneCellAnchor) { if ($oneCellAnchor->pic->blipFill) { /** @var SimpleXMLElement $blip */ - $blip = $oneCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip; + $blip = $oneCellAnchor->pic->blipFill->children(Namespaces::DRAWINGML)->blip; /** @var SimpleXMLElement $xfrm */ - $xfrm = $oneCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm; + $xfrm = $oneCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->xfrm; /** @var SimpleXMLElement $outerShdw */ - $outerShdw = $oneCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->effectLst->outerShdw; + $outerShdw = $oneCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->effectLst->outerShdw; /** @var SimpleXMLElement $hlinkClick */ - $hlinkClick = $oneCellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick; + $hlinkClick = $oneCellAnchor->pic->nvPicPr->cNvPr->children(Namespaces::DRAWINGML)->hlinkClick; $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); - $objDrawing->setName((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name')); - $objDrawing->setDescription((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'descr')); + $objDrawing->setName((string) self::getArrayItem(self::getAttributes($oneCellAnchor->pic->nvPicPr->cNvPr), 'name')); + $objDrawing->setDescription((string) self::getArrayItem(self::getAttributes($oneCellAnchor->pic->nvPicPr->cNvPr), 'descr')); $embedImageKey = (string) self::getArrayItem( - $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), + self::getAttributes($blip, $xmlNamespaceBase), 'embed' ); if (isset($images[$embedImageKey])) { @@ -1171,24 +1165,24 @@ public function load(string $pFilename, int $flags = 0) } $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((int) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1)); - $objDrawing->setOffsetX(Drawing::EMUToPixels($oneCellAnchor->from->colOff)); + $objDrawing->setOffsetX((int) Drawing::EMUToPixels($oneCellAnchor->from->colOff)); $objDrawing->setOffsetY(Drawing::EMUToPixels($oneCellAnchor->from->rowOff)); $objDrawing->setResizeProportional(false); - $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cx'))); - $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cy'))); + $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem((int) self::getAttributes($oneCellAnchor->ext), 'cx'))); + $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem((int) self::getAttributes($oneCellAnchor->ext), 'cy'))); if ($xfrm) { - $objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem($xfrm->attributes(), 'rot'))); + $objDrawing->setRotation((int) Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($xfrm), 'rot'))); } if ($outerShdw) { $shadow = $objDrawing->getShadow(); $shadow->setVisible(true); - $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'blurRad'))); - $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'dist'))); - $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem($outerShdw->attributes(), 'dir'))); - $shadow->setAlignment((string) self::getArrayItem($outerShdw->attributes(), 'algn')); + $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'blurRad'))); + $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'dist'))); + $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($outerShdw), 'dir'))); + $shadow->setAlignment((string) self::getArrayItem(self::getAttributes($outerShdw), 'algn')); $clr = $outerShdw->srgbClr ?? $outerShdw->prstClr; - $shadow->getColor()->setRGB(self::getArrayItem($clr->attributes(), 'val')); - $shadow->setAlpha(self::getArrayItem($clr->alpha->attributes(), 'val') / 1000); + $shadow->getColor()->setRGB(self::getArrayItem(self::getAttributes($clr), 'val')); + $shadow->setAlpha(self::getArrayItem(self::getAttributes($clr->alpha), 'val') / 1000); } $this->readHyperLinkDrawing($objDrawing, $oneCellAnchor, $hyperlinks); @@ -1199,13 +1193,13 @@ public function load(string $pFilename, int $flags = 0) $coordinates = Coordinate::stringFromColumnIndex(((int) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1); $offsetX = Drawing::EMUToPixels($oneCellAnchor->from->colOff); $offsetY = Drawing::EMUToPixels($oneCellAnchor->from->rowOff); - $width = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cx')); - $height = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cy')); + $width = Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cx')); + $height = Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cy')); - $graphic = $oneCellAnchor->graphicFrame->children('http://schemas.openxmlformats.org/drawingml/2006/main')->graphic; + $graphic = $oneCellAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic; /** @var SimpleXMLElement $chartRef */ - $chartRef = $graphic->graphicData->children('http://schemas.openxmlformats.org/drawingml/2006/chart')->chart; - $thisChart = (string) $chartRef->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart; + $thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase); $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [ 'fromCoordinate' => $coordinates, @@ -1221,15 +1215,15 @@ public function load(string $pFilename, int $flags = 0) if ($xmlDrawingChildren->twoCellAnchor) { foreach ($xmlDrawingChildren->twoCellAnchor as $twoCellAnchor) { if ($twoCellAnchor->pic->blipFill) { - $blip = $twoCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip; - $xfrm = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm; - $outerShdw = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->effectLst->outerShdw; - $hlinkClick = $twoCellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick; + $blip = $twoCellAnchor->pic->blipFill->children(Namespaces::DRAWINGML)->blip; + $xfrm = $twoCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->xfrm; + $outerShdw = $twoCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->effectLst->outerShdw; + $hlinkClick = $twoCellAnchor->pic->nvPicPr->cNvPr->children(Namespaces::DRAWINGML)->hlinkClick; $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); - $objDrawing->setName((string) self::getArrayItem($twoCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name')); - $objDrawing->setDescription((string) self::getArrayItem($twoCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'descr')); + $objDrawing->setName((string) self::getArrayItem(self::getAttributes($twoCellAnchor->pic->nvPicPr->cNvPr), 'name')); + $objDrawing->setDescription((string) self::getArrayItem(self::getAttributes($twoCellAnchor->pic->nvPicPr->cNvPr), 'descr')); $embedImageKey = (string) self::getArrayItem( - $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), + self::getAttributes($blip, $xmlNamespaceBase), 'embed' ); if (isset($images[$embedImageKey])) { @@ -1255,20 +1249,20 @@ public function load(string $pFilename, int $flags = 0) $objDrawing->setResizeProportional(false); if ($xfrm) { - $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem($xfrm->ext->attributes(), 'cx'))); - $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem($xfrm->ext->attributes(), 'cy'))); - $objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem($xfrm->attributes(), 'rot'))); + $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($xfrm->ext), 'cx'))); + $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($xfrm->ext), 'cy'))); + $objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($xfrm), 'rot'))); } if ($outerShdw) { $shadow = $objDrawing->getShadow(); $shadow->setVisible(true); - $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'blurRad'))); - $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'dist'))); - $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem($outerShdw->attributes(), 'dir'))); - $shadow->setAlignment((string) self::getArrayItem($outerShdw->attributes(), 'algn')); + $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'blurRad'))); + $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'dist'))); + $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($outerShdw), 'dir'))); + $shadow->setAlignment((string) self::getArrayItem(self::getAttributes($outerShdw), 'algn')); $clr = $outerShdw->srgbClr ?? $outerShdw->prstClr; - $shadow->getColor()->setRGB(self::getArrayItem($clr->attributes(), 'val')); - $shadow->setAlpha(self::getArrayItem($clr->alpha->attributes(), 'val') / 1000); + $shadow->getColor()->setRGB(self::getArrayItem(self::getAttributes($clr), 'val')); + $shadow->setAlpha(self::getArrayItem(self::getAttributes($clr->alpha), 'val') / 1000); } $this->readHyperLinkDrawing($objDrawing, $twoCellAnchor, $hyperlinks); @@ -1281,10 +1275,10 @@ public function load(string $pFilename, int $flags = 0) $toCoordinate = Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->to->col) + 1) . ($twoCellAnchor->to->row + 1); $toOffsetX = Drawing::EMUToPixels($twoCellAnchor->to->colOff); $toOffsetY = Drawing::EMUToPixels($twoCellAnchor->to->rowOff); - $graphic = $twoCellAnchor->graphicFrame->children('http://schemas.openxmlformats.org/drawingml/2006/main')->graphic; + $graphic = $twoCellAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic; /** @var SimpleXMLElement $chartRef */ - $chartRef = $graphic->graphicData->children('http://schemas.openxmlformats.org/drawingml/2006/chart')->chart; - $thisChart = (string) $chartRef->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart; + $thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase); $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [ 'fromCoordinate' => $fromCoordinate, @@ -1298,7 +1292,7 @@ public function load(string $pFilename, int $flags = 0) } } } - if ($relsDrawing === false && $xmlDrawing->count() == 0) { + if (empty($relsDrawing) && $xmlDrawing->count() == 0) { // Save Drawing without rels and children as unparsed $unparsedDrawings[$drawingRelId] = $xmlDrawing->asXML(); } @@ -1307,7 +1301,7 @@ public function load(string $pFilename, int $flags = 0) // store original rId of drawing files $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'] = []; foreach ($relsWorksheet->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing') { + if ((string) $ele['Type'] === "$xmlNamespaceBase/drawing") { $drawingRelId = (string) $ele['Id']; $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'][(string) $ele['Target']] = $drawingRelId; if (isset($unparsedDrawings[$drawingRelId])) { @@ -1317,22 +1311,19 @@ public function load(string $pFilename, int $flags = 0) } // unparsed drawing AlternateContent - $xmlAltDrawing = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, $fileDrawing)), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - )->children('http://schemas.openxmlformats.org/markup-compatibility/2006'); + $xmlAltDrawing = $this->loadZip($fileDrawing, Namespaces::COMPATIBILITY); if ($xmlAltDrawing->AlternateContent) { foreach ($xmlAltDrawing->AlternateContent as $alternateContent) { + $alternateContent = self::testSimpleXml($alternateContent); $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingAlternateContents'][] = $alternateContent->asXML(); } } } } - $this->readFormControlProperties($excel, $zip, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData); - $this->readPrinterSettings($excel, $zip, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData); + $this->readFormControlProperties($excel, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData); + $this->readPrinterSettings($excel, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData); // Loop through definedNames if ($xmlWorkbook->definedNames) { @@ -1473,11 +1464,11 @@ public function load(string $pFilename, int $flags = 0) } } - if ((!$this->readDataOnly || !empty($this->loadSheetsOnly)) && isset($xmlWorkbook->bookViews->workbookView)) { - $workbookView = $xmlWorkbook->bookViews->workbookView; - + $workbookView = $xmlWorkbook->children($mainNS)->bookViews->workbookView; + if ((!$this->readDataOnly || !empty($this->loadSheetsOnly)) && !empty($workbookView)) { + $workbookViewAttributes = self::testSimpleXml(self::getAttributes($workbookView)); // active sheet index - $activeTab = (int) ($workbookView['activeTab']); // refers to old sheet index + $activeTab = (int) $workbookViewAttributes->activeTab; // refers to old sheet index // keep active sheet index if sheet is still loaded, else first sheet is set as the active if (isset($mapSheetId[$activeTab]) && $mapSheetId[$activeTab] !== null) { @@ -1489,43 +1480,43 @@ public function load(string $pFilename, int $flags = 0) $excel->setActiveSheetIndex(0); } - if (isset($workbookView['showHorizontalScroll'])) { - $showHorizontalScroll = (string) $workbookView['showHorizontalScroll']; + if (isset($workbookViewAttributes->showHorizontalScroll)) { + $showHorizontalScroll = (string) $workbookViewAttributes->showHorizontalScroll; $excel->setShowHorizontalScroll($this->castXsdBooleanToBool($showHorizontalScroll)); } - if (isset($workbookView['showVerticalScroll'])) { - $showVerticalScroll = (string) $workbookView['showVerticalScroll']; + if (isset($workbookViewAttributes->showVerticalScroll)) { + $showVerticalScroll = (string) $workbookViewAttributes->showVerticalScroll; $excel->setShowVerticalScroll($this->castXsdBooleanToBool($showVerticalScroll)); } - if (isset($workbookView['showSheetTabs'])) { - $showSheetTabs = (string) $workbookView['showSheetTabs']; + if (isset($workbookViewAttributes->showSheetTabs)) { + $showSheetTabs = (string) $workbookViewAttributes->showSheetTabs; $excel->setShowSheetTabs($this->castXsdBooleanToBool($showSheetTabs)); } - if (isset($workbookView['minimized'])) { - $minimized = (string) $workbookView['minimized']; + if (isset($workbookViewAttributes->minimized)) { + $minimized = (string) $workbookViewAttributes->minimized; $excel->setMinimized($this->castXsdBooleanToBool($minimized)); } - if (isset($workbookView['autoFilterDateGrouping'])) { - $autoFilterDateGrouping = (string) $workbookView['autoFilterDateGrouping']; + if (isset($workbookViewAttributes->autoFilterDateGrouping)) { + $autoFilterDateGrouping = (string) $workbookViewAttributes->autoFilterDateGrouping; $excel->setAutoFilterDateGrouping($this->castXsdBooleanToBool($autoFilterDateGrouping)); } - if (isset($workbookView['firstSheet'])) { - $firstSheet = (string) $workbookView['firstSheet']; + if (isset($workbookViewAttributes->firstSheet)) { + $firstSheet = (string) $workbookViewAttributes->firstSheet; $excel->setFirstSheetIndex((int) $firstSheet); } - if (isset($workbookView['visibility'])) { - $visibility = (string) $workbookView['visibility']; + if (isset($workbookViewAttributes->visibility)) { + $visibility = (string) $workbookViewAttributes->visibility; $excel->setVisibility($visibility); } - if (isset($workbookView['tabRatio'])) { - $tabRatio = (string) $workbookView['tabRatio']; + if (isset($workbookViewAttributes->tabRatio)) { + $tabRatio = (string) $workbookViewAttributes->tabRatio; $excel->setTabRatio((int) $tabRatio); } } @@ -1535,13 +1526,7 @@ public function load(string $pFilename, int $flags = 0) } if (!$this->readDataOnly) { - $contentTypes = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, '[Content_Types].xml') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $contentTypes = $this->loadZip('[Content_Types].xml'); // Default content types foreach ($contentTypes->Default as $contentType) { @@ -1559,13 +1544,7 @@ public function load(string $pFilename, int $flags = 0) case 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml': if ($this->includeCharts) { $chartEntryRef = ltrim($contentType['PartName'], '/'); - $chartElements = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, $chartEntryRef) - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $chartElements = $this->loadZip($chartEntryRef); $objChart = Chart::readChart($chartElements, basename($chartEntryRef, '.xml')); if (isset($charts[$chartEntryRef])) { @@ -1629,8 +1608,8 @@ private static function readStyle(Style $docStyle, $style): void // protection if (isset($style->protection)) { - Styles::readProtectionLocked($docStyle, $style->protection); - Styles::readProtectionHidden($docStyle, $style->protection); + Styles::readProtectionLocked($docStyle, $style); + Styles::readProtectionHidden($docStyle, $style); } // top-level style settings @@ -1734,7 +1713,7 @@ private function readRibbon(Spreadsheet $excel, $customUITarget, $zip): void if (false !== $UIRels) { // we need to save id and target to avoid parsing customUI.xml and "guess" if it's a pseudo callback who load the image foreach ($UIRels->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') { + if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/image') { // an image ? $customUIImagesNames[(string) $ele['Id']] = (string) $ele['Target']; $customUIImagesBinaries[(string) $ele['Target']] = $this->getFromZipArchive($zip, $baseDir . '/' . (string) $ele['Target']); @@ -1820,16 +1799,16 @@ private static function boolean($value) */ private function readHyperLinkDrawing($objDrawing, $cellAnchor, $hyperlinks): void { - $hlinkClick = $cellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick; + $hlinkClick = $cellAnchor->pic->nvPicPr->cNvPr->children(Namespaces::DRAWINGML)->hlinkClick; if ($hlinkClick->count() === 0) { return; } - $hlinkId = (string) $hlinkClick->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships')['id']; + $hlinkId = (string) self::getAttributes($hlinkClick, Namespaces::SCHEMA_OFFICE_DOCUMENT)['id']; $hyperlink = new Hyperlink( $hyperlinks[$hlinkId], - (string) self::getArrayItem($cellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name') + (string) self::getArrayItem(self::getAttributes($cellAnchor->pic->nvPicPr->cNvPr), 'name') ); $objDrawing->setHyperlink($hyperlink); } @@ -1871,23 +1850,18 @@ private static function getLockValue(SimpleXmlElement $protection, string $key): return $returnValue; } - private function readFormControlProperties(Spreadsheet $excel, ZipArchive $zip, $dir, $fileWorksheet, $docSheet, array &$unparsedLoadedData): void + private function readFormControlProperties(Spreadsheet $excel, $dir, $fileWorksheet, $docSheet, array &$unparsedLoadedData): void { + $zip = $this->zip; if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { return; } - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorksheet = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $filename = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; + $relsWorksheet = $this->loadZipNoNamespace($filename, Namespaces::RELATIONSHIPS); $ctrlProps = []; foreach ($relsWorksheet->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp') { + if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/ctrlProp') { $ctrlProps[(string) $ele['Id']] = $ele; } } @@ -1903,23 +1877,18 @@ private function readFormControlProperties(Spreadsheet $excel, ZipArchive $zip, unset($unparsedCtrlProps); } - private function readPrinterSettings(Spreadsheet $excel, ZipArchive $zip, $dir, $fileWorksheet, $docSheet, array &$unparsedLoadedData): void + private function readPrinterSettings(Spreadsheet $excel, $dir, $fileWorksheet, $docSheet, array &$unparsedLoadedData): void { + $zip = $this->zip; if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { return; } - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorksheet = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $filename = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; + $relsWorksheet = $this->loadZipNoNamespace($filename, Namespaces::RELATIONSHIPS); $sheetPrinterSettings = []; foreach ($relsWorksheet->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings') { + if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/printerSettings') { $sheetPrinterSettings[(string) $ele['Id']] = $ele; } } @@ -1958,38 +1927,29 @@ private function castXsdBooleanToBool($xsdBoolean) return (bool) $xsdBoolean; } - /** - * @param ZipArchive $zip Opened zip archive - * - * @return string basename of the used excel workbook - */ - private function getWorkbookBaseName(ZipArchive $zip) + private function getWorkbookBaseName(): array { $workbookBasename = ''; + $xmlNamespaceBase = ''; // check if it is an OOXML archive - $rels = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, '_rels/.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - if ($rels !== false) { - foreach ($rels->Relationship as $rel) { - switch ($rel['Type']) { - case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument': - $basename = basename($rel['Target']); - if (preg_match('/workbook.*\.xml/', $basename)) { - $workbookBasename = $basename; - } + $rels = $this->loadZip('_rels/.rels'); + foreach ($rels->children(Namespaces::RELATIONSHIPS)->Relationship as $rel) { + $rel = self::getAttributes($rel); + switch ($rel['Type']) { + case Namespaces::OFFICE_DOCUMENT: + case Namespaces::PURL_OFFICE_DOCUMENT: + $basename = basename($rel['Target']); + $xmlNamespaceBase = dirname($rel['Type']); + if (preg_match('/workbook.*\.xml/', $basename)) { + $workbookBasename = $basename; + } - break; - } + break; } } - return $workbookBasename; + return [$workbookBasename, $xmlNamespaceBase]; } private function readSheetProtection(Worksheet $docSheet, SimpleXMLElement $xmlSheet): void diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php b/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php index a9afce38cf..dbc8c08532 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use SimpleXMLElement; @@ -19,16 +20,17 @@ public function __construct(Worksheet $workSheet) public function readHyperlinks(SimpleXMLElement $relsWorksheet): void { - foreach ($relsWorksheet->Relationship as $element) { - if ($element['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink') { - $this->hyperlinks[(string) $element['Id']] = (string) $element['Target']; + foreach ($relsWorksheet->children(Namespaces::RELATIONSHIPS)->Relationship as $elementx) { + $element = Xlsx::getAttributes($elementx); + if ($element->Type == Namespaces::HYPERLINK) { + $this->hyperlinks[(string) $element->Id] = (string) $element->Target; } } } public function setHyperlinks(SimpleXMLElement $worksheetXml): void { - foreach ($worksheetXml->hyperlink as $hyperlink) { + foreach ($worksheetXml->children(Namespaces::MAIN)->hyperlink as $hyperlink) { if ($hyperlink !== null) { $this->setHyperlink($hyperlink, $this->worksheet); } @@ -38,9 +40,10 @@ public function setHyperlinks(SimpleXMLElement $worksheetXml): void private function setHyperlink(SimpleXMLElement $hyperlink, Worksheet $worksheet): void { // Link url - $linkRel = $hyperlink->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $linkRel = Xlsx::getAttributes($hyperlink, Namespaces::SCHEMA_OFFICE_DOCUMENT); - foreach (Coordinate::extractAllCellReferencesInRange($hyperlink['ref']) as $cellReference) { + $attributes = Xlsx::getAttributes($hyperlink); + foreach (Coordinate::extractAllCellReferencesInRange($attributes->ref) as $cellReference) { $cell = $worksheet->getCell($cellReference); if (isset($linkRel['id'])) { $hyperlinkUrl = $this->hyperlinks[(string) $linkRel['id']] ?? null; diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php b/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php new file mode 100644 index 0000000000..54f56d7247 --- /dev/null +++ b/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php @@ -0,0 +1,76 @@ +setPageOrder((string) $xmlSheet->pageSetup['pageOrder']); } - $relAttributes = $xmlSheet->pageSetup->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $relAttributes = $xmlSheet->pageSetup->attributes(Namespaces::SCHEMA_OFFICE_DOCUMENT); if (isset($relAttributes['id'])) { $unparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId'] = (string) $relAttributes['id']; } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Properties.php b/src/PhpSpreadsheet/Reader/Xlsx/Properties.php index ffc7ec452c..fb341f71f5 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Properties.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Properties.php @@ -9,8 +9,10 @@ class Properties { + /** @var XmlScanner */ private $securityScanner; + /** @var DocumentProperties */ private $docProps; public function __construct(XmlScanner $securityScanner, DocumentProperties $docProps) @@ -19,30 +21,39 @@ public function __construct(XmlScanner $securityScanner, DocumentProperties $doc $this->docProps = $docProps; } - private function extractPropertyData($propertyData) + /** + * @param mixed $obj + */ + private static function nullOrSimple($obj): ?SimpleXMLElement { - return simplexml_load_string( + return ($obj instanceof SimpleXMLElement) ? $obj : null; + } + + private function extractPropertyData(string $propertyData): ?SimpleXMLElement + { + // okay to omit namespace because everything will be processed by xpath + $obj = simplexml_load_string( $this->securityScanner->scan($propertyData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions() ); + + return self::nullOrSimple($obj); } - public function readCoreProperties($propertyData): void + public function readCoreProperties(string $propertyData): void { $xmlCore = $this->extractPropertyData($propertyData); if (is_object($xmlCore)) { - $xmlCore->registerXPathNamespace('dc', 'http://purl.org/dc/elements/1.1/'); - $xmlCore->registerXPathNamespace('dcterms', 'http://purl.org/dc/terms/'); - $xmlCore->registerXPathNamespace('cp', 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties'); + $xmlCore->registerXPathNamespace('dc', Namespaces::DC_ELEMENTS); + $xmlCore->registerXPathNamespace('dcterms', Namespaces::DC_TERMS); + $xmlCore->registerXPathNamespace('cp', Namespaces::CORE_PROPERTIES2); $this->docProps->setCreator((string) self::getArrayItem($xmlCore->xpath('dc:creator'))); $this->docProps->setLastModifiedBy((string) self::getArrayItem($xmlCore->xpath('cp:lastModifiedBy'))); - $created = (string) self::getArrayItem($xmlCore->xpath('dcterms:created')); //! respect xsi:type - $this->docProps->setCreated($created); - $modified = (string) self::getArrayItem($xmlCore->xpath('dcterms:modified')); //! respect xsi:type - $this->docProps->setModified($modified); //! respect xsi:type + $this->docProps->setCreated((int) strtotime((string) self::getArrayItem($xmlCore->xpath('dcterms:created')))); //! respect xsi:type + $this->docProps->setModified((int) strtotime((string) self::getArrayItem($xmlCore->xpath('dcterms:modified')))); //! respect xsi:type $this->docProps->setTitle((string) self::getArrayItem($xmlCore->xpath('dc:title'))); $this->docProps->setDescription((string) self::getArrayItem($xmlCore->xpath('dc:description'))); $this->docProps->setSubject((string) self::getArrayItem($xmlCore->xpath('dc:subject'))); @@ -51,7 +62,7 @@ public function readCoreProperties($propertyData): void } } - public function readExtendedProperties($propertyData): void + public function readExtendedProperties(string $propertyData): void { $xmlCore = $this->extractPropertyData($propertyData); @@ -65,7 +76,7 @@ public function readExtendedProperties($propertyData): void } } - public function readCustomProperties($propertyData): void + public function readCustomProperties(string $propertyData): void { $xmlCore = $this->extractPropertyData($propertyData); @@ -87,8 +98,12 @@ public function readCustomProperties($propertyData): void } } - private static function getArrayItem(array $array, $key = 0) + /** + * @param array|false $array + * @param mixed $key + */ + private static function getArrayItem($array, $key = 0): ?SimpleXMLElement { - return $array[$key] ?? null; + return is_array($array) ? ($array[$key] ?? null) : null; } } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php b/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php index f6c4792906..9b3e47edd0 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php @@ -3,18 +3,25 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use SimpleXMLElement; class SheetViews extends BaseParserClass { + /** @var SimpleXMLElement */ private $sheetViewXml; + /** @var SimpleXMLElement */ + private $sheetViewAttributes; + + /** @var Worksheet */ private $worksheet; public function __construct(SimpleXMLElement $sheetViewXml, Worksheet $workSheet) { $this->sheetViewXml = $sheetViewXml; + $this->sheetViewAttributes = Xlsx::testSimpleXml($sheetViewXml->attributes()); $this->worksheet = $workSheet; } @@ -30,15 +37,15 @@ public function load(): void if (isset($this->sheetViewXml->pane)) { $this->pane(); } - if (isset($this->sheetViewXml->selection, $this->sheetViewXml->selection['sqref'])) { + if (isset($this->sheetViewXml->selection, $this->sheetViewXml->selection->attributes()->sqref)) { $this->selection(); } } private function zoomScale(): void { - if (isset($this->sheetViewXml['zoomScale'])) { - $zoomScale = (int) ($this->sheetViewXml['zoomScale']); + if (isset($this->sheetViewAttributes->zoomScale)) { + $zoomScale = (int) ($this->sheetViewAttributes->zoomScale); if ($zoomScale <= 0) { // setZoomScale will throw an Exception if the scale is less than or equals 0 // that is OK when manually creating documents, but we should be able to read all documents @@ -48,8 +55,8 @@ private function zoomScale(): void $this->worksheet->getSheetView()->setZoomScale($zoomScale); } - if (isset($this->sheetViewXml['zoomScaleNormal'])) { - $zoomScaleNormal = (int) ($this->sheetViewXml['zoomScaleNormal']); + if (isset($this->sheetViewAttributes->zoomScaleNormal)) { + $zoomScaleNormal = (int) ($this->sheetViewAttributes->zoomScaleNormal); if ($zoomScaleNormal <= 0) { // setZoomScaleNormal will throw an Exception if the scale is less than or equals 0 // that is OK when manually creating documents, but we should be able to read all documents @@ -62,43 +69,43 @@ private function zoomScale(): void private function view(): void { - if (isset($this->sheetViewXml['view'])) { - $this->worksheet->getSheetView()->setView((string) $this->sheetViewXml['view']); + if (isset($this->sheetViewAttributes->view)) { + $this->worksheet->getSheetView()->setView((string) $this->sheetViewAttributes->view); } } private function gridLines(): void { - if (isset($this->sheetViewXml['showGridLines'])) { + if (isset($this->sheetViewAttributes->showGridLines)) { $this->worksheet->setShowGridLines( - self::boolean((string) $this->sheetViewXml['showGridLines']) + self::boolean((string) $this->sheetViewAttributes->showGridLines) ); } } private function headers(): void { - if (isset($this->sheetViewXml['showRowColHeaders'])) { + if (isset($this->sheetViewAttributes->showRowColHeaders)) { $this->worksheet->setShowRowColHeaders( - self::boolean((string) $this->sheetViewXml['showRowColHeaders']) + self::boolean((string) $this->sheetViewAttributes->showRowColHeaders) ); } } private function direction(): void { - if (isset($this->sheetViewXml['rightToLeft'])) { + if (isset($this->sheetViewAttributes->rightToLeft)) { $this->worksheet->setRightToLeft( - self::boolean((string) $this->sheetViewXml['rightToLeft']) + self::boolean((string) $this->sheetViewAttributes->rightToLeft) ); } } private function showZeros(): void { - if (isset($this->sheetViewXml['showZeros'])) { + if (isset($this->sheetViewAttributes->showZeros)) { $this->worksheet->getSheetView()->setShowZeros( - self::boolean((string) $this->sheetViewXml['showZeros']) + self::boolean((string) $this->sheetViewAttributes->showZeros) ); } } @@ -108,17 +115,18 @@ private function pane(): void $xSplit = 0; $ySplit = 0; $topLeftCell = null; + $paneAttributes = $this->sheetViewXml->pane->attributes(); - if (isset($this->sheetViewXml->pane['xSplit'])) { - $xSplit = (int) ($this->sheetViewXml->pane['xSplit']); + if (isset($paneAttributes->xSplit)) { + $xSplit = (int) ($paneAttributes->xSplit); } - if (isset($this->sheetViewXml->pane['ySplit'])) { - $ySplit = (int) ($this->sheetViewXml->pane['ySplit']); + if (isset($paneAttributes->ySplit)) { + $ySplit = (int) ($paneAttributes->ySplit); } - if (isset($this->sheetViewXml->pane['topLeftCell'])) { - $topLeftCell = (string) $this->sheetViewXml->pane['topLeftCell']; + if (isset($paneAttributes->topLeftCell)) { + $topLeftCell = (string) $paneAttributes->topLeftCell; } $this->worksheet->freezePane( @@ -129,10 +137,12 @@ private function pane(): void private function selection(): void { - $sqref = (string) $this->sheetViewXml->selection['sqref']; - $sqref = explode(' ', $sqref); - $sqref = $sqref[0]; - - $this->worksheet->setSelectedCells($sqref); + $attributes = $this->sheetViewXml->selection->attributes(); + if ($attributes !== null) { + $sqref = (string) $attributes->sqref; + $sqref = explode(' ', $sqref); + $sqref = $sqref[0]; + $this->worksheet->setSelectedCells($sqref); + } } } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Styles.php b/src/PhpSpreadsheet/Reader/Xlsx/Styles.php index 80c320655b..a09a7b927d 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Styles.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Styles.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Style\Alignment; use PhpOffice\PhpSpreadsheet\Style\Border; use PhpOffice\PhpSpreadsheet\Style\Borders; @@ -82,7 +83,7 @@ private static function readNumberFormat(NumberFormat $numfmtStyle, SimpleXMLEle if ($numfmtStyleXml->count() === 0) { return; } - $numfmt = $numfmtStyleXml->attributes(); + $numfmt = Xlsx::getAttributes($numfmtStyleXml); if ($numfmt->count() > 0 && isset($numfmt['formatCode'])) { $numfmtStyle->setFormatCode((string) $numfmt['formatCode']); } @@ -97,13 +98,9 @@ public static function readFillStyle(Fill $fillStyle, SimpleXMLElement $fillStyl $fillStyle->setFillType((string) $gradientFill['type']); } $fillStyle->setRotation((float) ($gradientFill['degree'])); - $gradientFill->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); - $fillStyle->getStartColor()->setARGB( - self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color) - ); - $fillStyle->getEndColor()->setARGB( - self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color) - ); + $gradientFill->registerXPathNamespace('sml', Namespaces::MAIN); + $fillStyle->getStartColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color)); + $fillStyle->getEndColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color)); } elseif ($fillStyleXml->patternFill) { $defaultFillStyle = Fill::FILL_NONE; if ($fillStyleXml->patternFill->fgColor) { @@ -270,7 +267,8 @@ public function dxfs($readDataOnly = false) } // Cell Styles if ($this->styleXml->cellStyles) { - foreach ($this->styleXml->cellStyles->cellStyle as $cellStyle) { + foreach ($this->styleXml->cellStyles->cellStyle as $cellStylex) { + $cellStyle = Xlsx::getAttributes($cellStylex); if ((int) ($cellStyle['builtinId']) == 0) { if (isset($this->cellStyles[(int) ($cellStyle['xfId'])])) { // Set default style diff --git a/src/PhpSpreadsheet/ReferenceHelper.php b/src/PhpSpreadsheet/ReferenceHelper.php index d4fced37f2..660a877849 100644 --- a/src/PhpSpreadsheet/ReferenceHelper.php +++ b/src/PhpSpreadsheet/ReferenceHelper.php @@ -581,8 +581,8 @@ public function insertNewBefore($pBefore, $pNumCols, $pNumRows, Worksheet $pShee // Update worksheet: freeze pane if ($pSheet->getFreezePane()) { - $splitCell = $pSheet->getFreezePane(); - $topLeftCell = $pSheet->getTopLeftCell(); + $splitCell = $pSheet->getFreezePane() ?? ''; + $topLeftCell = $pSheet->getTopLeftCell() ?? ''; $splitCell = $this->updateCellReference($splitCell, $pBefore, $pNumCols, $pNumRows); $topLeftCell = $this->updateCellReference($topLeftCell, $pBefore, $pNumCols, $pNumRows); diff --git a/src/PhpSpreadsheet/Shared/Drawing.php b/src/PhpSpreadsheet/Shared/Drawing.php index 08982d84a3..0e9f0fb68a 100644 --- a/src/PhpSpreadsheet/Shared/Drawing.php +++ b/src/PhpSpreadsheet/Shared/Drawing.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheet\Shared; use GdImage; +use SimpleXMLElement; class Drawing { @@ -21,12 +22,13 @@ public static function pixelsToEMU($pixelValue) /** * Convert EMU to pixels. * - * @param int $emuValue Value in EMU + * @param int|SimpleXMLElement $emuValue Value in EMU * * @return int Value in pixels */ public static function EMUToPixels($emuValue) { + $emuValue = (int) $emuValue; if ($emuValue != 0) { return (int) round($emuValue / 9525); } @@ -136,12 +138,13 @@ public static function degreesToAngle($pValue) /** * Convert angle to degrees. * - * @param int $pValue Angle + * @param int|SimpleXMLElement $pValue Angle * * @return int Degrees */ public static function angleToDegrees($pValue) { + $pValue = (int) $pValue; if ($pValue != 0) { return (int) round($pValue / 60000); } diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php index 91e4ccb931..da31fa8b40 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -1928,7 +1928,7 @@ public function removeAutoFilter() /** * Get Freeze Pane. * - * @return string + * @return null|string */ public function getFreezePane() { diff --git a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php index 894ce03acd..6b5fa6fd83 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php @@ -1584,7 +1584,7 @@ private function writePanes(): void return; } - [$column, $row] = Coordinate::indexesFromString($this->phpSheet->getFreezePane()); + [$column, $row] = Coordinate::indexesFromString($this->phpSheet->getFreezePane() ?? ''); $x = $column - 1; $y = $row - 1; diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index 548ea49720..1d8d67d770 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -253,7 +253,7 @@ private function writeSheetViews(XMLWriter $objWriter, PhpspreadsheetWorksheet $ // Pane $pane = ''; if ($pSheet->getFreezePane()) { - [$xSplit, $ySplit] = Coordinate::coordinateFromString($pSheet->getFreezePane()); + [$xSplit, $ySplit] = Coordinate::coordinateFromString($pSheet->getFreezePane() ?? ''); $xSplit = Coordinate::columnIndexFromString($xSplit); --$xSplit; --$ySplit; diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/Namespace.Issue2109bTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/Namespace.Issue2109bTest.php new file mode 100644 index 0000000000..98da11ba73 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/Namespace.Issue2109bTest.php @@ -0,0 +1,89 @@ +listWorkSheetInfo(self::$testbook); + $info0 = $workSheetInfo[0]; + self::assertEquals('Sheet1', $info0['worksheetName']); + self::assertEquals('AF', $info0['lastColumnLetter']); + self::assertEquals(31, $info0['lastColumnIndex']); + self::assertEquals(4, $info0['totalRows']); + self::assertEquals(32, $info0['totalColumns']); + } + + public function testSheetNames(): void + { + $reader = new Xlsx(); + $worksheetNames = $reader->listWorksheetNames(self::$testbook); + self::assertEquals(['Sheet1'], $worksheetNames); + } + + public function testActive(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('Sheet1', $sheet->getTitle()); + self::assertNull($sheet->getFreezePane()); + self::assertNull($sheet->getTopLeftCell()); + self::assertSame('A1', $sheet->getSelectedCells()); + $spreadsheet->disconnectWorksheets(); + } + + private static function getCellValue(Worksheet $sheet, string $cell): string + { + $result = $sheet->getCell($cell)->getValue(); + + return (string) $result; + } + + public function testLoadXlsx(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getSheet(0); + self::assertEquals('Sheet1', $sheet->getTitle()); + $expectedArray = [ + 'A1' => 'Channel Name = Cartoon Network RSE', + 'B2' => 'Event ID', + 'C3' => '2021-05-17 03:00', + 'F4' => 'The Internet', + 'AF3' => '902476', + 'AF4' => '902477', + 'J2' => 'Episode Synopsis', + 'J3' => 'Gumball and Darwin\'s reputation is challenged and they really couldn\'t care less...', + 'J4' => 'Gumball accidentally uploads a video of himself and wants it gone.', + ]; + foreach ($expectedArray as $key => $value) { + self::assertSame($value, self::getCellValue($sheet, $key), "error in cell $key"); + } + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/Namespace.Openpyxl35Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/Namespace.Openpyxl35Test.php new file mode 100644 index 0000000000..16988a07b4 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/Namespace.Openpyxl35Test.php @@ -0,0 +1,111 @@ +listWorkSheetInfo(self::$testbook); + $info0 = $workSheetInfo[0]; + self::assertEquals('Shofar 5781', $info0['worksheetName']); + self::assertEquals('D', $info0['lastColumnLetter']); + self::assertEquals(3, $info0['lastColumnIndex']); + self::assertEquals(30, $info0['totalRows']); + self::assertEquals(4, $info0['totalColumns']); + } + + public function testSheetNames(): void + { + $reader = new Xlsx(); + $worksheetNames = $reader->listWorksheetNames(self::$testbook); + self::assertEquals(['Shofar 5781', 'Shofar 5782', 'Shofar 5783'], $worksheetNames); + } + + public function testActive(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('Shofar 5781', $sheet->getTitle()); + self::assertSame('A2', $sheet->getFreezePane()); + self::assertSame('A2', $sheet->getTopLeftCell()); + self::assertSame('D2', $sheet->getSelectedCells()); + $spreadsheet->disconnectWorksheets(); + } + + private static function getCellValue(Worksheet $sheet, string $cell): string + { + $result = $sheet->getCell($cell)->getValue(); + + return (string) $result; + } + + public function testLoadXlsx(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getSheet(0); + self::assertEquals('Shofar 5781', $sheet->getTitle()); + $expectedArray = [ + 'Shofar 5781' => [ + 'A1' => 'Weekday', + 'B6' => 'August 13', + 'A14' => 'Saturday', + 'C14' => 'Elul 13', + 'D30' => 'N/A', + 'B30' => 'September 6', + ], + 'Shofar 5782' => [ + 'C1' => 'Jewish Date', + 'B6' => 'September 1', + 'A14' => 'Friday', + 'C14' => 'Elul 13', + 'D28' => '', + 'B30' => 'September 25', + ], + 'Shofar 5783' => [ + 'B1' => 'Civil Date', + 'B6' => 'August 22', + 'A14' => 'Wednesday', + 'C14' => 'Elul 13', + 'D30' => 'N/A', + 'B30' => 'September 15', + ], + ]; + foreach ($expectedArray as $sheetName => $array1) { + $sheet = $spreadsheet->getSheetByName($sheetName); + if ($sheet === null) { + self::fail("Unable to find sheet $sheetName"); + } else { + foreach ($array1 as $key => $value) { + self::assertSame($value, self::getCellValue($sheet, $key), "error in sheet $sheetName cell $key"); + } + } + } + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceNonStdTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceNonStdTest.php new file mode 100644 index 0000000000..5002b5d709 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceNonStdTest.php @@ -0,0 +1,179 @@ +listWorkSheetInfo(self::$testbook); + $info0 = $workSheetInfo[0]; + self::assertEquals('SylkTest', $info0['worksheetName']); + self::assertEquals('J', $info0['lastColumnLetter']); + self::assertEquals(9, $info0['lastColumnIndex']); + self::assertEquals(18, $info0['totalRows']); + self::assertEquals(10, $info0['totalColumns']); + } + + public function testSheetNames(): void + { + $reader = new Xlsx(); + $worksheetNames = $reader->listWorksheetNames(self::$testbook); + self::assertEquals(['SylkTest', 'Second'], $worksheetNames); + } + + public function testActive(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('Second', $sheet->getTitle()); + self::assertSame('A2', $sheet->getFreezePane()); + self::assertSame('A2', $sheet->getTopLeftCell()); + self::assertSame('B3', $sheet->getSelectedCells()); + $sheet = $spreadsheet->getSheetByName('SylkTest'); + if ($sheet === null) { + self::fail('Unable to load expected sheet'); + } else { + self::assertNull($sheet->getFreezePane()); + self::assertNull($sheet->getTopLeftCell()); + } + } + + public function testLoadXlsx(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getSheet(0); + self::assertEquals('SylkTest', $sheet->getTitle()); + if (strpos(__FILE__, 'NonStd') !== false) { + self::markTestIncomplete('Not yet ready'); + } + + self::assertEquals('FFFF0000', $sheet->getCell('A1')->getStyle()->getFont()->getColor()->getARGB()); + self::assertEquals(Fill::FILL_PATTERN_GRAY125, $sheet->getCell('A2')->getStyle()->getFill()->getFillType()); + self::assertEquals(Font::UNDERLINE_SINGLE, $sheet->getCell('A4')->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('# ?/?', $sheet->getCell('A11')->getStyle()->getNumberFormat()->getFormatCode()); + 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::assertEquals('=MEDIAN(B6:B8)', $sheet->getCell('B9')->getValue()); + + self::assertEquals(11, $sheet->getCell('E1')->getStyle()->getFont()->getSize()); + self::assertTrue($sheet->getCell('E1')->getStyle()->getFont()->getBold()); + self::assertTrue($sheet->getCell('E1')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_SINGLE, $sheet->getCell('E1')->getStyle()->getFont()->getUnderline()); + 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_SINGLE, $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_THIN, $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_THIN, $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_THIN, $sheet->getCell('C14')->getStyle()->getBorders()->getLeft()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C16')->getStyle()->getBorders()->getTop()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $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_THIN, $sheet->getCell('C18')->getStyle()->getBorders()->getTop()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C18')->getStyle()->getBorders()->getRight()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C18')->getStyle()->getBorders()->getBottom()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C18')->getStyle()->getBorders()->getLeft()->getBorderStyle()); + } + + public function testLoadXlsxSheet2Contents(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getSheet(1); + self::assertEquals('Second', $sheet->getTitle()); + self::assertSame('Hyperlink', $sheet->getCell('B2')->getValue()); + $hyper = $sheet->getCell('B2')->getHyperlink(); + self::assertSame('http://www.example.com/', $hyper->getUrl()); + self::assertSame('Comment', $sheet->getCell('B3')->getValue()); + $comment = $sheet->getComment('B3'); + // Created as "threaded comment" with Excel 365, not quite as expected. + self::assertStringContainsString('This is a comment', (string) $comment); + } + + public function testLoadXlsxSheet2Styles(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getSheet(1); + self::assertEquals('Second', $sheet->getTitle()); + if (strpos(__FILE__, 'NonStd') !== false) { + self::markTestIncomplete('Not yet ready'); + } + self::assertEquals('center', $sheet->getCell('A2')->getStyle()->getAlignment()->getHorizontal()); + self::assertSame('inherit', $sheet->getCell('A2')->getStyle()->getProtection()->getLocked()); + self::assertEquals('top', $sheet->getCell('A3')->getStyle()->getAlignment()->getVertical()); + self::assertSame('unprotected', $sheet->getCell('A3')->getStyle()->getProtection()->getLocked()); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespacePurlTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespacePurlTest.php new file mode 100644 index 0000000000..ddd6ac5d52 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespacePurlTest.php @@ -0,0 +1,62 @@ +canRead($filename); + self::assertTrue($actual); + + $sheets = $reader->listWorksheetNames($filename); + self::assertEquals(['ml_out'], $sheets); + + $actual = $reader->listWorksheetInfo($filename); + $expected = [ + [ + 'worksheetName' => 'ml_out', + 'lastColumnLetter' => 'R', + 'lastColumnIndex' => 17, + 'totalRows' => '76', + 'totalColumns' => 18, + ], + ]; + + self::assertEquals($expected, $actual); + } + + public function testPurlLoad(): void + { + $filename = self::$testbook; + $reader = new Xlsx(); + $spreadsheet = $reader->load($filename); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('ml_out', $sheet->getTitle()); + self::assertSame('Item', $sheet->getCell('A1')->getValue()); + self::assertEquals(97.91, $sheet->getCell('G3')->getValue()); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceStdTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceStdTest.php new file mode 100644 index 0000000000..73e5d5c3ea --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceStdTest.php @@ -0,0 +1,179 @@ +listWorkSheetInfo(self::$testbook); + $info0 = $workSheetInfo[0]; + self::assertEquals('SylkTest', $info0['worksheetName']); + self::assertEquals('J', $info0['lastColumnLetter']); + self::assertEquals(9, $info0['lastColumnIndex']); + self::assertEquals(18, $info0['totalRows']); + self::assertEquals(10, $info0['totalColumns']); + } + + public function testSheetNames(): void + { + $reader = new Xlsx(); + $worksheetNames = $reader->listWorksheetNames(self::$testbook); + self::assertEquals(['SylkTest', 'Second'], $worksheetNames); + } + + public function testActive(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('Second', $sheet->getTitle()); + self::assertSame('A2', $sheet->getFreezePane()); + self::assertSame('A2', $sheet->getTopLeftCell()); + self::assertSame('B3', $sheet->getSelectedCells()); + $sheet = $spreadsheet->getSheetByName('SylkTest'); + if ($sheet === null) { + self::fail('Unable to load expected sheet'); + } else { + self::assertNull($sheet->getFreezePane()); + self::assertNull($sheet->getTopLeftCell()); + } + } + + public function testLoadXlsx(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getSheet(0); + self::assertEquals('SylkTest', $sheet->getTitle()); + if (strpos(__FILE__, 'NonStd') !== false) { + self::markTestIncomplete('Not yet ready'); + } + + self::assertEquals('FFFF0000', $sheet->getCell('A1')->getStyle()->getFont()->getColor()->getARGB()); + self::assertEquals(Fill::FILL_PATTERN_GRAY125, $sheet->getCell('A2')->getStyle()->getFill()->getFillType()); + self::assertEquals(Font::UNDERLINE_SINGLE, $sheet->getCell('A4')->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('# ?/?', $sheet->getCell('A11')->getStyle()->getNumberFormat()->getFormatCode()); + 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::assertEquals('=MEDIAN(B6:B8)', $sheet->getCell('B9')->getValue()); + + self::assertEquals(11, $sheet->getCell('E1')->getStyle()->getFont()->getSize()); + self::assertTrue($sheet->getCell('E1')->getStyle()->getFont()->getBold()); + self::assertTrue($sheet->getCell('E1')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_SINGLE, $sheet->getCell('E1')->getStyle()->getFont()->getUnderline()); + 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_SINGLE, $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_THIN, $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_THIN, $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_THIN, $sheet->getCell('C14')->getStyle()->getBorders()->getLeft()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C16')->getStyle()->getBorders()->getTop()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $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_THIN, $sheet->getCell('C18')->getStyle()->getBorders()->getTop()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C18')->getStyle()->getBorders()->getRight()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C18')->getStyle()->getBorders()->getBottom()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C18')->getStyle()->getBorders()->getLeft()->getBorderStyle()); + } + + public function testLoadXlsxSheet2Contents(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getSheet(1); + self::assertEquals('Second', $sheet->getTitle()); + self::assertSame('Hyperlink', $sheet->getCell('B2')->getValue()); + $hyper = $sheet->getCell('B2')->getHyperlink(); + self::assertSame('http://www.example.com/', $hyper->getUrl()); + self::assertSame('Comment', $sheet->getCell('B3')->getValue()); + $comment = $sheet->getComment('B3'); + // Created as "threaded comment" with Excel 365, not quite as expected. + self::assertStringContainsString('This is a comment', (string) $comment); + } + + public function testLoadXlsxSheet2Styles(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getSheet(1); + self::assertEquals('Second', $sheet->getTitle()); + if (strpos(__FILE__, 'NonStd') !== false) { + self::markTestIncomplete('Not yet ready'); + } + self::assertEquals('center', $sheet->getCell('A2')->getStyle()->getAlignment()->getHorizontal()); + self::assertSame('inherit', $sheet->getCell('A2')->getStyle()->getProtection()->getLocked()); + self::assertEquals('top', $sheet->getCell('A3')->getStyle()->getAlignment()->getVertical()); + self::assertSame('unprotected', $sheet->getCell('A3')->getStyle()->getProtection()->getLocked()); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/WorksheetInfoNamesTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/WorksheetInfoNamesTest.php new file mode 100644 index 0000000000..ed01db251b --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/WorksheetInfoNamesTest.php @@ -0,0 +1,80 @@ +listWorksheetInfo($filename); + + $expected = [ + [ + 'worksheetName' => 'Sheet1', + 'lastColumnLetter' => 'F', + 'lastColumnIndex' => 5, + 'totalRows' => '6', + 'totalColumns' => 6, + ], + ]; + + self::assertEquals($expected, $actual); + } + + public function testListWorksheetInfoNamespace(): void + { + $filename = 'tests/data/Reader/XLSX/namespaces.xlsx'; + $file = 'zip://'; + $file .= $filename; + $file .= '#xl/workbook.xml'; + $data = file_get_contents($file); + // confirm that file contains expected namespaced xml tag + if ($data === false) { + self::fail('Unable to read file'); + } else { + self::assertStringContainsString('listWorksheetInfo($filename); + + $expected = [ + [ + 'worksheetName' => 'transactions', + 'lastColumnLetter' => 'K', + 'lastColumnIndex' => 10, + 'totalRows' => 2, + 'totalColumns' => 11, + ], + ]; + + self::assertEquals($expected, $actual); + } + + public function testListWorksheetNames(): void + { + $filename = 'tests/data/Reader/XLSX/rowColumnAttributeTest.xlsx'; + $reader = new Xlsx(); + $actual = $reader->listWorksheetNames($filename); + + $expected = ['Sheet1']; + + self::assertEquals($expected, $actual); + } + + public function testListWorksheetNamesNamespace(): void + { + $filename = 'tests/data/Reader/XLSX/namespaces.xlsx'; + $reader = new Xlsx(); + $actual = $reader->listWorksheetNames($filename); + + $expected = ['transactions']; + + self::assertEquals($expected, $actual); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/XlsxTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/XlsxTest.php index 4a3a421f76..a3776eb4a9 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/XlsxTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/XlsxTest.php @@ -15,25 +15,6 @@ class XlsxTest extends TestCase { - public function testListWorksheetInfo(): void - { - $filename = 'tests/data/Reader/XLSX/rowColumnAttributeTest.xlsx'; - $reader = new Xlsx(); - $actual = $reader->listWorksheetInfo($filename); - - $expected = [ - [ - 'worksheetName' => 'Sheet1', - 'lastColumnLetter' => 'F', - 'lastColumnIndex' => 5, - 'totalRows' => '6', - 'totalColumns' => 6, - ], - ]; - - self::assertEquals($expected, $actual); - } - public function testLoadXlsxRowColumnAttributes(): void { $filename = 'tests/data/Reader/XLSX/rowColumnAttributeTest.xlsx'; diff --git a/tests/data/Reader/XLSX/issue2109b.xlsx b/tests/data/Reader/XLSX/issue2109b.xlsx new file mode 100644 index 0000000000..632263b18e Binary files /dev/null and b/tests/data/Reader/XLSX/issue2109b.xlsx differ diff --git a/tests/data/Reader/XLSX/namespacenonstd.xlsx b/tests/data/Reader/XLSX/namespacenonstd.xlsx new file mode 100644 index 0000000000..1fe40823eb Binary files /dev/null and b/tests/data/Reader/XLSX/namespacenonstd.xlsx differ diff --git a/tests/data/Reader/XLSX/namespacepurl.xlsx b/tests/data/Reader/XLSX/namespacepurl.xlsx new file mode 100644 index 0000000000..9b2a661dd4 Binary files /dev/null and b/tests/data/Reader/XLSX/namespacepurl.xlsx differ diff --git a/tests/data/Reader/XLSX/namespaces.openpyxl35.xlsx b/tests/data/Reader/XLSX/namespaces.openpyxl35.xlsx new file mode 100644 index 0000000000..631a6702d6 Binary files /dev/null and b/tests/data/Reader/XLSX/namespaces.openpyxl35.xlsx differ diff --git a/tests/data/Reader/XLSX/namespaces.xlsx b/tests/data/Reader/XLSX/namespaces.xlsx new file mode 100644 index 0000000000..3b92ace62a Binary files /dev/null and b/tests/data/Reader/XLSX/namespaces.xlsx differ diff --git a/tests/data/Reader/XLSX/namespacestd.xlsx b/tests/data/Reader/XLSX/namespacestd.xlsx new file mode 100644 index 0000000000..15969e2320 Binary files /dev/null and b/tests/data/Reader/XLSX/namespacestd.xlsx differ