From 7694ad8f658d303e700b37d5dbb14335f7f07c77 Mon Sep 17 00:00:00 2001 From: Owen Leibman Date: Fri, 18 Jun 2021 20:45:30 -0700 Subject: [PATCH 1/3] Xlsx Reader Better Namespace Handling Phase 1 Try2 This is a replacement for #2088, which has run into merge conflicts. I will close that PR in the near future, however the comments in that PR may prove useful for this one. While that PR has been in draft status all along, I am marking this one as ready. I will gladly add additional tests (and, of course, make code changes) that anyone has to suggest, but, with my most recent test files which I will describe in a separate comment, I have no further ideas on useful additions. As mentioned in the earlier ticket, this is a risky change. But, as has been demonstrated, delaying it comes with its own set of risks. It would be helpful to have a temporary moratorium on changes to Reader/Xlsx until this change is merged. The original commit message follows. There have been a number of issues concerning the handling of legitimate but unexpected namespace prefixes in Xlsx spreadsheets created by software other than Excel and PhpSpreadsheet/PhpExcel.I have studied them, but, till now, have not had a good idea on how to act on them. A recent comment https://github.com/PHPOffice/PhpSpreadsheet/issues/860#issuecomment-824926224 in issue #860 by @IMSoP has triggered an idea about how to proceed. Gnumeric Reader was recently changed to handle namespaces better. Using that as a model, this PR begins the process of doing the same for Xlsx. Xlsx is much larger and more complicated than Gnumeric, hence the need to tackle it in multiple phases. I believe that this PR handles all of: - listWorkSheetNames - listWorkSheetInfo. Note that there was a bug in this function which would cause it to count only used columns rather than all columns. That bug is corrected. - active sheet - selected cell and top left cell - cell content (formulas, numbers, text) - hyperlinks - comments (partial - see below) This PR does not address: - styles - images and charts - VBA and ribbons - many other items, I'm sure The issue for non-standard namespacing till now has been the use of unexpected prefixes. While I was working on this change, @Lambik introduced issue #2067 PR #2068 which introduced a completely different problem - the use of unexpected URLs. That PR and the issue associated with it were quite well documented, including the supplying of a test file and tests for it. I asked if I could take a look to see if it could be integrated with my change, and the result seems to be yes, so those changes are also part of this PR. While adding a comment to my test file, I discovered that Microsoft had added "threaded comments" as a new feature. I believe these are not yet supported by PhpSpreadsheet, and I am not going to add it, at least not now. I believe that, among other things, this will make identifying the author of a comment more difficult. Although there are a number of Phpstan baseline changes as part of this PR, I did not attempt to resolve all Phpstan reports for Reader/Xlsx. Nor did I do anything to increase coverage. This change is already large and complex enough without those efforts. I will add more detail as comments after I push this change. --- phpstan-baseline.neon | 195 ----- src/PhpSpreadsheet/Reader/Xlsx.php | 802 +++++++++--------- src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php | 15 +- src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php | 76 ++ src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php | 2 +- src/PhpSpreadsheet/Reader/Xlsx/Properties.php | 43 +- src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php | 62 +- src/PhpSpreadsheet/Reader/Xlsx/Styles.php | 16 +- src/PhpSpreadsheet/ReferenceHelper.php | 4 +- src/PhpSpreadsheet/Worksheet/Worksheet.php | 2 +- src/PhpSpreadsheet/Writer/Xls/Worksheet.php | 2 +- src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php | 2 +- .../Reader/Xlsx/Namespace.Issue2109bTest.php | 89 ++ .../Reader/Xlsx/Namespace.Openpyxl35Test.php | 111 +++ .../Reader/Xlsx/NamespaceNonStdTest.php | 179 ++++ .../Reader/Xlsx/NamespacePurlTest.php | 62 ++ .../Reader/Xlsx/NamespaceStdTest.php | 179 ++++ .../Reader/Xlsx/WorksheetInfoNamesTest.php | 80 ++ .../Reader/Xlsx/XlsxTest.php | 19 - tests/data/Reader/XLSX/issue2109b.xlsx | Bin 0 -> 6925 bytes tests/data/Reader/XLSX/namespacenonstd.xlsx | Bin 0 -> 13207 bytes tests/data/Reader/XLSX/namespacepurl.xlsx | Bin 0 -> 21205 bytes .../Reader/XLSX/namespaces.openpyxl35.xlsx | Bin 0 -> 8639 bytes tests/data/Reader/XLSX/namespaces.xlsx | Bin 0 -> 8582 bytes tests/data/Reader/XLSX/namespacestd.xlsx | Bin 0 -> 15773 bytes 25 files changed, 1244 insertions(+), 696 deletions(-) create mode 100644 src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php create mode 100644 tests/PhpSpreadsheetTests/Reader/Xlsx/Namespace.Issue2109bTest.php create mode 100644 tests/PhpSpreadsheetTests/Reader/Xlsx/Namespace.Openpyxl35Test.php create mode 100644 tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceNonStdTest.php create mode 100644 tests/PhpSpreadsheetTests/Reader/Xlsx/NamespacePurlTest.php create mode 100644 tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceStdTest.php create mode 100644 tests/PhpSpreadsheetTests/Reader/Xlsx/WorksheetInfoNamesTest.php create mode 100644 tests/data/Reader/XLSX/issue2109b.xlsx create mode 100644 tests/data/Reader/XLSX/namespacenonstd.xlsx create mode 100644 tests/data/Reader/XLSX/namespacepurl.xlsx create mode 100644 tests/data/Reader/XLSX/namespaces.openpyxl35.xlsx create mode 100644 tests/data/Reader/XLSX/namespaces.xlsx create mode 100644 tests/data/Reader/XLSX/namespacestd.xlsx diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 9dee7fae19..0343a1a627 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -2935,21 +2935,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Reader/Xls/RC4.php - - - message: "#^Cannot access property \\$Relationship on SimpleXMLElement\\|false\\.$#" - count: 12 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot access property \\$sheets on SimpleXMLElement\\|false\\.$#" - count: 6 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot call method registerXPathNamespace\\(\\) on SimpleXMLElement\\|false\\.$#" - count: 4 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToBoolean\\(\\) has no return typehint specified\\.$#" count: 1 @@ -3020,86 +3005,16 @@ parameters: count: 1 path: src/PhpSpreadsheet/Reader/Xlsx.php - - - message: "#^Offset 'name' does not exist on SimpleXMLElement\\|null\\.$#" - count: 2 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot call method xpath\\(\\) on SimpleXMLElement\\|false\\.$#" - count: 4 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - message: "#^Parameter \\#1 \\$is of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:parseRichText\\(\\) expects SimpleXMLElement\\|null, object given\\.$#" count: 1 path: src/PhpSpreadsheet/Reader/Xlsx.php - - - message: "#^Parameter \\#1 \\$styleXml of class PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles constructor expects SimpleXMLElement, SimpleXMLElement\\|false given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot access property \\$workbookPr on SimpleXMLElement\\|false\\.$#" - count: 2 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Parameter \\#2 \\$xmlWorkbook of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readProtection\\(\\) expects SimpleXMLElement, SimpleXMLElement\\|false given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Parameter \\#1 \\$relsWorksheet of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Hyperlinks\\:\\:readHyperlinks\\(\\) expects SimpleXMLElement, SimpleXMLElement\\|false given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot access property \\$authors on SimpleXMLElement\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot access property \\$commentList on SimpleXMLElement\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" - count: 2 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - message: "#^Negated boolean expression is always true\\.$#" count: 1 path: src/PhpSpreadsheet/Reader/Xlsx.php - - - message: "#^Cannot access property \\$drawing on SimpleXMLElement\\|false\\.$#" - count: 2 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot call method children\\(\\) on SimpleXMLElement\\|false\\.$#" - count: 2 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot call method count\\(\\) on SimpleXMLElement\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot call method asXML\\(\\) on SimpleXMLElement\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot access property \\$definedNames on SimpleXMLElement\\|false\\.$#" - count: 4 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" count: 1 @@ -3115,26 +3030,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 @@ -3275,11 +3170,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Reader/Xlsx.php - - - message: "#^Offset 'id' does not exist on SimpleXMLElement\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readFormControlProperties\\(\\) has parameter \\$dir with no typehint specified\\.$#" count: 1 @@ -3710,66 +3600,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 @@ -3780,16 +3610,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 @@ -3820,11 +3640,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php - - - message: "#^Cannot call method count\\(\\) on SimpleXMLElement\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:readStyle\\(\\) has parameter \\$style with no typehint specified\\.$#" count: 1 @@ -3935,11 +3750,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 @@ -5645,11 +5455,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Worksheet/Worksheet.php - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:getFreezePane\\(\\) should return string but returns string\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Worksheet/Worksheet.php - - message: "#^Parameter \\#1 \\$row of method PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Cells\\:\\:removeRow\\(\\) expects string, int given\\.$#" count: 2 diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index da4a80d1a2..593858e3ba 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -13,6 +13,7 @@ use PhpOffice\PhpSpreadsheet\Reader\Xlsx\ConditionalStyles; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\DataValidations; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Hyperlinks; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\PageSetup; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Properties as PropertyReader; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViewOptions; @@ -54,6 +55,11 @@ class Xlsx extends BaseReader */ private static $theme = null; + /** + * @var ZipArchive + */ + private $zip; + /** * Create a new Xlsx Reader instance. */ @@ -76,10 +82,10 @@ public function canRead($pFilename) File::assertFile($pFilename); $result = false; - $zip = new ZipArchive(); + $this->zip = $zip = new ZipArchive(); if ($zip->open($pFilename) === true) { - $workbookBasename = $this->getWorkbookBaseName($zip); + [$workbookBasename] = $this->getWorkbookBaseName(); $result = !empty($workbookBasename); $zip->close(); @@ -88,6 +94,71 @@ public function canRead($pFilename) return $result; } + /** + * @param mixed $value + */ + public static function testSimpleXml($value): SimpleXMLElement + { + return ($value instanceof SimpleXMLElement) ? $value : new SimpleXMLElement(''); + } + + public static function getAttributes(?SimpleXMLElement $value, string $ns = ''): SimpleXMLElement + { + return self::testSimpleXml($value === null ? $value : $value->attributes($ns)); + } + + // Phpstan thinks, correctly, that xpath can return false. + // Scrutinizer thinks it can't. + // Sigh. + private static function xpathNoFalse(SimpleXmlElement $sxml, string $path): array + { + return self::falseToArray($sxml->xpath($path)); + } + + /** + * @param mixed $value + */ + public static function falseToArray($value): array + { + return is_array($value) ? $value : []; + } + + private function loadZip(string $filename, string $ns = ''): SimpleXMLElement + { + $contents = $this->getFromZipArchive($this->zip, $filename); + $rels = simplexml_load_string( + $this->securityScanner->scan($contents), + 'SimpleXMLElement', + Settings::getLibXmlLoaderOptions(), + $ns + ); + + return self::testSimpleXml($rels); + } + + // This function is just to identify cases where I'm not sure + // why empty namespace is required. + private function loadZipNonamespace(string $filename, string $ns): SimpleXMLElement + { + $contents = $this->getFromZipArchive($this->zip, $filename); + $rels = simplexml_load_string( + $this->securityScanner->scan($contents), + 'SimpleXMLElement', + Settings::getLibXmlLoaderOptions(), + ($ns === '' ? $ns : '') + ); + + return self::testSimpleXml($rels); + } + + private const REL_TO_MAIN = [ + Namespaces::PURL_OFFICE_DOCUMENT => Namespaces::PURL_MAIN, + ]; + + private const REL_TO_DRAWING = [ + Namespaces::PURL_RELATIONSHIPS => Namespaces::PURL_DRAWING, + ]; + /** * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object. * @@ -101,28 +172,24 @@ public function listWorksheetNames($pFilename) $worksheetNames = []; - $zip = new ZipArchive(); + $this->zip = $zip = new ZipArchive(); $zip->open($pFilename); // The files we're looking at here are small enough that simpleXML is more efficient than XMLReader - //~ http://schemas.openxmlformats.org/package/2006/relationships"); - $rels = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels')) - ); - foreach ($rels->Relationship as $rel) { - switch ($rel['Type']) { - case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument': - //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" - $xmlWorkbook = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "{$rel['Target']}")) - ); - - if ($xmlWorkbook->sheets) { - foreach ($xmlWorkbook->sheets->sheet as $eleSheet) { - // Check if sheet should be skipped - $worksheetNames[] = (string) $eleSheet['name']; - } + $rels = $this->loadZip('_rels/.rels', Namespaces::RELATIONSHIPS); + foreach ($rels->Relationship as $relx) { + $rel = self::getAttributes($relx); + $relType = (string) $rel['Type']; + $mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN; + if ($mainNS !== '') { + $xmlWorkbook = $this->loadZip($rel['Target'], $mainNS); + + if ($xmlWorkbook->sheets) { + foreach ($xmlWorkbook->sheets->sheet as $eleSheet) { + // Check if sheet should be skipped + $worksheetNames[] = (string) self::getAttributes($eleSheet)['name']; } + } } } @@ -144,57 +211,42 @@ public function listWorksheetInfo($pFilename) $worksheetInfo = []; - $zip = new ZipArchive(); + $this->zip = $zip = new ZipArchive(); $zip->open($pFilename); - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $rels = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels')), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - foreach ($rels->Relationship as $rel) { - if ($rel['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument') { - $dir = dirname($rel['Target']); - - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorkbook = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, "$dir/_rels/" . basename($rel['Target']) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - $relsWorkbook->registerXPathNamespace('rel', 'http://schemas.openxmlformats.org/package/2006/relationships'); + $rels = $this->loadZip('_rels/.rels', Namespaces::RELATIONSHIPS); + foreach ($rels->Relationship as $relx) { + $rel = self::getAttributes($relx); + $relType = (string) $rel['Type']; + $mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN; + if ($mainNS !== '') { + $relTarget = (string) $rel['Target']; + $dir = dirname($relTarget); + $namespace = dirname($relType); + $relsWorkbook = $this->loadZip("$dir/_rels/" . basename($relTarget) . '.rels', ''); $worksheets = []; - foreach ($relsWorkbook->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet') { + foreach ($relsWorkbook->Relationship as $elex) { + $ele = self::getAttributes($elex); + if ((string) $ele['Type'] === "$namespace/worksheet") { $worksheets[(string) $ele['Id']] = $ele['Target']; } } - //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" - $xmlWorkbook = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, "{$rel['Target']}") - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $xmlWorkbook = $this->loadZip($relTarget, $mainNS); if ($xmlWorkbook->sheets) { - $dir = dirname($rel['Target']); + $dir = dirname($relTarget); /** @var SimpleXMLElement $eleSheet */ foreach ($xmlWorkbook->sheets->sheet as $eleSheet) { $tmpInfo = [ - 'worksheetName' => (string) $eleSheet['name'], + 'worksheetName' => (string) self::getAttributes($eleSheet)['name'], 'lastColumnLetter' => 'A', 'lastColumnIndex' => 0, 'totalRows' => 0, 'totalColumns' => 0, ]; - $fileWorksheet = $worksheets[(string) self::getArrayItem($eleSheet->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')]; + $fileWorksheet = $worksheets[(string) self::getArrayItem(self::getAttributes($eleSheet, $namespace), 'id')]; $fileWorksheetPath = strpos($fileWorksheet, '/') === 0 ? substr($fileWorksheet, 1) : "$dir/$fileWorksheet"; $xml = new XMLReader(); @@ -209,13 +261,14 @@ public function listWorksheetInfo($pFilename) $currCells = 0; while ($xml->read()) { - if ($xml->name == 'row' && $xml->nodeType == XMLReader::ELEMENT) { + if ($xml->localName == 'row' && $xml->nodeType == XMLReader::ELEMENT && $xml->namespaceURI === $mainNS) { $row = $xml->getAttribute('r'); $tmpInfo['totalRows'] = $row; $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells); $currCells = 0; - } elseif ($xml->name == 'c' && $xml->nodeType == XMLReader::ELEMENT) { - ++$currCells; + } elseif ($xml->localName == 'c' && $xml->nodeType == XMLReader::ELEMENT && $xml->namespaceURI === $mainNS) { + $cell = $xml->getAttribute('r'); + $currCells = $cell ? max($currCells, Coordinate::indexesFromString($cell)[0]) : ($currCells + 1); } } $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells); @@ -309,10 +362,8 @@ private function getFromZipArchive(ZipArchive $archive, $fileName = '') /** * Loads Spreadsheet from file. - * - * @return Spreadsheet */ - public function load(string $pFilename, int $flags = 0) + public function load(string $pFilename, int $flags = 0): Spreadsheet { File::assertFile($pFilename); $this->processFlags($flags); @@ -326,107 +377,93 @@ public function load(string $pFilename, int $flags = 0) } $unparsedLoadedData = []; - $zip = new ZipArchive(); + $this->zip = $zip = new ZipArchive(); $zip->open($pFilename); // Read the theme first, because we need the colour scheme when reading the styles - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $workbookBasename = $this->getWorkbookBaseName($zip); - $wbRels = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "xl/_rels/${workbookBasename}.rels")), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - foreach ($wbRels->Relationship as $rel) { + [$workbookBasename, $xmlNamespaceBase] = $this->getWorkbookBaseName(); + $wbRels = $this->loadZip("xl/_rels/${workbookBasename}.rels", Namespaces::RELATIONSHIPS); + foreach ($wbRels->Relationship as $relx) { + $rel = self::getAttributes($relx); switch ($rel['Type']) { - case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme': + case "$xmlNamespaceBase/theme": $themeOrderArray = ['lt1', 'dk1', 'lt2', 'dk2']; $themeOrderAdditional = count($themeOrderArray); - - $xmlTheme = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "xl/{$rel['Target']}")), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - if (is_object($xmlTheme)) { - $xmlThemeName = $xmlTheme->attributes(); - $xmlTheme = $xmlTheme->children('http://schemas.openxmlformats.org/drawingml/2006/main'); - $themeName = (string) $xmlThemeName['name']; - - $colourScheme = $xmlTheme->themeElements->clrScheme->attributes(); - $colourSchemeName = (string) $colourScheme['name']; - $colourScheme = $xmlTheme->themeElements->clrScheme->children('http://schemas.openxmlformats.org/drawingml/2006/main'); - - $themeColours = []; - foreach ($colourScheme as $k => $xmlColour) { - $themePos = array_search($k, $themeOrderArray); - if ($themePos === false) { - $themePos = $themeOrderAdditional++; - } - if (isset($xmlColour->sysClr)) { - $xmlColourData = $xmlColour->sysClr->attributes(); - $themeColours[$themePos] = $xmlColourData['lastClr']; - } elseif (isset($xmlColour->srgbClr)) { - $xmlColourData = $xmlColour->srgbClr->attributes(); - $themeColours[$themePos] = $xmlColourData['val']; - } + $drawingNS = self::REL_TO_DRAWING[$xmlNamespaceBase] ?? Namespaces::DRAWINGML; + + $xmlTheme = $this->loadZip("xl/{$rel['Target']}", $drawingNS); + $xmlThemeName = self::getAttributes($xmlTheme); + $xmlTheme = $xmlTheme->children($drawingNS); + $themeName = (string) $xmlThemeName['name']; + + $colourScheme = self::getAttributes($xmlTheme->themeElements->clrScheme); + $colourSchemeName = (string) $colourScheme['name']; + $colourScheme = $xmlTheme->themeElements->clrScheme->children($drawingNS); + + $themeColours = []; + foreach ($colourScheme as $k => $xmlColour) { + $themePos = array_search($k, $themeOrderArray); + if ($themePos === false) { + $themePos = $themeOrderAdditional++; + } + if (isset($xmlColour->sysClr)) { + $xmlColourData = self::getAttributes($xmlColour->sysClr); + $themeColours[$themePos] = $xmlColourData['lastClr']; + } elseif (isset($xmlColour->srgbClr)) { + $xmlColourData = self::getAttributes($xmlColour->srgbClr); + $themeColours[$themePos] = $xmlColourData['val']; } - self::$theme = new Xlsx\Theme($themeName, $colourSchemeName, $themeColours); } + self::$theme = new Xlsx\Theme($themeName, $colourSchemeName, $themeColours); break; } } - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $rels = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels')), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $rels = $this->loadZip('_rels/.rels', Namespaces::RELATIONSHIPS); $propertyReader = new PropertyReader($this->securityScanner, $excel->getProperties()); - foreach ($rels->Relationship as $rel) { - switch ($rel['Type']) { - case 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties': - $propertyReader->readCoreProperties($this->getFromZipArchive($zip, "{$rel['Target']}")); + foreach ($rels->Relationship as $relx) { + $rel = self::getAttributes($relx); + $relType = (string) $rel['Type']; + $mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN; + switch ($relType) { + case Namespaces::CORE_PROPERTIES: + $propertyReader->readCoreProperties($this->getFromZipArchive($zip, $rel['Target'])); break; - case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties': - $propertyReader->readExtendedProperties($this->getFromZipArchive($zip, "{$rel['Target']}")); + case "$xmlNamespaceBase/extended-properties": + $propertyReader->readExtendedProperties($this->getFromZipArchive($zip, $rel['Target'])); break; - case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties': - $propertyReader->readCustomProperties($this->getFromZipArchive($zip, "{$rel['Target']}")); + case "$xmlNamespaceBase/custom-properties": + $propertyReader->readCustomProperties($this->getFromZipArchive($zip, $rel['Target'])); break; //Ribbon - case 'http://schemas.microsoft.com/office/2006/relationships/ui/extensibility': + case Namespaces::EXTENSIBILITY: $customUI = $rel['Target']; if ($customUI !== null) { $this->readRibbon($excel, $customUI, $zip); } break; - case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument': + case "$xmlNamespaceBase/officeDocument": $dir = dirname($rel['Target']); - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorkbook = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/_rels/" . basename($rel['Target']) . '.rels')), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - $relsWorkbook->registerXPathNamespace('rel', 'http://schemas.openxmlformats.org/package/2006/relationships'); + + // Do not specify namespace in next stmt - do it in Xpath + $relsWorkbook = $this->loadZip("$dir/_rels/" . basename($rel['Target']) . '.rels', ''); + $relsWorkbook->registerXPathNamespace('rel', Namespaces::RELATIONSHIPS); $sharedStrings = []; - $xpath = self::getArrayItem($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings']")); + $relType = "rel:Relationship[@Type='" + //. Namespaces::SHARED_STRINGS + . "$xmlNamespaceBase/sharedStrings" + . "']"; + $xpath = self::getArrayItem($relsWorkbook->xpath($relType)); + if ($xpath) { - //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" - $xmlStrings = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$xpath[Target]")), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $xmlStrings = $this->loadZip("$dir/$xpath[Target]", $mainNS); if (isset($xmlStrings->si)) { foreach ($xmlStrings->si as $val) { if (isset($val->t)) { @@ -440,14 +477,16 @@ public function load(string $pFilename, int $flags = 0) $worksheets = []; $macros = $customUI = null; - foreach ($relsWorkbook->Relationship as $ele) { + foreach ($relsWorkbook->Relationship as $elex) { + $ele = self::getAttributes($elex); switch ($ele['Type']) { - case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet': + case Namespaces::WORKSHEET: + case Namespaces::PURL_WORKSHEET: $worksheets[(string) $ele['Id']] = $ele['Target']; break; // a vbaProject ? (: some macros) - case 'http://schemas.microsoft.com/office/2006/relationships/vbaProject': + case Namespaces::VBA: $macros = $ele['Target']; break; @@ -467,25 +506,31 @@ public function load(string $pFilename, int $flags = 0) } } - $xpath = self::getArrayItem($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles']")); - //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" - $xmlStyles = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$xpath[Target]")), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $relType = "rel:Relationship[@Type='" + . "$xmlNamespaceBase/styles" + . "']"; + $xpath = self::getArrayItem(self::xpathNoFalse($relsWorkbook, $relType)); + // I think Nonamespace is okay because I'm using xpath. + $xmlStyles = $this->loadZipNonamespace("$dir/$xpath[Target]", $mainNS); + $xmlStyles->registerXPathNamespace('smm', Namespaces::MAIN); + $fills = self::xpathNoFalse($xmlStyles, 'smm:fills/smm:fill'); + $fonts = self::xpathNoFalse($xmlStyles, 'smm:fonts/smm:font'); + $borders = self::xpathNoFalse($xmlStyles, 'smm:borders/smm:border'); + $xfTags = self::xpathNoFalse($xmlStyles, 'smm:cellXfs/smm:xf'); + $cellXfTags = self::xpathNoFalse($xmlStyles, 'smm:cellStyleXfs/smm:xf'); $styles = []; $cellStyles = []; $numFmts = null; - if ($xmlStyles && $xmlStyles->numFmts[0]) { + if (/*$xmlStyles && */ $xmlStyles->numFmts[0]) { $numFmts = $xmlStyles->numFmts[0]; } if (isset($numFmts) && ($numFmts !== null)) { - $numFmts->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); + $numFmts->registerXPathNamespace('sml', $mainNS); } - if (!$this->readDataOnly && $xmlStyles) { - foreach ($xmlStyles->cellXfs->xf as $xf) { + if (!$this->readDataOnly/* && $xmlStyles*/) { + foreach ($xfTags as $xfTag) { + $xf = self::getAttributes($xfTag); $numFmt = null; if ($xf['numFmtId']) { @@ -512,11 +557,11 @@ public function load(string $pFilename, int $flags = 0) $style = (object) [ 'numFmt' => $numFmt ?? NumberFormat::FORMAT_GENERAL, - 'font' => $xmlStyles->fonts->font[(int) ($xf['fontId'])], - 'fill' => $xmlStyles->fills->fill[(int) ($xf['fillId'])], - 'border' => $xmlStyles->borders->border[(int) ($xf['borderId'])], - 'alignment' => $xf->alignment, - 'protection' => $xf->protection, + 'font' => $fonts[(int) ($xf['fontId'])], + 'fill' => $fills[(int) ($xf['fillId'])], + 'border' => $borders[(int) ($xf['borderId'])], + 'alignment' => $xfTag->alignment, + 'protection' => $xfTag->protection, 'quotePrefix' => $quotePrefix, ]; $styles[] = $style; @@ -527,7 +572,8 @@ public function load(string $pFilename, int $flags = 0) $excel->addCellXf($objStyle); } - foreach ($xmlStyles->cellStyleXfs->xf ?? [] as $xf) { + foreach ($cellXfTags as $xfTag) { + $xf = self::getAttributes($xfTag); $numFmt = NumberFormat::FORMAT_GENERAL; if ($numFmts && $xf['numFmtId']) { $tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]")); @@ -542,11 +588,11 @@ public function load(string $pFilename, int $flags = 0) $cellStyle = (object) [ 'numFmt' => $numFmt, - 'font' => $xmlStyles->fonts->font[(int) ($xf['fontId'])], - 'fill' => $xmlStyles->fills->fill[(int) ($xf['fillId'])], - 'border' => $xmlStyles->borders->border[(int) ($xf['borderId'])], - 'alignment' => $xf->alignment, - 'protection' => $xf->protection, + 'font' => $fonts[(int) ($xf['fontId'])], + 'fill' => $fills[((int) $xf['fillId'])], + 'border' => $borders[(int) ($xf['borderId'])], + 'alignment' => $xfTag->alignment, + 'protection' => $xfTag->protection, 'quotePrefix' => $quotePrefix, ]; $cellStyles[] = $cellStyle; @@ -563,18 +609,15 @@ public function load(string $pFilename, int $flags = 0) $dxfs = $styleReader->dxfs($this->readDataOnly); $styles = $styleReader->styles(); - //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" - $xmlWorkbook = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "{$rel['Target']}")), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $xmlWorkbook = $this->loadZipNoNamespace($rel['Target'], $mainNS); + $xmlWorkbookNS = $this->loadZip($rel['Target'], $mainNS); // Set base date - if ($xmlWorkbook->workbookPr) { + if ($xmlWorkbookNS->workbookPr) { Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - if (isset($xmlWorkbook->workbookPr['date1904'])) { - if (self::boolean((string) $xmlWorkbook->workbookPr['date1904'])) { + $attrs1904 = self::getAttributes($xmlWorkbookNS->workbookPr); + if (isset($attrs1904['date1904'])) { + if (self::boolean((string) $attrs1904['date1904'])) { Date::setExcelCalendar(Date::CALENDAR_MAC_1904); } } @@ -590,13 +633,14 @@ public function load(string $pFilename, int $flags = 0) $charts = $chartDetails = []; - if ($xmlWorkbook->sheets) { + if ($xmlWorkbookNS->sheets) { /** @var SimpleXMLElement $eleSheet */ - foreach ($xmlWorkbook->sheets->sheet as $eleSheet) { + foreach ($xmlWorkbookNS->sheets->sheet as $eleSheet) { + $eleSheetAttr = self::getAttributes($eleSheet); ++$oldSheetId; // Check if sheet should be skipped - if (isset($this->loadSheetsOnly) && !in_array((string) $eleSheet['name'], $this->loadSheetsOnly)) { + if (is_array($this->loadSheetsOnly) && !in_array((string) $eleSheetAttr['name'], $this->loadSheetsOnly)) { ++$countSkippedSheets; $mapSheetId[$oldSheetId] = null; @@ -613,30 +657,25 @@ public function load(string $pFilename, int $flags = 0) // references in formula cells... during the load, all formulae should be correct, // and we're simply bringing the worksheet name in line with the formula, not the // reverse - $docSheet->setTitle((string) $eleSheet['name'], false, false); - $fileWorksheet = $worksheets[(string) self::getArrayItem($eleSheet->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')]; - //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" - $xmlSheet = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$fileWorksheet")), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $docSheet->setTitle((string) $eleSheetAttr['name'], false, false); + $fileWorksheet = $worksheets[(string) self::getArrayItem(self::getAttributes($eleSheet, $xmlNamespaceBase), 'id')]; + $xmlSheet = $this->loadZipNoNamespace("$dir/$fileWorksheet", $mainNS); + $xmlSheetNS = $this->loadZip("$dir/$fileWorksheet", $mainNS); $sharedFormulas = []; - if (isset($eleSheet['state']) && (string) $eleSheet['state'] != '') { - $docSheet->setSheetState((string) $eleSheet['state']); + if (isset($eleSheetAttr['state']) && (string) $eleSheetAttr['state'] != '') { + $docSheet->setSheetState((string) $eleSheetAttr['state']); } - - if ($xmlSheet) { + if ($xmlSheetNS) { + $xmlSheetMain = $xmlSheetNS->children($mainNS); // Setting Conditional Styles adjusts selected cells, so we need to execute this // before reading the sheet view data to get the actual selected cells if (!$this->readDataOnly && $xmlSheet->conditionalFormatting) { (new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->load(); } - - if (isset($xmlSheet->sheetViews, $xmlSheet->sheetViews->sheetView)) { - $sheetViews = new SheetViews($xmlSheet->sheetViews->sheetView, $docSheet); + if (isset($xmlSheetMain->sheetViews, $xmlSheetMain->sheetViews->sheetView)) { + $sheetViews = new SheetViews($xmlSheetMain->sheetViews->sheetView, $docSheet); $sheetViews->load(); } @@ -647,16 +686,17 @@ public function load(string $pFilename, int $flags = 0) ->load($this->getReadFilter(), $this->getReadDataOnly()); } - if ($xmlSheet && $xmlSheet->sheetData && $xmlSheet->sheetData->row) { + if ($xmlSheetNS && $xmlSheetNS->sheetData && $xmlSheetNS->sheetData->row) { $cIndex = 1; // Cell Start from 1 - foreach ($xmlSheet->sheetData->row as $row) { + foreach ($xmlSheetNS->sheetData->row as $row) { $rowIndex = 1; foreach ($row->c as $c) { - $r = (string) $c['r']; + $cAttr = self::getAttributes($c); + $r = (string) $cAttr['r']; if ($r == '') { $r = Coordinate::stringFromColumnIndex($rowIndex) . $cIndex; } - $cellDataType = (string) $c['t']; + $cellDataType = (string) $cAttr['t']; $value = null; $calculatedValue = null; @@ -665,7 +705,7 @@ public function load(string $pFilename, int $flags = 0) $coordinates = Coordinate::coordinateFromString($r); if (!$this->getReadFilter()->readCell($coordinates[0], (int) $coordinates[1], $docSheet->getTitle())) { - if (isset($c->f)) { + if (isset($cAttr->f)) { $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError'); } ++$rowIndex; @@ -756,10 +796,10 @@ public function load(string $pFilename, int $flags = 0) } // Style information? - if ($c['s'] && !$this->readDataOnly) { + if ($cAttr['s'] && !$this->readDataOnly) { // no style index means 0, it seems - $cell->setXfIndex(isset($styles[(int) ($c['s'])]) ? - (int) ($c['s']) : 0); + $cell->setXfIndex(isset($styles[(int) ($cAttr['s'])]) ? + (int) ($cAttr['s']) : 0); } } ++$rowIndex; @@ -819,9 +859,10 @@ public function load(string $pFilename, int $flags = 0) // unparsed sheet AlternateContent if ($xmlSheet && !$this->readDataOnly) { - $mc = $xmlSheet->children('http://schemas.openxmlformats.org/markup-compatibility/2006'); + $mc = $xmlSheet->children(Namespaces::COMPATIBILITY); if ($mc->AlternateContent) { foreach ($mc->AlternateContent as $alternateContent) { + $alternateContent = self::testSimpleXml($alternateContent); $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['AlternateContents'][] = $alternateContent->asXML(); } } @@ -833,20 +874,13 @@ public function load(string $pFilename, int $flags = 0) // Locate hyperlink relations $relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; if ($zip->locateName($relationsFileName)) { - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorksheet = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, $relationsFileName) - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $relsWorksheet = $this->loadZip($relationsFileName, Namespaces::RELATIONSHIPS); $hyperlinkReader->readHyperlinks($relsWorksheet); } // Loop through hyperlinks - if ($xmlSheet && $xmlSheet->hyperlinks) { - $hyperlinkReader->setHyperlinks($xmlSheet->hyperlinks); + if ($xmlSheetNS && $xmlSheetNS->children($mainNS)->hyperlinks) { + $hyperlinkReader->setHyperlinks($xmlSheetNS->children($mainNS)->hyperlinks); } } @@ -855,20 +889,15 @@ public function load(string $pFilename, int $flags = 0) $vmlComments = []; if (!$this->readDataOnly) { // Locate comment relations - if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorksheet = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - foreach ($relsWorksheet->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments') { + $commentRelations = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; + if ($zip->locateName($commentRelations)) { + $relsWorksheet = $this->loadZip($commentRelations, Namespaces::RELATIONSHIPS); + foreach ($relsWorksheet->Relationship as $elex) { + $ele = self::getAttributes($elex); + if ($ele['Type'] == Namespaces::COMMENTS) { $comments[(string) $ele['Id']] = (string) $ele['Target']; } - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing') { + if ($ele['Type'] == Namespaces::VML) { $vmlComments[(string) $ele['Id']] = (string) $ele['Target']; } } @@ -878,27 +907,25 @@ public function load(string $pFilename, int $flags = 0) foreach ($comments as $relName => $relPath) { // Load comments file $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath); - $commentsFile = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, $relPath)), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + // okay to ignore namespace - using xpath + $commentsFile = $this->loadZip($relPath, ''); // Utility variables $authors = []; - - // Loop through authors - foreach ($commentsFile->authors->author as $author) { + $commentsFile->registerXpathNamespace('com', $mainNS); + $authorPath = self::xpathNoFalse($commentsFile, 'com:authors/com:author'); + foreach ($authorPath as $author) { $authors[] = (string) $author; } // Loop through contents - foreach ($commentsFile->commentList->comment as $comment) { + $contentPath = self::xpathNoFalse($commentsFile, 'com:commentList/com:comment'); + foreach ($contentPath as $comment) { $commentModel = $docSheet->getComment((string) $comment['ref']); if (!empty($comment['authorId'])) { - $commentModel->setAuthor($authors[$comment['authorId']]); + $commentModel->setAuthor($authors[(int) $comment['authorId']]); } - $commentModel->setText($this->parseRichText($comment->text)); + $commentModel->setText($this->parseRichText($comment->children($mainNS)->text)); } } @@ -911,20 +938,17 @@ public function load(string $pFilename, int $flags = 0) $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath); try { - $vmlCommentsFile = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, $relPath)), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - $vmlCommentsFile->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml'); + // no namespace okay - processed with Xpath + $vmlCommentsFile = $this->loadZip($relPath, ''); + $vmlCommentsFile->registerXPathNamespace('v', Namespaces::URN_VML); } catch (Throwable $ex) { //Ignore unparsable vmlDrawings. Later they will be moved from $unparsedVmlDrawings to $unparsedLoadedData continue; } - $shapes = $vmlCommentsFile->xpath('//v:shape'); + $shapes = self::xpathNoFalse($vmlCommentsFile, '//v:shape'); foreach ($shapes as $shape) { - $shape->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml'); + $shape->registerXPathNamespace('v', Namespaces::URN_VML); if (isset($shape['style'])) { $style = (string) $shape['style']; @@ -998,62 +1022,44 @@ public function load(string $pFilename, int $flags = 0) // Header/footer images if ($xmlSheet && $xmlSheet->legacyDrawingHF && !$this->readDataOnly) { if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorksheet = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $relsWorksheet = $this->loadZipNoNamespace(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels', Namespaces::RELATIONSHIPS); $vmlRelationship = ''; foreach ($relsWorksheet->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing') { + if ($ele['Type'] == Namespaces::VML) { $vmlRelationship = self::dirAdd("$dir/$fileWorksheet", $ele['Target']); } } if ($vmlRelationship != '') { // Fetch linked images - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsVML = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, dirname($vmlRelationship) . '/_rels/' . basename($vmlRelationship) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $relsVML = $this->loadZipNoNamespace(dirname($vmlRelationship) . '/_rels/' . basename($vmlRelationship) . '.rels', Namespaces::RELATIONSHIPS); $drawings = []; if (isset($relsVML->Relationship)) { foreach ($relsVML->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') { + if ($ele['Type'] == Namespaces::IMAGE) { $drawings[(string) $ele['Id']] = self::dirAdd($vmlRelationship, $ele['Target']); } } } // Fetch VML document - $vmlDrawing = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, $vmlRelationship)), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - $vmlDrawing->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml'); + $vmlDrawing = $this->loadZipNoNamespace($vmlRelationship, ''); + $vmlDrawing->registerXPathNamespace('v', Namespaces::URN_VML); $hfImages = []; - $shapes = $vmlDrawing->xpath('//v:shape'); + $shapes = self::xpathNoFalse($vmlDrawing, '//v:shape'); foreach ($shapes as $idx => $shape) { - $shape->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml'); + $shape->registerXPathNamespace('v', Namespaces::URN_VML); $imageData = $shape->xpath('//v:imagedata'); - if (!$imageData) { + if (empty($imageData)) { continue; } $imageData = $imageData[$idx]; - $imageData = $imageData->attributes('urn:schemas-microsoft-com:office:office'); + $imageData = self::getAttributes($imageData, Namespaces::URN_MSOFFICE); $style = self::toCSSArray((string) $shape['style']); $hfImages[(string) $shape['id']] = new HeaderFooterDrawing(); @@ -1079,18 +1085,15 @@ public function load(string $pFilename, int $flags = 0) } // TODO: Autoshapes from twoCellAnchors! - if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorksheet = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $filename = dirname("$dir/$fileWorksheet") + . '/_rels/' + . basename($fileWorksheet) + . '.rels'; + if ($zip->locateName($filename)) { + $relsWorksheet = $this->loadZipNoNamespace($filename, Namespaces::RELATIONSHIPS); $drawings = []; foreach ($relsWorksheet->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing') { + if ((string) $ele['Type'] === "$xmlNamespaceBase/drawing") { $drawings[(string) $ele['Id']] = self::dirAdd("$dir/$fileWorksheet", $ele['Target']); } } @@ -1098,26 +1101,21 @@ public function load(string $pFilename, int $flags = 0) $unparsedDrawings = []; $fileDrawing = null; foreach ($xmlSheet->drawing as $drawing) { - $drawingRelId = (string) self::getArrayItem($drawing->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id'); + $drawingRelId = (string) self::getArrayItem(self::getAttributes($drawing, $xmlNamespaceBase), 'id'); $fileDrawing = $drawings[$drawingRelId]; - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsDrawing = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, dirname($fileDrawing) . '/_rels/' . basename($fileDrawing) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $filename = dirname($fileDrawing) . '/_rels/' . basename($fileDrawing) . '.rels'; + $relsDrawing = $this->loadZipNoNamespace($filename, $xmlNamespaceBase); $images = []; $hyperlinks = []; if ($relsDrawing && $relsDrawing->Relationship) { foreach ($relsDrawing->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink') { + $eleType = (string) $ele['Type']; + if ($eleType === Namespaces::HYPERLINK) { $hyperlinks[(string) $ele['Id']] = (string) $ele['Target']; } - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') { + if ($eleType === "$xmlNamespaceBase/image") { $images[(string) $ele['Id']] = self::dirAdd($fileDrawing, $ele['Target']); - } elseif ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart') { + } elseif ($eleType === "$xmlNamespaceBase/chart") { if ($this->includeCharts) { $charts[self::dirAdd($fileDrawing, $ele['Target'])] = [ 'id' => (string) $ele['Id'], @@ -1127,30 +1125,26 @@ public function load(string $pFilename, int $flags = 0) } } } - $xmlDrawing = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, $fileDrawing)), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - $xmlDrawingChildren = $xmlDrawing->children('http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing'); + $xmlDrawing = $this->loadZipNoNamespace($fileDrawing, ''); + $xmlDrawingChildren = $xmlDrawing->children(Namespaces::SPREADSHEET_DRAWING); if ($xmlDrawingChildren->oneCellAnchor) { foreach ($xmlDrawingChildren->oneCellAnchor as $oneCellAnchor) { if ($oneCellAnchor->pic->blipFill) { /** @var SimpleXMLElement $blip */ - $blip = $oneCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip; + $blip = $oneCellAnchor->pic->blipFill->children(Namespaces::DRAWINGML)->blip; /** @var SimpleXMLElement $xfrm */ - $xfrm = $oneCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm; + $xfrm = $oneCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->xfrm; /** @var SimpleXMLElement $outerShdw */ - $outerShdw = $oneCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->effectLst->outerShdw; + $outerShdw = $oneCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->effectLst->outerShdw; /** @var SimpleXMLElement $hlinkClick */ - $hlinkClick = $oneCellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick; + $hlinkClick = $oneCellAnchor->pic->nvPicPr->cNvPr->children(Namespaces::DRAWINGML)->hlinkClick; $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); - $objDrawing->setName((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name')); - $objDrawing->setDescription((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'descr')); + $objDrawing->setName((string) self::getArrayItem(self::getAttributes($oneCellAnchor->pic->nvPicPr->cNvPr), 'name')); + $objDrawing->setDescription((string) self::getArrayItem(self::getAttributes($oneCellAnchor->pic->nvPicPr->cNvPr), 'descr')); $imageKey = (string) self::getArrayItem( - $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), + self::getAttributes($blip, $xmlNamespaceBase), 'embed' ); @@ -1163,24 +1157,24 @@ public function load(string $pFilename, int $flags = 0) } $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((int) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1)); - $objDrawing->setOffsetX(Drawing::EMUToPixels($oneCellAnchor->from->colOff)); - $objDrawing->setOffsetY(Drawing::EMUToPixels($oneCellAnchor->from->rowOff)); + $objDrawing->setOffsetX((int) Drawing::EMUToPixels((int) $oneCellAnchor->from->colOff)); + $objDrawing->setOffsetY(Drawing::EMUToPixels((int) $oneCellAnchor->from->rowOff)); $objDrawing->setResizeProportional(false); - $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cx'))); - $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cy'))); + $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cx'))); + $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem((int) self::getAttributes($oneCellAnchor->ext), 'cy'))); if ($xfrm) { - $objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem($xfrm->attributes(), 'rot'))); + $objDrawing->setRotation((int) Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($xfrm), 'rot'))); } if ($outerShdw) { $shadow = $objDrawing->getShadow(); $shadow->setVisible(true); - $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'blurRad'))); - $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'dist'))); - $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem($outerShdw->attributes(), 'dir'))); - $shadow->setAlignment((string) self::getArrayItem($outerShdw->attributes(), 'algn')); + $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'blurRad'))); + $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'dist'))); + $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($outerShdw), 'dir'))); + $shadow->setAlignment((string) self::getArrayItem(self::getAttributes($outerShdw), 'algn')); $clr = $outerShdw->srgbClr ?? $outerShdw->prstClr; - $shadow->getColor()->setRGB(self::getArrayItem($clr->attributes(), 'val')); - $shadow->setAlpha(self::getArrayItem($clr->alpha->attributes(), 'val') / 1000); + $shadow->getColor()->setRGB(self::getArrayItem(self::getAttributes($clr), 'val')); + $shadow->setAlpha(self::getArrayItem(self::getAttributes($clr->alpha), 'val') / 1000); } $this->readHyperLinkDrawing($objDrawing, $oneCellAnchor, $hyperlinks); @@ -1191,13 +1185,13 @@ public function load(string $pFilename, int $flags = 0) $coordinates = Coordinate::stringFromColumnIndex(((int) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1); $offsetX = Drawing::EMUToPixels($oneCellAnchor->from->colOff); $offsetY = Drawing::EMUToPixels($oneCellAnchor->from->rowOff); - $width = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cx')); - $height = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cy')); + $width = Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cx')); + $height = Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cy')); - $graphic = $oneCellAnchor->graphicFrame->children('http://schemas.openxmlformats.org/drawingml/2006/main')->graphic; + $graphic = $oneCellAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic; /** @var SimpleXMLElement $chartRef */ - $chartRef = $graphic->graphicData->children('http://schemas.openxmlformats.org/drawingml/2006/chart')->chart; - $thisChart = (string) $chartRef->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart; + $thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase); $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [ 'fromCoordinate' => $coordinates, @@ -1213,15 +1207,15 @@ public function load(string $pFilename, int $flags = 0) if ($xmlDrawingChildren->twoCellAnchor) { foreach ($xmlDrawingChildren->twoCellAnchor as $twoCellAnchor) { if ($twoCellAnchor->pic->blipFill) { - $blip = $twoCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip; - $xfrm = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm; - $outerShdw = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->effectLst->outerShdw; - $hlinkClick = $twoCellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick; + $blip = $twoCellAnchor->pic->blipFill->children(Namespaces::DRAWINGML)->blip; + $xfrm = $twoCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->xfrm; + $outerShdw = $twoCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->effectLst->outerShdw; + $hlinkClick = $twoCellAnchor->pic->nvPicPr->cNvPr->children(Namespaces::DRAWINGML)->hlinkClick; $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); - $objDrawing->setName((string) self::getArrayItem($twoCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name')); - $objDrawing->setDescription((string) self::getArrayItem($twoCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'descr')); + $objDrawing->setName((string) self::getArrayItem(self::getAttributes($twoCellAnchor->pic->nvPicPr->cNvPr), 'name')); + $objDrawing->setDescription((string) self::getArrayItem(self::getAttributes($twoCellAnchor->pic->nvPicPr->cNvPr), 'descr')); $imageKey = (string) self::getArrayItem( - $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), + self::getAttributes($blip, $xmlNamespaceBase), 'embed' ); if (isset($images[$imageKey])) { @@ -1238,20 +1232,20 @@ public function load(string $pFilename, int $flags = 0) $objDrawing->setResizeProportional(false); if ($xfrm) { - $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem($xfrm->ext->attributes(), 'cx'))); - $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem($xfrm->ext->attributes(), 'cy'))); - $objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem($xfrm->attributes(), 'rot'))); + $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($xfrm->ext), 'cx'))); + $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($xfrm->ext), 'cy'))); + $objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($xfrm), 'rot'))); } if ($outerShdw) { $shadow = $objDrawing->getShadow(); $shadow->setVisible(true); - $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'blurRad'))); - $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'dist'))); - $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem($outerShdw->attributes(), 'dir'))); - $shadow->setAlignment((string) self::getArrayItem($outerShdw->attributes(), 'algn')); + $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'blurRad'))); + $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'dist'))); + $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($outerShdw), 'dir'))); + $shadow->setAlignment((string) self::getArrayItem(self::getAttributes($outerShdw), 'algn')); $clr = $outerShdw->srgbClr ?? $outerShdw->prstClr; - $shadow->getColor()->setRGB(self::getArrayItem($clr->attributes(), 'val')); - $shadow->setAlpha(self::getArrayItem($clr->alpha->attributes(), 'val') / 1000); + $shadow->getColor()->setRGB(self::getArrayItem(self::getAttributes($clr), 'val')); + $shadow->setAlpha(self::getArrayItem(self::getAttributes($clr->alpha), 'val') / 1000); } $this->readHyperLinkDrawing($objDrawing, $twoCellAnchor, $hyperlinks); @@ -1264,10 +1258,10 @@ public function load(string $pFilename, int $flags = 0) $toCoordinate = Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->to->col) + 1) . ($twoCellAnchor->to->row + 1); $toOffsetX = Drawing::EMUToPixels($twoCellAnchor->to->colOff); $toOffsetY = Drawing::EMUToPixels($twoCellAnchor->to->rowOff); - $graphic = $twoCellAnchor->graphicFrame->children('http://schemas.openxmlformats.org/drawingml/2006/main')->graphic; + $graphic = $twoCellAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic; /** @var SimpleXMLElement $chartRef */ - $chartRef = $graphic->graphicData->children('http://schemas.openxmlformats.org/drawingml/2006/chart')->chart; - $thisChart = (string) $chartRef->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart; + $thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase); $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [ 'fromCoordinate' => $fromCoordinate, @@ -1281,7 +1275,7 @@ public function load(string $pFilename, int $flags = 0) } } } - if ($relsDrawing === false && $xmlDrawing->count() == 0) { + if (empty($relsDrawing) && $xmlDrawing->count() == 0) { // Save Drawing without rels and children as unparsed $unparsedDrawings[$drawingRelId] = $xmlDrawing->asXML(); } @@ -1290,7 +1284,7 @@ public function load(string $pFilename, int $flags = 0) // store original rId of drawing files $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'] = []; foreach ($relsWorksheet->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing') { + if ((string) $ele['Type'] === "$xmlNamespaceBase/drawing") { $drawingRelId = (string) $ele['Id']; $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'][(string) $ele['Target']] = $drawingRelId; if (isset($unparsedDrawings[$drawingRelId])) { @@ -1300,22 +1294,19 @@ public function load(string $pFilename, int $flags = 0) } // unparsed drawing AlternateContent - $xmlAltDrawing = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, $fileDrawing)), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - )->children('http://schemas.openxmlformats.org/markup-compatibility/2006'); + $xmlAltDrawing = $this->loadZip($fileDrawing, Namespaces::COMPATIBILITY); if ($xmlAltDrawing->AlternateContent) { foreach ($xmlAltDrawing->AlternateContent as $alternateContent) { + $alternateContent = self::testSimpleXml($alternateContent); $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingAlternateContents'][] = $alternateContent->asXML(); } } } } - $this->readFormControlProperties($excel, $zip, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData); - $this->readPrinterSettings($excel, $zip, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData); + $this->readFormControlProperties($excel, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData); + $this->readPrinterSettings($excel, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData); // Loop through definedNames if ($xmlWorkbook->definedNames) { @@ -1456,11 +1447,11 @@ public function load(string $pFilename, int $flags = 0) } } - if ((!$this->readDataOnly || !empty($this->loadSheetsOnly)) && isset($xmlWorkbook->bookViews->workbookView)) { - $workbookView = $xmlWorkbook->bookViews->workbookView; - + $workbookView = $xmlWorkbook->children($mainNS)->bookViews->workbookView; + if ((!$this->readDataOnly || !empty($this->loadSheetsOnly)) && !empty($workbookView)) { + $workbookViewAttributes = self::testSimpleXml(self::getAttributes($workbookView)); // active sheet index - $activeTab = (int) ($workbookView['activeTab']); // refers to old sheet index + $activeTab = (int) $workbookViewAttributes->activeTab; // refers to old sheet index // keep active sheet index if sheet is still loaded, else first sheet is set as the active if (isset($mapSheetId[$activeTab]) && $mapSheetId[$activeTab] !== null) { @@ -1472,43 +1463,43 @@ public function load(string $pFilename, int $flags = 0) $excel->setActiveSheetIndex(0); } - if (isset($workbookView['showHorizontalScroll'])) { - $showHorizontalScroll = (string) $workbookView['showHorizontalScroll']; + if (isset($workbookViewAttributes->showHorizontalScroll)) { + $showHorizontalScroll = (string) $workbookViewAttributes->showHorizontalScroll; $excel->setShowHorizontalScroll($this->castXsdBooleanToBool($showHorizontalScroll)); } - if (isset($workbookView['showVerticalScroll'])) { - $showVerticalScroll = (string) $workbookView['showVerticalScroll']; + if (isset($workbookViewAttributes->showVerticalScroll)) { + $showVerticalScroll = (string) $workbookViewAttributes->showVerticalScroll; $excel->setShowVerticalScroll($this->castXsdBooleanToBool($showVerticalScroll)); } - if (isset($workbookView['showSheetTabs'])) { - $showSheetTabs = (string) $workbookView['showSheetTabs']; + if (isset($workbookViewAttributes->showSheetTabs)) { + $showSheetTabs = (string) $workbookViewAttributes->showSheetTabs; $excel->setShowSheetTabs($this->castXsdBooleanToBool($showSheetTabs)); } - if (isset($workbookView['minimized'])) { - $minimized = (string) $workbookView['minimized']; + if (isset($workbookViewAttributes->minimized)) { + $minimized = (string) $workbookViewAttributes->minimized; $excel->setMinimized($this->castXsdBooleanToBool($minimized)); } - if (isset($workbookView['autoFilterDateGrouping'])) { - $autoFilterDateGrouping = (string) $workbookView['autoFilterDateGrouping']; + if (isset($workbookViewAttributes->autoFilterDateGrouping)) { + $autoFilterDateGrouping = (string) $workbookViewAttributes->autoFilterDateGrouping; $excel->setAutoFilterDateGrouping($this->castXsdBooleanToBool($autoFilterDateGrouping)); } - if (isset($workbookView['firstSheet'])) { - $firstSheet = (string) $workbookView['firstSheet']; + if (isset($workbookViewAttributes->firstSheet)) { + $firstSheet = (string) $workbookViewAttributes->firstSheet; $excel->setFirstSheetIndex((int) $firstSheet); } - if (isset($workbookView['visibility'])) { - $visibility = (string) $workbookView['visibility']; + if (isset($workbookViewAttributes->visibility)) { + $visibility = (string) $workbookViewAttributes->visibility; $excel->setVisibility($visibility); } - if (isset($workbookView['tabRatio'])) { - $tabRatio = (string) $workbookView['tabRatio']; + if (isset($workbookViewAttributes->tabRatio)) { + $tabRatio = (string) $workbookViewAttributes->tabRatio; $excel->setTabRatio((int) $tabRatio); } } @@ -1518,13 +1509,7 @@ public function load(string $pFilename, int $flags = 0) } if (!$this->readDataOnly) { - $contentTypes = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, '[Content_Types].xml') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $contentTypes = $this->loadZip('[Content_Types].xml'); // Default content types foreach ($contentTypes->Default as $contentType) { @@ -1542,13 +1527,7 @@ public function load(string $pFilename, int $flags = 0) case 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml': if ($this->includeCharts) { $chartEntryRef = ltrim($contentType['PartName'], '/'); - $chartElements = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, $chartEntryRef) - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $chartElements = $this->loadZip($chartEntryRef); $objChart = Chart::readChart($chartElements, basename($chartEntryRef, '.xml')); if (isset($charts[$chartEntryRef])) { @@ -1612,8 +1591,8 @@ private static function readStyle(Style $docStyle, $style): void // protection if (isset($style->protection)) { - Styles::readProtectionLocked($docStyle, $style->protection); - Styles::readProtectionHidden($docStyle, $style->protection); + Styles::readProtectionLocked($docStyle, $style); + Styles::readProtectionHidden($docStyle, $style); } // top-level style settings @@ -1717,7 +1696,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']); @@ -1803,16 +1782,16 @@ private static function boolean($value) */ private function readHyperLinkDrawing($objDrawing, $cellAnchor, $hyperlinks): void { - $hlinkClick = $cellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick; + $hlinkClick = $cellAnchor->pic->nvPicPr->cNvPr->children(Namespaces::DRAWINGML)->hlinkClick; if ($hlinkClick->count() === 0) { return; } - $hlinkId = (string) $hlinkClick->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships')['id']; + $hlinkId = (string) self::getAttributes($hlinkClick, Namespaces::SCHEMA_OFFICE_DOCUMENT)['id']; $hyperlink = new Hyperlink( $hyperlinks[$hlinkId], - (string) self::getArrayItem($cellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name') + (string) self::getArrayItem(self::getAttributes($cellAnchor->pic->nvPicPr->cNvPr), 'name') ); $objDrawing->setHyperlink($hyperlink); } @@ -1854,23 +1833,18 @@ private static function getLockValue(SimpleXmlElement $protection, string $key): return $returnValue; } - private function readFormControlProperties(Spreadsheet $excel, ZipArchive $zip, $dir, $fileWorksheet, $docSheet, array &$unparsedLoadedData): void + private function readFormControlProperties(Spreadsheet $excel, $dir, $fileWorksheet, $docSheet, array &$unparsedLoadedData): void { + $zip = $this->zip; if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { return; } - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorksheet = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $filename = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; + $relsWorksheet = $this->loadZipNoNamespace($filename, Namespaces::RELATIONSHIPS); $ctrlProps = []; foreach ($relsWorksheet->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp') { + if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/ctrlProp') { $ctrlProps[(string) $ele['Id']] = $ele; } } @@ -1886,23 +1860,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; } } @@ -1941,38 +1910,29 @@ private function castXsdBooleanToBool($xsdBoolean) return (bool) $xsdBoolean; } - /** - * @param ZipArchive $zip Opened zip archive - * - * @return string basename of the used excel workbook - */ - private function getWorkbookBaseName(ZipArchive $zip) + private function getWorkbookBaseName(): array { $workbookBasename = ''; + $xmlNamespaceBase = ''; // check if it is an OOXML archive - $rels = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, '_rels/.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - if ($rels !== false) { - foreach ($rels->Relationship as $rel) { - switch ($rel['Type']) { - case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument': - $basename = basename($rel['Target']); - if (preg_match('/workbook.*\.xml/', $basename)) { - $workbookBasename = $basename; - } + $rels = $this->loadZip('_rels/.rels'); + foreach ($rels->children(Namespaces::RELATIONSHIPS)->Relationship as $rel) { + $rel = self::getAttributes($rel); + switch ($rel['Type']) { + case Namespaces::OFFICE_DOCUMENT: + case Namespaces::PURL_OFFICE_DOCUMENT: + $basename = basename($rel['Target']); + $xmlNamespaceBase = dirname($rel['Type']); + if (preg_match('/workbook.*\.xml/', $basename)) { + $workbookBasename = $basename; + } - break; - } + break; } } - return $workbookBasename; + return [$workbookBasename, $xmlNamespaceBase]; } private function readSheetProtection(Worksheet $docSheet, SimpleXMLElement $xmlSheet): void diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php b/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php index a9afce38cf..dbc8c08532 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use SimpleXMLElement; @@ -19,16 +20,17 @@ public function __construct(Worksheet $workSheet) public function readHyperlinks(SimpleXMLElement $relsWorksheet): void { - foreach ($relsWorksheet->Relationship as $element) { - if ($element['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink') { - $this->hyperlinks[(string) $element['Id']] = (string) $element['Target']; + foreach ($relsWorksheet->children(Namespaces::RELATIONSHIPS)->Relationship as $elementx) { + $element = Xlsx::getAttributes($elementx); + if ($element->Type == Namespaces::HYPERLINK) { + $this->hyperlinks[(string) $element->Id] = (string) $element->Target; } } } public function setHyperlinks(SimpleXMLElement $worksheetXml): void { - foreach ($worksheetXml->hyperlink as $hyperlink) { + foreach ($worksheetXml->children(Namespaces::MAIN)->hyperlink as $hyperlink) { if ($hyperlink !== null) { $this->setHyperlink($hyperlink, $this->worksheet); } @@ -38,9 +40,10 @@ public function setHyperlinks(SimpleXMLElement $worksheetXml): void private function setHyperlink(SimpleXMLElement $hyperlink, Worksheet $worksheet): void { // Link url - $linkRel = $hyperlink->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $linkRel = Xlsx::getAttributes($hyperlink, Namespaces::SCHEMA_OFFICE_DOCUMENT); - foreach (Coordinate::extractAllCellReferencesInRange($hyperlink['ref']) as $cellReference) { + $attributes = Xlsx::getAttributes($hyperlink); + foreach (Coordinate::extractAllCellReferencesInRange($attributes->ref) as $cellReference) { $cell = $worksheet->getCell($cellReference); if (isset($linkRel['id'])) { $hyperlinkUrl = $this->hyperlinks[(string) $linkRel['id']] ?? null; diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php b/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php new file mode 100644 index 0000000000..54f56d7247 --- /dev/null +++ b/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php @@ -0,0 +1,76 @@ +setPageOrder((string) $xmlSheet->pageSetup['pageOrder']); } - $relAttributes = $xmlSheet->pageSetup->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $relAttributes = $xmlSheet->pageSetup->attributes(Namespaces::SCHEMA_OFFICE_DOCUMENT); if (isset($relAttributes['id'])) { $unparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId'] = (string) $relAttributes['id']; } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Properties.php b/src/PhpSpreadsheet/Reader/Xlsx/Properties.php index ffc7ec452c..fb341f71f5 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Properties.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Properties.php @@ -9,8 +9,10 @@ class Properties { + /** @var XmlScanner */ private $securityScanner; + /** @var DocumentProperties */ private $docProps; public function __construct(XmlScanner $securityScanner, DocumentProperties $docProps) @@ -19,30 +21,39 @@ public function __construct(XmlScanner $securityScanner, DocumentProperties $doc $this->docProps = $docProps; } - private function extractPropertyData($propertyData) + /** + * @param mixed $obj + */ + private static function nullOrSimple($obj): ?SimpleXMLElement { - return simplexml_load_string( + return ($obj instanceof SimpleXMLElement) ? $obj : null; + } + + private function extractPropertyData(string $propertyData): ?SimpleXMLElement + { + // okay to omit namespace because everything will be processed by xpath + $obj = simplexml_load_string( $this->securityScanner->scan($propertyData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions() ); + + return self::nullOrSimple($obj); } - public function readCoreProperties($propertyData): void + public function readCoreProperties(string $propertyData): void { $xmlCore = $this->extractPropertyData($propertyData); if (is_object($xmlCore)) { - $xmlCore->registerXPathNamespace('dc', 'http://purl.org/dc/elements/1.1/'); - $xmlCore->registerXPathNamespace('dcterms', 'http://purl.org/dc/terms/'); - $xmlCore->registerXPathNamespace('cp', 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties'); + $xmlCore->registerXPathNamespace('dc', Namespaces::DC_ELEMENTS); + $xmlCore->registerXPathNamespace('dcterms', Namespaces::DC_TERMS); + $xmlCore->registerXPathNamespace('cp', Namespaces::CORE_PROPERTIES2); $this->docProps->setCreator((string) self::getArrayItem($xmlCore->xpath('dc:creator'))); $this->docProps->setLastModifiedBy((string) self::getArrayItem($xmlCore->xpath('cp:lastModifiedBy'))); - $created = (string) self::getArrayItem($xmlCore->xpath('dcterms:created')); //! respect xsi:type - $this->docProps->setCreated($created); - $modified = (string) self::getArrayItem($xmlCore->xpath('dcterms:modified')); //! respect xsi:type - $this->docProps->setModified($modified); //! respect xsi:type + $this->docProps->setCreated((int) strtotime((string) self::getArrayItem($xmlCore->xpath('dcterms:created')))); //! respect xsi:type + $this->docProps->setModified((int) strtotime((string) self::getArrayItem($xmlCore->xpath('dcterms:modified')))); //! respect xsi:type $this->docProps->setTitle((string) self::getArrayItem($xmlCore->xpath('dc:title'))); $this->docProps->setDescription((string) self::getArrayItem($xmlCore->xpath('dc:description'))); $this->docProps->setSubject((string) self::getArrayItem($xmlCore->xpath('dc:subject'))); @@ -51,7 +62,7 @@ public function readCoreProperties($propertyData): void } } - public function readExtendedProperties($propertyData): void + public function readExtendedProperties(string $propertyData): void { $xmlCore = $this->extractPropertyData($propertyData); @@ -65,7 +76,7 @@ public function readExtendedProperties($propertyData): void } } - public function readCustomProperties($propertyData): void + public function readCustomProperties(string $propertyData): void { $xmlCore = $this->extractPropertyData($propertyData); @@ -87,8 +98,12 @@ public function readCustomProperties($propertyData): void } } - private static function getArrayItem(array $array, $key = 0) + /** + * @param array|false $array + * @param mixed $key + */ + private static function getArrayItem($array, $key = 0): ?SimpleXMLElement { - return $array[$key] ?? null; + return is_array($array) ? ($array[$key] ?? null) : null; } } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php b/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php index f6c4792906..9b3e47edd0 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php @@ -3,18 +3,25 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use SimpleXMLElement; class SheetViews extends BaseParserClass { + /** @var SimpleXMLElement */ private $sheetViewXml; + /** @var SimpleXMLElement */ + private $sheetViewAttributes; + + /** @var Worksheet */ private $worksheet; public function __construct(SimpleXMLElement $sheetViewXml, Worksheet $workSheet) { $this->sheetViewXml = $sheetViewXml; + $this->sheetViewAttributes = Xlsx::testSimpleXml($sheetViewXml->attributes()); $this->worksheet = $workSheet; } @@ -30,15 +37,15 @@ public function load(): void if (isset($this->sheetViewXml->pane)) { $this->pane(); } - if (isset($this->sheetViewXml->selection, $this->sheetViewXml->selection['sqref'])) { + if (isset($this->sheetViewXml->selection, $this->sheetViewXml->selection->attributes()->sqref)) { $this->selection(); } } private function zoomScale(): void { - if (isset($this->sheetViewXml['zoomScale'])) { - $zoomScale = (int) ($this->sheetViewXml['zoomScale']); + if (isset($this->sheetViewAttributes->zoomScale)) { + $zoomScale = (int) ($this->sheetViewAttributes->zoomScale); if ($zoomScale <= 0) { // setZoomScale will throw an Exception if the scale is less than or equals 0 // that is OK when manually creating documents, but we should be able to read all documents @@ -48,8 +55,8 @@ private function zoomScale(): void $this->worksheet->getSheetView()->setZoomScale($zoomScale); } - if (isset($this->sheetViewXml['zoomScaleNormal'])) { - $zoomScaleNormal = (int) ($this->sheetViewXml['zoomScaleNormal']); + if (isset($this->sheetViewAttributes->zoomScaleNormal)) { + $zoomScaleNormal = (int) ($this->sheetViewAttributes->zoomScaleNormal); if ($zoomScaleNormal <= 0) { // setZoomScaleNormal will throw an Exception if the scale is less than or equals 0 // that is OK when manually creating documents, but we should be able to read all documents @@ -62,43 +69,43 @@ private function zoomScale(): void private function view(): void { - if (isset($this->sheetViewXml['view'])) { - $this->worksheet->getSheetView()->setView((string) $this->sheetViewXml['view']); + if (isset($this->sheetViewAttributes->view)) { + $this->worksheet->getSheetView()->setView((string) $this->sheetViewAttributes->view); } } private function gridLines(): void { - if (isset($this->sheetViewXml['showGridLines'])) { + if (isset($this->sheetViewAttributes->showGridLines)) { $this->worksheet->setShowGridLines( - self::boolean((string) $this->sheetViewXml['showGridLines']) + self::boolean((string) $this->sheetViewAttributes->showGridLines) ); } } private function headers(): void { - if (isset($this->sheetViewXml['showRowColHeaders'])) { + if (isset($this->sheetViewAttributes->showRowColHeaders)) { $this->worksheet->setShowRowColHeaders( - self::boolean((string) $this->sheetViewXml['showRowColHeaders']) + self::boolean((string) $this->sheetViewAttributes->showRowColHeaders) ); } } private function direction(): void { - if (isset($this->sheetViewXml['rightToLeft'])) { + if (isset($this->sheetViewAttributes->rightToLeft)) { $this->worksheet->setRightToLeft( - self::boolean((string) $this->sheetViewXml['rightToLeft']) + self::boolean((string) $this->sheetViewAttributes->rightToLeft) ); } } private function showZeros(): void { - if (isset($this->sheetViewXml['showZeros'])) { + if (isset($this->sheetViewAttributes->showZeros)) { $this->worksheet->getSheetView()->setShowZeros( - self::boolean((string) $this->sheetViewXml['showZeros']) + self::boolean((string) $this->sheetViewAttributes->showZeros) ); } } @@ -108,17 +115,18 @@ private function pane(): void $xSplit = 0; $ySplit = 0; $topLeftCell = null; + $paneAttributes = $this->sheetViewXml->pane->attributes(); - if (isset($this->sheetViewXml->pane['xSplit'])) { - $xSplit = (int) ($this->sheetViewXml->pane['xSplit']); + if (isset($paneAttributes->xSplit)) { + $xSplit = (int) ($paneAttributes->xSplit); } - if (isset($this->sheetViewXml->pane['ySplit'])) { - $ySplit = (int) ($this->sheetViewXml->pane['ySplit']); + if (isset($paneAttributes->ySplit)) { + $ySplit = (int) ($paneAttributes->ySplit); } - if (isset($this->sheetViewXml->pane['topLeftCell'])) { - $topLeftCell = (string) $this->sheetViewXml->pane['topLeftCell']; + if (isset($paneAttributes->topLeftCell)) { + $topLeftCell = (string) $paneAttributes->topLeftCell; } $this->worksheet->freezePane( @@ -129,10 +137,12 @@ private function pane(): void private function selection(): void { - $sqref = (string) $this->sheetViewXml->selection['sqref']; - $sqref = explode(' ', $sqref); - $sqref = $sqref[0]; - - $this->worksheet->setSelectedCells($sqref); + $attributes = $this->sheetViewXml->selection->attributes(); + if ($attributes !== null) { + $sqref = (string) $attributes->sqref; + $sqref = explode(' ', $sqref); + $sqref = $sqref[0]; + $this->worksheet->setSelectedCells($sqref); + } } } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Styles.php b/src/PhpSpreadsheet/Reader/Xlsx/Styles.php index 80c320655b..a09a7b927d 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Styles.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Styles.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Style\Alignment; use PhpOffice\PhpSpreadsheet\Style\Border; use PhpOffice\PhpSpreadsheet\Style\Borders; @@ -82,7 +83,7 @@ private static function readNumberFormat(NumberFormat $numfmtStyle, SimpleXMLEle if ($numfmtStyleXml->count() === 0) { return; } - $numfmt = $numfmtStyleXml->attributes(); + $numfmt = Xlsx::getAttributes($numfmtStyleXml); if ($numfmt->count() > 0 && isset($numfmt['formatCode'])) { $numfmtStyle->setFormatCode((string) $numfmt['formatCode']); } @@ -97,13 +98,9 @@ public static function readFillStyle(Fill $fillStyle, SimpleXMLElement $fillStyl $fillStyle->setFillType((string) $gradientFill['type']); } $fillStyle->setRotation((float) ($gradientFill['degree'])); - $gradientFill->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); - $fillStyle->getStartColor()->setARGB( - self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color) - ); - $fillStyle->getEndColor()->setARGB( - self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color) - ); + $gradientFill->registerXPathNamespace('sml', Namespaces::MAIN); + $fillStyle->getStartColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color)); + $fillStyle->getEndColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color)); } elseif ($fillStyleXml->patternFill) { $defaultFillStyle = Fill::FILL_NONE; if ($fillStyleXml->patternFill->fgColor) { @@ -270,7 +267,8 @@ public function dxfs($readDataOnly = false) } // Cell Styles if ($this->styleXml->cellStyles) { - foreach ($this->styleXml->cellStyles->cellStyle as $cellStyle) { + foreach ($this->styleXml->cellStyles->cellStyle as $cellStylex) { + $cellStyle = Xlsx::getAttributes($cellStylex); if ((int) ($cellStyle['builtinId']) == 0) { if (isset($this->cellStyles[(int) ($cellStyle['xfId'])])) { // Set default style diff --git a/src/PhpSpreadsheet/ReferenceHelper.php b/src/PhpSpreadsheet/ReferenceHelper.php index d4fced37f2..660a877849 100644 --- a/src/PhpSpreadsheet/ReferenceHelper.php +++ b/src/PhpSpreadsheet/ReferenceHelper.php @@ -581,8 +581,8 @@ public function insertNewBefore($pBefore, $pNumCols, $pNumRows, Worksheet $pShee // Update worksheet: freeze pane if ($pSheet->getFreezePane()) { - $splitCell = $pSheet->getFreezePane(); - $topLeftCell = $pSheet->getTopLeftCell(); + $splitCell = $pSheet->getFreezePane() ?? ''; + $topLeftCell = $pSheet->getTopLeftCell() ?? ''; $splitCell = $this->updateCellReference($splitCell, $pBefore, $pNumCols, $pNumRows); $topLeftCell = $this->updateCellReference($topLeftCell, $pBefore, $pNumCols, $pNumRows); diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php index 91e4ccb931..da31fa8b40 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -1928,7 +1928,7 @@ public function removeAutoFilter() /** * Get Freeze Pane. * - * @return string + * @return null|string */ public function getFreezePane() { diff --git a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php index 894ce03acd..6b5fa6fd83 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php @@ -1584,7 +1584,7 @@ private function writePanes(): void return; } - [$column, $row] = Coordinate::indexesFromString($this->phpSheet->getFreezePane()); + [$column, $row] = Coordinate::indexesFromString($this->phpSheet->getFreezePane() ?? ''); $x = $column - 1; $y = $row - 1; diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index 99f3cfb09b..726e37977e 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/Namespace.Issue2109bTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/Namespace.Issue2109bTest.php new file mode 100644 index 0000000000..98da11ba73 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/Namespace.Issue2109bTest.php @@ -0,0 +1,89 @@ +listWorkSheetInfo(self::$testbook); + $info0 = $workSheetInfo[0]; + self::assertEquals('Sheet1', $info0['worksheetName']); + self::assertEquals('AF', $info0['lastColumnLetter']); + self::assertEquals(31, $info0['lastColumnIndex']); + self::assertEquals(4, $info0['totalRows']); + self::assertEquals(32, $info0['totalColumns']); + } + + public function testSheetNames(): void + { + $reader = new Xlsx(); + $worksheetNames = $reader->listWorksheetNames(self::$testbook); + self::assertEquals(['Sheet1'], $worksheetNames); + } + + public function testActive(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('Sheet1', $sheet->getTitle()); + self::assertNull($sheet->getFreezePane()); + self::assertNull($sheet->getTopLeftCell()); + self::assertSame('A1', $sheet->getSelectedCells()); + $spreadsheet->disconnectWorksheets(); + } + + private static function getCellValue(Worksheet $sheet, string $cell): string + { + $result = $sheet->getCell($cell)->getValue(); + + return (string) $result; + } + + public function testLoadXlsx(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getSheet(0); + self::assertEquals('Sheet1', $sheet->getTitle()); + $expectedArray = [ + 'A1' => 'Channel Name = Cartoon Network RSE', + 'B2' => 'Event ID', + 'C3' => '2021-05-17 03:00', + 'F4' => 'The Internet', + 'AF3' => '902476', + 'AF4' => '902477', + 'J2' => 'Episode Synopsis', + 'J3' => 'Gumball and Darwin\'s reputation is challenged and they really couldn\'t care less...', + 'J4' => 'Gumball accidentally uploads a video of himself and wants it gone.', + ]; + foreach ($expectedArray as $key => $value) { + self::assertSame($value, self::getCellValue($sheet, $key), "error in cell $key"); + } + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/Namespace.Openpyxl35Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/Namespace.Openpyxl35Test.php new file mode 100644 index 0000000000..16988a07b4 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/Namespace.Openpyxl35Test.php @@ -0,0 +1,111 @@ +listWorkSheetInfo(self::$testbook); + $info0 = $workSheetInfo[0]; + self::assertEquals('Shofar 5781', $info0['worksheetName']); + self::assertEquals('D', $info0['lastColumnLetter']); + self::assertEquals(3, $info0['lastColumnIndex']); + self::assertEquals(30, $info0['totalRows']); + self::assertEquals(4, $info0['totalColumns']); + } + + public function testSheetNames(): void + { + $reader = new Xlsx(); + $worksheetNames = $reader->listWorksheetNames(self::$testbook); + self::assertEquals(['Shofar 5781', 'Shofar 5782', 'Shofar 5783'], $worksheetNames); + } + + public function testActive(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('Shofar 5781', $sheet->getTitle()); + self::assertSame('A2', $sheet->getFreezePane()); + self::assertSame('A2', $sheet->getTopLeftCell()); + self::assertSame('D2', $sheet->getSelectedCells()); + $spreadsheet->disconnectWorksheets(); + } + + private static function getCellValue(Worksheet $sheet, string $cell): string + { + $result = $sheet->getCell($cell)->getValue(); + + return (string) $result; + } + + public function testLoadXlsx(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getSheet(0); + self::assertEquals('Shofar 5781', $sheet->getTitle()); + $expectedArray = [ + 'Shofar 5781' => [ + 'A1' => 'Weekday', + 'B6' => 'August 13', + 'A14' => 'Saturday', + 'C14' => 'Elul 13', + 'D30' => 'N/A', + 'B30' => 'September 6', + ], + 'Shofar 5782' => [ + 'C1' => 'Jewish Date', + 'B6' => 'September 1', + 'A14' => 'Friday', + 'C14' => 'Elul 13', + 'D28' => '', + 'B30' => 'September 25', + ], + 'Shofar 5783' => [ + 'B1' => 'Civil Date', + 'B6' => 'August 22', + 'A14' => 'Wednesday', + 'C14' => 'Elul 13', + 'D30' => 'N/A', + 'B30' => 'September 15', + ], + ]; + foreach ($expectedArray as $sheetName => $array1) { + $sheet = $spreadsheet->getSheetByName($sheetName); + if ($sheet === null) { + self::fail("Unable to find sheet $sheetName"); + } else { + foreach ($array1 as $key => $value) { + self::assertSame($value, self::getCellValue($sheet, $key), "error in sheet $sheetName cell $key"); + } + } + } + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceNonStdTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceNonStdTest.php new file mode 100644 index 0000000000..5002b5d709 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceNonStdTest.php @@ -0,0 +1,179 @@ +listWorkSheetInfo(self::$testbook); + $info0 = $workSheetInfo[0]; + self::assertEquals('SylkTest', $info0['worksheetName']); + self::assertEquals('J', $info0['lastColumnLetter']); + self::assertEquals(9, $info0['lastColumnIndex']); + self::assertEquals(18, $info0['totalRows']); + self::assertEquals(10, $info0['totalColumns']); + } + + public function testSheetNames(): void + { + $reader = new Xlsx(); + $worksheetNames = $reader->listWorksheetNames(self::$testbook); + self::assertEquals(['SylkTest', 'Second'], $worksheetNames); + } + + public function testActive(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('Second', $sheet->getTitle()); + self::assertSame('A2', $sheet->getFreezePane()); + self::assertSame('A2', $sheet->getTopLeftCell()); + self::assertSame('B3', $sheet->getSelectedCells()); + $sheet = $spreadsheet->getSheetByName('SylkTest'); + if ($sheet === null) { + self::fail('Unable to load expected sheet'); + } else { + self::assertNull($sheet->getFreezePane()); + self::assertNull($sheet->getTopLeftCell()); + } + } + + public function testLoadXlsx(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getSheet(0); + self::assertEquals('SylkTest', $sheet->getTitle()); + if (strpos(__FILE__, 'NonStd') !== false) { + self::markTestIncomplete('Not yet ready'); + } + + self::assertEquals('FFFF0000', $sheet->getCell('A1')->getStyle()->getFont()->getColor()->getARGB()); + self::assertEquals(Fill::FILL_PATTERN_GRAY125, $sheet->getCell('A2')->getStyle()->getFill()->getFillType()); + self::assertEquals(Font::UNDERLINE_SINGLE, $sheet->getCell('A4')->getStyle()->getFont()->getUnderline()); + self::assertEquals('Test with (;) in string', $sheet->getCell('A4')->getValue()); + + self::assertEquals(22269, $sheet->getCell('A10')->getValue()); + self::assertEquals('dd/mm/yyyy', $sheet->getCell('A10')->getStyle()->getNumberFormat()->getFormatCode()); + self::assertEquals('19/12/1960', $sheet->getCell('A10')->getFormattedValue()); + self::assertEquals(1.5, $sheet->getCell('A11')->getValue()); + self::assertEquals('# ?/?', $sheet->getCell('A11')->getStyle()->getNumberFormat()->getFormatCode()); + self::assertEquals('1 1/2', $sheet->getCell('A11')->getFormattedValue()); + + self::assertEquals('=B1+C1', $sheet->getCell('H1')->getValue()); + self::assertEquals('=E2&F2', $sheet->getCell('J2')->getValue()); + self::assertEquals('=SUM(C1:C4)', $sheet->getCell('I5')->getValue()); + self::assertEquals('=MEDIAN(B6:B8)', $sheet->getCell('B9')->getValue()); + + self::assertEquals(11, $sheet->getCell('E1')->getStyle()->getFont()->getSize()); + self::assertTrue($sheet->getCell('E1')->getStyle()->getFont()->getBold()); + self::assertTrue($sheet->getCell('E1')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_SINGLE, $sheet->getCell('E1')->getStyle()->getFont()->getUnderline()); + self::assertFalse($sheet->getCell('E2')->getStyle()->getFont()->getBold()); + self::assertFalse($sheet->getCell('E2')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_NONE, $sheet->getCell('E2')->getStyle()->getFont()->getUnderline()); + self::assertTrue($sheet->getCell('E3')->getStyle()->getFont()->getBold()); + self::assertFalse($sheet->getCell('E3')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_NONE, $sheet->getCell('E3')->getStyle()->getFont()->getUnderline()); + self::assertFalse($sheet->getCell('E4')->getStyle()->getFont()->getBold()); + self::assertTrue($sheet->getCell('E4')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_NONE, $sheet->getCell('E4')->getStyle()->getFont()->getUnderline()); + + self::assertTrue($sheet->getCell('F1')->getStyle()->getFont()->getBold()); + self::assertFalse($sheet->getCell('F1')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_SINGLE, $sheet->getCell('F1')->getStyle()->getFont()->getUnderline()); + self::assertFalse($sheet->getCell('F2')->getStyle()->getFont()->getBold()); + self::assertFalse($sheet->getCell('F2')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_NONE, $sheet->getCell('F2')->getStyle()->getFont()->getUnderline()); + self::assertTrue($sheet->getCell('F3')->getStyle()->getFont()->getBold()); + self::assertTrue($sheet->getCell('F3')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_NONE, $sheet->getCell('F3')->getStyle()->getFont()->getUnderline()); + self::assertFalse($sheet->getCell('F4')->getStyle()->getFont()->getBold()); + self::assertFalse($sheet->getCell('F4')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_NONE, $sheet->getCell('F4')->getStyle()->getFont()->getUnderline()); + + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C10')->getStyle()->getBorders()->getTop()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C10')->getStyle()->getBorders()->getRight()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C10')->getStyle()->getBorders()->getBottom()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C10')->getStyle()->getBorders()->getLeft()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C12')->getStyle()->getBorders()->getTop()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C12')->getStyle()->getBorders()->getRight()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C12')->getStyle()->getBorders()->getBottom()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C12')->getStyle()->getBorders()->getLeft()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C14')->getStyle()->getBorders()->getTop()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C14')->getStyle()->getBorders()->getRight()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C14')->getStyle()->getBorders()->getBottom()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C14')->getStyle()->getBorders()->getLeft()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C16')->getStyle()->getBorders()->getTop()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C16')->getStyle()->getBorders()->getRight()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C16')->getStyle()->getBorders()->getBottom()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C16')->getStyle()->getBorders()->getLeft()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C18')->getStyle()->getBorders()->getTop()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C18')->getStyle()->getBorders()->getRight()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C18')->getStyle()->getBorders()->getBottom()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C18')->getStyle()->getBorders()->getLeft()->getBorderStyle()); + } + + public function testLoadXlsxSheet2Contents(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getSheet(1); + self::assertEquals('Second', $sheet->getTitle()); + self::assertSame('Hyperlink', $sheet->getCell('B2')->getValue()); + $hyper = $sheet->getCell('B2')->getHyperlink(); + self::assertSame('http://www.example.com/', $hyper->getUrl()); + self::assertSame('Comment', $sheet->getCell('B3')->getValue()); + $comment = $sheet->getComment('B3'); + // Created as "threaded comment" with Excel 365, not quite as expected. + self::assertStringContainsString('This is a comment', (string) $comment); + } + + public function testLoadXlsxSheet2Styles(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getSheet(1); + self::assertEquals('Second', $sheet->getTitle()); + if (strpos(__FILE__, 'NonStd') !== false) { + self::markTestIncomplete('Not yet ready'); + } + self::assertEquals('center', $sheet->getCell('A2')->getStyle()->getAlignment()->getHorizontal()); + self::assertSame('inherit', $sheet->getCell('A2')->getStyle()->getProtection()->getLocked()); + self::assertEquals('top', $sheet->getCell('A3')->getStyle()->getAlignment()->getVertical()); + self::assertSame('unprotected', $sheet->getCell('A3')->getStyle()->getProtection()->getLocked()); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespacePurlTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespacePurlTest.php new file mode 100644 index 0000000000..ddd6ac5d52 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespacePurlTest.php @@ -0,0 +1,62 @@ +canRead($filename); + self::assertTrue($actual); + + $sheets = $reader->listWorksheetNames($filename); + self::assertEquals(['ml_out'], $sheets); + + $actual = $reader->listWorksheetInfo($filename); + $expected = [ + [ + 'worksheetName' => 'ml_out', + 'lastColumnLetter' => 'R', + 'lastColumnIndex' => 17, + 'totalRows' => '76', + 'totalColumns' => 18, + ], + ]; + + self::assertEquals($expected, $actual); + } + + public function testPurlLoad(): void + { + $filename = self::$testbook; + $reader = new Xlsx(); + $spreadsheet = $reader->load($filename); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('ml_out', $sheet->getTitle()); + self::assertSame('Item', $sheet->getCell('A1')->getValue()); + self::assertEquals(97.91, $sheet->getCell('G3')->getValue()); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceStdTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceStdTest.php new file mode 100644 index 0000000000..73e5d5c3ea --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceStdTest.php @@ -0,0 +1,179 @@ +listWorkSheetInfo(self::$testbook); + $info0 = $workSheetInfo[0]; + self::assertEquals('SylkTest', $info0['worksheetName']); + self::assertEquals('J', $info0['lastColumnLetter']); + self::assertEquals(9, $info0['lastColumnIndex']); + self::assertEquals(18, $info0['totalRows']); + self::assertEquals(10, $info0['totalColumns']); + } + + public function testSheetNames(): void + { + $reader = new Xlsx(); + $worksheetNames = $reader->listWorksheetNames(self::$testbook); + self::assertEquals(['SylkTest', 'Second'], $worksheetNames); + } + + public function testActive(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('Second', $sheet->getTitle()); + self::assertSame('A2', $sheet->getFreezePane()); + self::assertSame('A2', $sheet->getTopLeftCell()); + self::assertSame('B3', $sheet->getSelectedCells()); + $sheet = $spreadsheet->getSheetByName('SylkTest'); + if ($sheet === null) { + self::fail('Unable to load expected sheet'); + } else { + self::assertNull($sheet->getFreezePane()); + self::assertNull($sheet->getTopLeftCell()); + } + } + + public function testLoadXlsx(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getSheet(0); + self::assertEquals('SylkTest', $sheet->getTitle()); + if (strpos(__FILE__, 'NonStd') !== false) { + self::markTestIncomplete('Not yet ready'); + } + + self::assertEquals('FFFF0000', $sheet->getCell('A1')->getStyle()->getFont()->getColor()->getARGB()); + self::assertEquals(Fill::FILL_PATTERN_GRAY125, $sheet->getCell('A2')->getStyle()->getFill()->getFillType()); + self::assertEquals(Font::UNDERLINE_SINGLE, $sheet->getCell('A4')->getStyle()->getFont()->getUnderline()); + self::assertEquals('Test with (;) in string', $sheet->getCell('A4')->getValue()); + + self::assertEquals(22269, $sheet->getCell('A10')->getValue()); + self::assertEquals('dd/mm/yyyy', $sheet->getCell('A10')->getStyle()->getNumberFormat()->getFormatCode()); + self::assertEquals('19/12/1960', $sheet->getCell('A10')->getFormattedValue()); + self::assertEquals(1.5, $sheet->getCell('A11')->getValue()); + self::assertEquals('# ?/?', $sheet->getCell('A11')->getStyle()->getNumberFormat()->getFormatCode()); + self::assertEquals('1 1/2', $sheet->getCell('A11')->getFormattedValue()); + + self::assertEquals('=B1+C1', $sheet->getCell('H1')->getValue()); + self::assertEquals('=E2&F2', $sheet->getCell('J2')->getValue()); + self::assertEquals('=SUM(C1:C4)', $sheet->getCell('I5')->getValue()); + self::assertEquals('=MEDIAN(B6:B8)', $sheet->getCell('B9')->getValue()); + + self::assertEquals(11, $sheet->getCell('E1')->getStyle()->getFont()->getSize()); + self::assertTrue($sheet->getCell('E1')->getStyle()->getFont()->getBold()); + self::assertTrue($sheet->getCell('E1')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_SINGLE, $sheet->getCell('E1')->getStyle()->getFont()->getUnderline()); + self::assertFalse($sheet->getCell('E2')->getStyle()->getFont()->getBold()); + self::assertFalse($sheet->getCell('E2')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_NONE, $sheet->getCell('E2')->getStyle()->getFont()->getUnderline()); + self::assertTrue($sheet->getCell('E3')->getStyle()->getFont()->getBold()); + self::assertFalse($sheet->getCell('E3')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_NONE, $sheet->getCell('E3')->getStyle()->getFont()->getUnderline()); + self::assertFalse($sheet->getCell('E4')->getStyle()->getFont()->getBold()); + self::assertTrue($sheet->getCell('E4')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_NONE, $sheet->getCell('E4')->getStyle()->getFont()->getUnderline()); + + self::assertTrue($sheet->getCell('F1')->getStyle()->getFont()->getBold()); + self::assertFalse($sheet->getCell('F1')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_SINGLE, $sheet->getCell('F1')->getStyle()->getFont()->getUnderline()); + self::assertFalse($sheet->getCell('F2')->getStyle()->getFont()->getBold()); + self::assertFalse($sheet->getCell('F2')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_NONE, $sheet->getCell('F2')->getStyle()->getFont()->getUnderline()); + self::assertTrue($sheet->getCell('F3')->getStyle()->getFont()->getBold()); + self::assertTrue($sheet->getCell('F3')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_NONE, $sheet->getCell('F3')->getStyle()->getFont()->getUnderline()); + self::assertFalse($sheet->getCell('F4')->getStyle()->getFont()->getBold()); + self::assertFalse($sheet->getCell('F4')->getStyle()->getFont()->getItalic()); + self::assertEquals(Font::UNDERLINE_NONE, $sheet->getCell('F4')->getStyle()->getFont()->getUnderline()); + + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C10')->getStyle()->getBorders()->getTop()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C10')->getStyle()->getBorders()->getRight()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C10')->getStyle()->getBorders()->getBottom()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C10')->getStyle()->getBorders()->getLeft()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C12')->getStyle()->getBorders()->getTop()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C12')->getStyle()->getBorders()->getRight()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C12')->getStyle()->getBorders()->getBottom()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C12')->getStyle()->getBorders()->getLeft()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C14')->getStyle()->getBorders()->getTop()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C14')->getStyle()->getBorders()->getRight()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C14')->getStyle()->getBorders()->getBottom()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C14')->getStyle()->getBorders()->getLeft()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C16')->getStyle()->getBorders()->getTop()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C16')->getStyle()->getBorders()->getRight()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C16')->getStyle()->getBorders()->getBottom()->getBorderStyle()); + self::assertEquals(Border::BORDER_NONE, $sheet->getCell('C16')->getStyle()->getBorders()->getLeft()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C18')->getStyle()->getBorders()->getTop()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C18')->getStyle()->getBorders()->getRight()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C18')->getStyle()->getBorders()->getBottom()->getBorderStyle()); + self::assertEquals(Border::BORDER_THIN, $sheet->getCell('C18')->getStyle()->getBorders()->getLeft()->getBorderStyle()); + } + + public function testLoadXlsxSheet2Contents(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getSheet(1); + self::assertEquals('Second', $sheet->getTitle()); + self::assertSame('Hyperlink', $sheet->getCell('B2')->getValue()); + $hyper = $sheet->getCell('B2')->getHyperlink(); + self::assertSame('http://www.example.com/', $hyper->getUrl()); + self::assertSame('Comment', $sheet->getCell('B3')->getValue()); + $comment = $sheet->getComment('B3'); + // Created as "threaded comment" with Excel 365, not quite as expected. + self::assertStringContainsString('This is a comment', (string) $comment); + } + + public function testLoadXlsxSheet2Styles(): void + { + $reader = new Xlsx(); + $spreadsheet = $reader->load(self::$testbook); + $sheet = $spreadsheet->getSheet(1); + self::assertEquals('Second', $sheet->getTitle()); + if (strpos(__FILE__, 'NonStd') !== false) { + self::markTestIncomplete('Not yet ready'); + } + self::assertEquals('center', $sheet->getCell('A2')->getStyle()->getAlignment()->getHorizontal()); + self::assertSame('inherit', $sheet->getCell('A2')->getStyle()->getProtection()->getLocked()); + self::assertEquals('top', $sheet->getCell('A3')->getStyle()->getAlignment()->getVertical()); + self::assertSame('unprotected', $sheet->getCell('A3')->getStyle()->getProtection()->getLocked()); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/WorksheetInfoNamesTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/WorksheetInfoNamesTest.php new file mode 100644 index 0000000000..ed01db251b --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/WorksheetInfoNamesTest.php @@ -0,0 +1,80 @@ +listWorksheetInfo($filename); + + $expected = [ + [ + 'worksheetName' => 'Sheet1', + 'lastColumnLetter' => 'F', + 'lastColumnIndex' => 5, + 'totalRows' => '6', + 'totalColumns' => 6, + ], + ]; + + self::assertEquals($expected, $actual); + } + + public function testListWorksheetInfoNamespace(): void + { + $filename = 'tests/data/Reader/XLSX/namespaces.xlsx'; + $file = 'zip://'; + $file .= $filename; + $file .= '#xl/workbook.xml'; + $data = file_get_contents($file); + // confirm that file contains expected namespaced xml tag + if ($data === false) { + self::fail('Unable to read file'); + } else { + self::assertStringContainsString('listWorksheetInfo($filename); + + $expected = [ + [ + 'worksheetName' => 'transactions', + 'lastColumnLetter' => 'K', + 'lastColumnIndex' => 10, + 'totalRows' => 2, + 'totalColumns' => 11, + ], + ]; + + self::assertEquals($expected, $actual); + } + + public function testListWorksheetNames(): void + { + $filename = 'tests/data/Reader/XLSX/rowColumnAttributeTest.xlsx'; + $reader = new Xlsx(); + $actual = $reader->listWorksheetNames($filename); + + $expected = ['Sheet1']; + + self::assertEquals($expected, $actual); + } + + public function testListWorksheetNamesNamespace(): void + { + $filename = 'tests/data/Reader/XLSX/namespaces.xlsx'; + $reader = new Xlsx(); + $actual = $reader->listWorksheetNames($filename); + + $expected = ['transactions']; + + self::assertEquals($expected, $actual); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/XlsxTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/XlsxTest.php index 4a3a421f76..a3776eb4a9 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/XlsxTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/XlsxTest.php @@ -15,25 +15,6 @@ class XlsxTest extends TestCase { - public function testListWorksheetInfo(): void - { - $filename = 'tests/data/Reader/XLSX/rowColumnAttributeTest.xlsx'; - $reader = new Xlsx(); - $actual = $reader->listWorksheetInfo($filename); - - $expected = [ - [ - 'worksheetName' => 'Sheet1', - 'lastColumnLetter' => 'F', - 'lastColumnIndex' => 5, - 'totalRows' => '6', - 'totalColumns' => 6, - ], - ]; - - self::assertEquals($expected, $actual); - } - public function testLoadXlsxRowColumnAttributes(): void { $filename = 'tests/data/Reader/XLSX/rowColumnAttributeTest.xlsx'; diff --git a/tests/data/Reader/XLSX/issue2109b.xlsx b/tests/data/Reader/XLSX/issue2109b.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..632263b18e9f1aca7b4cff81f02958695847245a GIT binary patch literal 6925 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 literal 0 HcmV?d00001 diff --git a/tests/data/Reader/XLSX/namespacenonstd.xlsx b/tests/data/Reader/XLSX/namespacenonstd.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..1fe40823eb855d09a24b211bd8e076bdba2b3f6b GIT binary patch literal 13207 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 literal 0 HcmV?d00001 diff --git a/tests/data/Reader/XLSX/namespacepurl.xlsx b/tests/data/Reader/XLSX/namespacepurl.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..9b2a661dd4f3749b78b0bd0d9e2cfa199e83ee9b GIT binary patch literal 21205 zcmeFY^LK4Q*De_A#I|i)C$?>z*miPa+qP}n$%$>-+;KYZ+udW_9$)_l_pTpm*RD0k z*sE&KIiIKIdP+eW6buyz0tgBS2#5&iH%GvXEie!e7&s6R3J?^Cwy>S8vx%*8n9uUZn{r|cCH(r4%m2ukvM#OIQa{-YawebAViV7&)4qR>0Q^+*; z1P#)bBP_}f9|}`N)q1FYGDYhdZSn_R%<6S=O(DSpYx*}$c~XO4rP0-ncYL1`xuO9@ zRcg_Vagl6!m~b~Y5DU$aGHvTd^p?cm#AN7fO)xAc{r7TbLX}|x!ergl#}y#2s~3FZ z1DYVxR4$lCobG{2bl6hc>Rnxm%q(i8wV6(Ms`N>KXe!ith4!X50?G4+aqL7vh*5*#|W(F8Uul0dsLt zs(JED4Q}n0hq1pN(w!bIS;va2!|5ipOfKaB&|mJDCf-kWv_%gX{xhe9IZSbP9^FAu zP9caETR__#S4fYzPSqf%(07k&92mD4?@916i=3RXS?fTlZiX1~HQZ0IVE(X|!j9&v z&r!Iql^gI|*#*A|c2n-)alU@y?E4!ONa6nzraN(Y5MMvO$MSRc*H4)0JDONKG0^{~ z{XgOPzi~|dFHmIKGU?1LRqVD8>*j>JC;%It4!hE=+$-i=fYsKjpr0e2Q?{gTh z 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 UW(B4}1_mA=R0AgCnf@Ri0Nx)Q5dZ)H literal 0 HcmV?d00001 diff --git a/tests/data/Reader/XLSX/namespaces.openpyxl35.xlsx b/tests/data/Reader/XLSX/namespaces.openpyxl35.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..631a6702d629d9c5d379539a01981d7150b921f1 GIT binary patch literal 8639 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 V@<=dQ5&*#X^@qJKndHB&{s-jigJJ*x literal 0 HcmV?d00001 diff --git a/tests/data/Reader/XLSX/namespaces.xlsx b/tests/data/Reader/XLSX/namespaces.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..3b92ace62ade6797db152de018c95e1f4ef13255 GIT binary patch literal 8582 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 MEQV8KW!k;^Kd(eoU;qFB literal 0 HcmV?d00001 diff --git a/tests/data/Reader/XLSX/namespacestd.xlsx b/tests/data/Reader/XLSX/namespacestd.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..15969e23204943c67a6286e439605782071de97d GIT binary patch literal 15773 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 Date: Fri, 18 Jun 2021 22:12:17 -0700 Subject: [PATCH 2/3] Scrutinizer Add 2 casts to eliminate minor Scrutinizer problem. --- src/PhpSpreadsheet/Reader/Xlsx.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 593858e3ba..896d1c0a07 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -1160,10 +1160,10 @@ public function load(string $pFilename, int $flags = 0): Spreadsheet $objDrawing->setOffsetX((int) Drawing::EMUToPixels((int) $oneCellAnchor->from->colOff)); $objDrawing->setOffsetY(Drawing::EMUToPixels((int) $oneCellAnchor->from->rowOff)); $objDrawing->setResizeProportional(false); - $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cx'))); + $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem((int) self::getAttributes($oneCellAnchor->ext), 'cx'))); $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem((int) self::getAttributes($oneCellAnchor->ext), 'cy'))); if ($xfrm) { - $objDrawing->setRotation((int) Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($xfrm), 'rot'))); + $objDrawing->setRotation((int) Drawing::angleToDegrees((int) self::getArrayItem(self::getAttributes($xfrm), 'rot'))); } if ($outerShdw) { $shadow = $objDrawing->getShadow(); From a488574cb614daec5e52d59017e46752228270ae Mon Sep 17 00:00:00 2001 From: Owen Leibman Date: Fri, 18 Jun 2021 22:50:06 -0700 Subject: [PATCH 3/3] One More Attempt to Satisfy Scrutinizer We shall sess. --- src/PhpSpreadsheet/Reader/Xlsx.php | 6 +++--- src/PhpSpreadsheet/Shared/Drawing.php | 7 +++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 896d1c0a07..b9e3e5a442 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -1157,13 +1157,13 @@ public function load(string $pFilename, int $flags = 0): Spreadsheet } $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((int) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1)); - $objDrawing->setOffsetX((int) Drawing::EMUToPixels((int) $oneCellAnchor->from->colOff)); - $objDrawing->setOffsetY(Drawing::EMUToPixels((int) $oneCellAnchor->from->rowOff)); + $objDrawing->setOffsetX((int) Drawing::EMUToPixels($oneCellAnchor->from->colOff)); + $objDrawing->setOffsetY(Drawing::EMUToPixels($oneCellAnchor->from->rowOff)); $objDrawing->setResizeProportional(false); $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem((int) self::getAttributes($oneCellAnchor->ext), 'cx'))); $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem((int) self::getAttributes($oneCellAnchor->ext), 'cy'))); if ($xfrm) { - $objDrawing->setRotation((int) Drawing::angleToDegrees((int) self::getArrayItem(self::getAttributes($xfrm), 'rot'))); + $objDrawing->setRotation((int) Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($xfrm), 'rot'))); } if ($outerShdw) { $shadow = $objDrawing->getShadow(); diff --git a/src/PhpSpreadsheet/Shared/Drawing.php b/src/PhpSpreadsheet/Shared/Drawing.php index 08982d84a3..0e9f0fb68a 100644 --- a/src/PhpSpreadsheet/Shared/Drawing.php +++ b/src/PhpSpreadsheet/Shared/Drawing.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheet\Shared; use GdImage; +use SimpleXMLElement; class Drawing { @@ -21,12 +22,13 @@ public static function pixelsToEMU($pixelValue) /** * Convert EMU to pixels. * - * @param int $emuValue Value in EMU + * @param int|SimpleXMLElement $emuValue Value in EMU * * @return int Value in pixels */ public static function EMUToPixels($emuValue) { + $emuValue = (int) $emuValue; if ($emuValue != 0) { return (int) round($emuValue / 9525); } @@ -136,12 +138,13 @@ public static function degreesToAngle($pValue) /** * Convert angle to degrees. * - * @param int $pValue Angle + * @param int|SimpleXMLElement $pValue Angle * * @return int Degrees */ public static function angleToDegrees($pValue) { + $pValue = (int) $pValue; if ($pValue != 0) { return (int) round($pValue / 60000); }