From 8fcde62db649b5e8d43b7e6c3b8cf487056b045f Mon Sep 17 00:00:00 2001 From: Emeka Oziri Date: Fri, 3 Oct 2025 23:37:56 +0100 Subject: [PATCH] Xlsx: register x and o namespaces for VML shapes; add test for #4505 Fixes SimpleXMLElement::xpath(): Undefined namespace prefix when reading VML comments/shapes by registering x: (Excel) and o: (Office) namespaces alongside v:. Includes: - tests/PhpSpreadsheetTests/Reader/Xlsx/Issue4505Test.php - tests/data/Reader/XLSX/issue.4505.xlsx Refs: https://github.com/PHPOffice/PhpSpreadsheet/issues/4505 --- src/PhpSpreadsheet/Reader/Xlsx.php | 2 + .../Reader/Xlsx/Issue4505Test.php | 80 ++++++++++++++++++ tests/data/Reader/XLSX/issue.4505.xlsx | Bin 0 -> 6417 bytes 3 files changed, 82 insertions(+) create mode 100644 tests/PhpSpreadsheetTests/Reader/Xlsx/Issue4505Test.php create mode 100644 tests/data/Reader/XLSX/issue.4505.xlsx diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index f8a0b11324..53bb7ed752 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -1206,6 +1206,8 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet foreach ($shapes as $shape) { /** @var SimpleXMLElement $shape */ $shape->registerXPathNamespace('v', Namespaces::URN_VML); + $shape->registerXPathNamespace('x', Namespaces::URN_VML); + $shape->registerXPathNamespace('o', Namespaces::URN_MSOFFICE); if (isset($shape['style'])) { $style = (string) $shape['style']; diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue4505Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue4505Test.php new file mode 100644 index 0000000000..fc859bf578 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue4505Test.php @@ -0,0 +1,80 @@ +load(self::$file); + $sheet = $spreadsheet->getActiveSheet(); + + $comments = $sheet->getComments(); + self::assertSame('Sheet1', $sheet->getTitle()); + + if (!empty($comments)) { + $comment = reset($comments); + $comment->getText(); + } + + $spreadsheet->disconnectWorksheets(); + } + + public function testVmlFileContainsRequiredNamespaces(): void + { + $file = 'zip://' . self::$file . '#xl/drawings/vmlDrawing1.vml'; + $data = file_get_contents($file); + + if ($data === false) { + self::markTestSkipped('Test file issue.4505.xlsx not found or VML file missing.'); + } + + self::assertStringContainsString('registerXPathNamespace('v', XlsxNamespaces::URN_VML); + $shape->registerXPathNamespace('x', XlsxNamespaces::URN_VML); + $shape->registerXPathNamespace('o', XlsxNamespaces::URN_MSOFFICE); + + $clientData = $shape->xpath('.//x:ClientData'); + self::assertNotEmpty($clientData, 'XPath with x: prefix works with namespace registration'); + + $relid = $shape->xpath('.//v:fill/@o:relid'); + self::assertNotEmpty($relid, 'XPath with o: prefix works with namespace registration'); + + $row = $clientData[0]->xpath('.//x:Row'); + self::assertNotEmpty($row, 'Can access nested x: elements'); + self::assertEquals('5', (string) $row[0], 'Row value is correct'); + + self::assertEquals('rId1', (string) $relid[0], 'RelId value is correct'); + } +} diff --git a/tests/data/Reader/XLSX/issue.4505.xlsx b/tests/data/Reader/XLSX/issue.4505.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..9b0a417b59a666fceb30ba0bb18d7efb198e48c0 GIT binary patch literal 6417 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 X`dYV;(eO*fA^^A{)vQ$VUvK{dXMOWd literal 0 HcmV?d00001