diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 6390e0fc21..a3017bb3f4 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -3735,21 +3735,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.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 @@ -3825,81 +3810,16 @@ parameters: 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 @@ -3915,26 +3835,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 @@ -4535,66 +4435,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 @@ -4605,16 +4445,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 @@ -4820,11 +4650,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 @@ -6680,11 +6505,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 @@ -8395,16 +8215,6 @@ parameters: count: 1 path: tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilterTest.php - - - message: "#^Function PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\getTitleText\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsTitleTest.php - - - - message: "#^Function PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\getTitleText\\(\\) has parameter \\$title with no typehint specified\\.$#" - count: 1 - path: tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsTitleTest.php - - message: "#^Cannot call method setMinimumConditionalFormatValueObject\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\|null\\.$#" count: 1 @@ -8435,31 +8245,6 @@ parameters: count: 1 path: tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php - - - message: "#^Cannot call method getRowHeight\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" - count: 1 - path: tests/PhpSpreadsheetTests/Reader/XlsxTest.php - - - - message: "#^Cannot call method getVisible\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" - count: 1 - path: tests/PhpSpreadsheetTests/Reader/XlsxTest.php - - - - message: "#^Cannot call method getWidth\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" - count: 1 - path: tests/PhpSpreadsheetTests/Reader/XlsxTest.php - - - - message: "#^Cannot call method getVisible\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" - count: 1 - path: tests/PhpSpreadsheetTests/Reader/XlsxTest.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\XlsxTest\\:\\:testStripsWhiteSpaceFromStyleString\\(\\) has parameter \\$string with no typehint specified\\.$#" - count: 1 - path: tests/PhpSpreadsheetTests/Reader/XlsxTest.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\Xml\\\\XmlTest\\:\\:testInvalidSimpleXML\\(\\) has parameter \\$filename with no typehint specified\\.$#" count: 1 diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index a7c71c7373..d81e50d868 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; @@ -58,6 +59,11 @@ class Xlsx extends BaseReader */ private static $theme = null; + /** + * @var ZipArchive + */ + private $zip; + /** * Create a new Xlsx Reader instance. */ @@ -80,10 +86,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(); @@ -92,6 +98,57 @@ public function canRead($pFilename) return $result; } + /** + * @param mixed $value + */ + public static function testSimpleXml($value): SimpleXMLElement + { + return ($value instanceof SimpleXMLElement) ? $value : new SimpleXMLElement(''); + } + + private static function xpathNoFalse(SimpleXmlElement $sxml, string $path): array + { + $result = $sxml->xpath($path); + + return is_array($result) ? $result : []; + } + + 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. * @@ -105,28 +162,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 = $relx->attributes(); + $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) $eleSheet->attributes()['name']; } + } } } @@ -148,57 +201,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 = $relx->attributes(); + $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 = $elex->attributes(); + 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) $eleSheet->attributes()['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($eleSheet->attributes($namespace), 'id')]; $fileWorksheetPath = strpos($fileWorksheet, '/') === 0 ? substr($fileWorksheet, 1) : "$dir/$fileWorksheet"; $xml = new XMLReader(); @@ -213,13 +251,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); @@ -331,107 +370,93 @@ public function load($pFilename) } $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::testSimpleXml($relx->attributes()); 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 = $xmlTheme->attributes(); + $xmlTheme = $xmlTheme->children($drawingNS); + $themeName = (string) $xmlThemeName['name']; + + $colourScheme = self::testSimpleXml($xmlTheme->themeElements->clrScheme->attributes()); + $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 = $xmlColour->sysClr->attributes(); + $themeColours[$themePos] = $xmlColourData['lastClr']; + } elseif (isset($xmlColour->srgbClr)) { + $xmlColourData = $xmlColour->srgbClr->attributes(); + $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 = $relx->attributes(); + $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)) { @@ -445,14 +470,16 @@ public function load($pFilename) $worksheets = []; $macros = $customUI = null; - foreach ($relsWorkbook->Relationship as $ele) { + foreach ($relsWorkbook->Relationship as $elex) { + $ele = $elex->attributes(); 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; @@ -472,25 +499,31 @@ public function load($pFilename) } } - $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::testSimpleXml($xfTag->attributes()); $numFmt = null; if ($xf['numFmtId']) { @@ -517,11 +550,11 @@ public function load($pFilename) $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; @@ -532,7 +565,8 @@ public function load($pFilename) $excel->addCellXf($objStyle); } - foreach ($xmlStyles->cellStyleXfs->xf ?? [] as $xf) { + foreach ($cellXfTags as $xfTag) { + $xf = $xfTag->attributes(); $numFmt = NumberFormat::FORMAT_GENERAL; if ($numFmts && $xf['numFmtId']) { $tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]")); @@ -547,11 +581,11 @@ public function load($pFilename) $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; @@ -568,18 +602,15 @@ public function load($pFilename) $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 = $xmlWorkbookNS->workbookPr->attributes(); + if (isset($attrs1904['date1904'])) { + if (self::boolean((string) $attrs1904['date1904'])) { Date::setExcelCalendar(Date::CALENDAR_MAC_1904); } } @@ -595,13 +626,14 @@ public function load($pFilename) $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::testSimpleXml($eleSheet->attributes()); ++$oldSheetId; // Check if sheet should be skipped - if (isset($this->loadSheetsOnly) && !in_array((string) $eleSheet['name'], $this->loadSheetsOnly)) { + if (isset($this->loadSheetsOnly) && !in_array((string) $eleSheetAttr['name'], $this->loadSheetsOnly)) { ++$countSkippedSheets; $mapSheetId[$oldSheetId] = null; @@ -618,24 +650,21 @@ public function load($pFilename) // 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($eleSheet->attributes($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 (isset($xmlSheet->sheetViews, $xmlSheet->sheetViews->sheetView)) { - $sheetViews = new SheetViews($xmlSheet->sheetViews->sheetView, $docSheet); + if ($xmlSheetNS) { + $xmlSheetMain = $xmlSheetNS->children($mainNS); + if (isset($xmlSheetMain->sheetViews, $xmlSheetMain->sheetViews->sheetView)) { + $sheetViews = new SheetViews($xmlSheetMain->sheetViews->sheetView, $docSheet); $sheetViews->load(); } @@ -646,16 +675,17 @@ public function load($pFilename) ->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 = $c->attributes(); + $r = (string) $cAttr['r']; if ($r == '') { $r = Coordinate::stringFromColumnIndex($rowIndex) . $cIndex; } - $cellDataType = (string) $c['t']; + $cellDataType = (string) $cAttr['t']; $value = null; $calculatedValue = null; @@ -664,7 +694,7 @@ public function load($pFilename) $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; @@ -755,10 +785,10 @@ public function load($pFilename) } // 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; @@ -806,7 +836,7 @@ public function load($pFilename) // 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) { $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['AlternateContents'][] = $alternateContent->asXML(); @@ -820,20 +850,13 @@ public function load($pFilename) // 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); } } @@ -842,20 +865,15 @@ public function load($pFilename) $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 = $elex->attributes(); + 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']; } } @@ -865,27 +883,24 @@ public function load($pFilename) 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)); } } @@ -898,20 +913,17 @@ public function load($pFilename) $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']; @@ -985,53 +997,35 @@ public function load($pFilename) // 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) { @@ -1040,7 +1034,7 @@ public function load($pFilename) $imageData = $imageData[$idx]; - $imageData = $imageData->attributes('urn:schemas-microsoft-com:office:office'); + $imageData = $imageData->attributes(Namespaces::URN_MSOFFICE); $style = self::toCSSArray((string) $shape['style']); $hfImages[(string) $shape['id']] = new HeaderFooterDrawing(); @@ -1066,18 +1060,15 @@ public function load($pFilename) } // 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']); } } @@ -1085,26 +1076,21 @@ public function load($pFilename) $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($drawing->attributes($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'], @@ -1114,30 +1100,26 @@ public function load($pFilename) } } } - $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')); $imageKey = (string) self::getArrayItem( - $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), + $blip->attributes($xmlNamespaceBase), 'embed' ); @@ -1181,10 +1163,10 @@ public function load($pFilename) $width = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cx')); $height = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), '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) $chartRef->attributes($xmlNamespaceBase); $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [ 'fromCoordinate' => $coordinates, @@ -1200,15 +1182,15 @@ public function load($pFilename) 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')); $imageKey = (string) self::getArrayItem( - $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), + $blip->attributes($xmlNamespaceBase), 'embed' ); if (isset($images[$imageKey])) { @@ -1251,10 +1233,10 @@ public function load($pFilename) $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) $chartRef->attributes($xmlNamespaceBase); $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [ 'fromCoordinate' => $fromCoordinate, @@ -1268,7 +1250,7 @@ public function load($pFilename) } } } - if ($relsDrawing === false && $xmlDrawing->count() == 0) { + if (empty($relsDrawing) && $xmlDrawing->count() == 0) { // Save Drawing without rels and children as unparsed $unparsedDrawings[$drawingRelId] = $xmlDrawing->asXML(); } @@ -1277,7 +1259,7 @@ public function load($pFilename) // 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])) { @@ -1287,11 +1269,7 @@ public function load($pFilename) } // 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) { @@ -1301,8 +1279,8 @@ public function load($pFilename) } } - $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) { @@ -1443,11 +1421,11 @@ public function load($pFilename) } } - 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($workbookView->attributes()); // 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) { @@ -1459,43 +1437,43 @@ public function load($pFilename) $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); } } @@ -1505,13 +1483,7 @@ public function load($pFilename) } 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) { @@ -1529,13 +1501,7 @@ public function load($pFilename) 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])) { @@ -1643,7 +1609,7 @@ private static function readStyle(Style $docStyle, $style): void $docStyle->getFill()->setFillType((string) $gradientFill['type']); } $docStyle->getFill()->setRotation((float) ($gradientFill['degree'])); - $gradientFill->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); + $gradientFill->registerXPathNamespace('sml', Namespaces::MAIN); $docStyle->getFill()->getStartColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color)); $docStyle->getFill()->getEndColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color)); } elseif ($style->fill->patternFill) { @@ -1830,7 +1796,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']); @@ -1916,13 +1882,13 @@ 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) $hlinkClick->attributes(Namespaces::SCHEMA_OFFICE_DOCUMENT)['id']; $hyperlink = new Hyperlink( $hyperlinks[$hlinkId], (string) self::getArrayItem($cellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name') @@ -1957,23 +1923,18 @@ private function readProtection(Spreadsheet $excel, SimpleXMLElement $xmlWorkboo } } - 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; } } @@ -1989,23 +1950,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; } } @@ -2044,38 +2000,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 = $rel->attributes(); + 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..8c3b017a03 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 = $elementx->attributes(); + 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 = $hyperlink->attributes(Namespaces::SCHEMA_OFFICE_DOCUMENT); - foreach (Coordinate::extractAllCellReferencesInRange($hyperlink['ref']) as $cellReference) { + $attributes = Xlsx::testSimpleXml($hyperlink->attributes()); + 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..f5d02399a9 --- /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 b6f3c61fc3..6c4f3c5dfe 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,28 +21,31 @@ public function __construct(XmlScanner $securityScanner, DocumentProperties $doc $this->docProps = $docProps; } - private function extractPropertyData($propertyData) + private function extractPropertyData(string $propertyData): ?SimpleXMLElement { - return simplexml_load_string( + // okay to omit namespace because everything will be processed by xpath + $obj = simplexml_load_string( $this->securityScanner->scan($propertyData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions() ); + + return ($obj instanceof SimpleXMLElement) ? $obj : null; } - 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'))); - $this->docProps->setCreated(strtotime(self::getArrayItem($xmlCore->xpath('dcterms:created')))); //! respect xsi:type - $this->docProps->setModified(strtotime(self::getArrayItem($xmlCore->xpath('dcterms: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'))); @@ -49,7 +54,7 @@ public function readCoreProperties($propertyData): void } } - public function readExtendedProperties($propertyData): void + public function readExtendedProperties(string $propertyData): void { $xmlCore = $this->extractPropertyData($propertyData); @@ -63,7 +68,7 @@ public function readExtendedProperties($propertyData): void } } - public function readCustomProperties($propertyData): void + public function readCustomProperties(string $propertyData): void { $xmlCore = $this->extractPropertyData($propertyData); @@ -85,8 +90,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 c43ccc971f..a67e88725c 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Styles.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Styles.php @@ -93,7 +93,7 @@ private static function readFillStyle(Fill $fillStyle, SimpleXMLElement $fillSty $fillStyle->setFillType((string) $gradientFill['type']); } $fillStyle->setRotation((float) ($gradientFill['degree'])); - $gradientFill->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); + $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) { @@ -253,7 +253,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 = $cellStylex->attributes(); 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/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php index 2ecd210c6a..25fa5dc795 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -1941,7 +1941,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 7051ab2df4..7724b4b8f2 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php @@ -1577,7 +1577,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 3978eb6f04..0d36ef228f 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -252,7 +252,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/ChartsTitleTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsTitleTest.php index 5e1711390d..4d3d6aeace 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsTitleTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsTitleTest.php @@ -1,11 +1,14 @@ getCaption()) { return null; diff --git a/tests/PhpSpreadsheetTests/Reader/CondNumFmtTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/CondNumFmtTest.php similarity index 100% rename from tests/PhpSpreadsheetTests/Reader/CondNumFmtTest.php rename to tests/PhpSpreadsheetTests/Reader/Xlsx/CondNumFmtTest.php diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceNonStdTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceNonStdTest.php new file mode 100644 index 0000000000..be508cf47b --- /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..6a2cdc51f3 --- /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..c85c387d4b --- /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/OddColumnReadFilter.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/OddColumnReadFilter.php similarity index 84% rename from tests/PhpSpreadsheetTests/Reader/OddColumnReadFilter.php rename to tests/PhpSpreadsheetTests/Reader/Xlsx/OddColumnReadFilter.php index b937331488..028f2fa32e 100644 --- a/tests/PhpSpreadsheetTests/Reader/OddColumnReadFilter.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/OddColumnReadFilter.php @@ -1,6 +1,6 @@ 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/Xlsx2Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/Xlsx2Test.php similarity index 99% rename from tests/PhpSpreadsheetTests/Reader/Xlsx2Test.php rename to tests/PhpSpreadsheetTests/Reader/Xlsx/Xlsx2Test.php index cef721211f..8212f089a9 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx2Test.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/Xlsx2Test.php @@ -1,6 +1,6 @@ listWorksheetInfo($filename); - - $expected = [ - [ - 'worksheetName' => 'Sheet1', - 'lastColumnLetter' => 'F', - 'lastColumnIndex' => 5, - 'totalRows' => '6', - 'totalColumns' => 6, - ], - ]; + return ($rowDim === null) ? -1 : $rowDim->getRowHeight(); + } - self::assertEquals($expected, $actual); + private static function getRowVisible(?RowDimension $rowDim): bool + { + return ($rowDim === null) ? true : $rowDim->getVisible(); + } + + private static function getColumnWidth(?ColumnDimension $colDim): float + { + return ($colDim === null) ? -1 : $colDim->getWidth(); + } + + private static function getColumnVisible(?ColumnDimension $colDim): bool + { + return ($colDim === null) ? true : $colDim->getVisible(); } public function testLoadXlsxRowColumnAttributes(): void @@ -82,20 +85,20 @@ public function testLoadXlsxRowColumnAttributes(): void $worksheet = $spreadsheet->getActiveSheet(); for ($row = 1; $row <= 4; ++$row) { - self::assertEquals($row * 5 + 10, floor($worksheet->getRowDimension($row)->getRowHeight())); + self::assertEquals($row * 5 + 10, floor(self::getRowHeight($worksheet->getRowDimension($row)))); } - self::assertFalse($worksheet->getRowDimension(5)->getVisible()); + self::assertFalse(self::getRowVisible($worksheet->getRowDimension(5))); for ($column = 1; $column <= 4; ++$column) { $columnAddress = Coordinate::stringFromColumnIndex($column); self::assertEquals( $column * 2 + 2, - floor($worksheet->getColumnDimension($columnAddress)->getWidth()) + floor(self::getColumnWidth($worksheet->getColumnDimension($columnAddress))) ); } - self::assertFalse($worksheet->getColumnDimension('E')->getVisible()); + self::assertFalse(self::getColumnVisible($worksheet->getColumnDimension('E'))); } public function testLoadXlsxWithStyles(): void @@ -252,7 +255,7 @@ public function testLoadSaveWithEmptyDrawings(): void * * @dataProvider providerStripsWhiteSpaceFromStyleString */ - public function testStripsWhiteSpaceFromStyleString($string): void + public function testStripsWhiteSpaceFromStyleString(string $string): void { $string = Xlsx::stripWhiteSpaceFromStyleString($string); self::assertEquals(preg_match('/\s/', $string), 0); 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.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