From afc29e15d5c1b947dda4a9f4876522b124ef31be Mon Sep 17 00:00:00 2001 From: Jamie Shiell Date: Sun, 8 Feb 2026 16:39:03 +0000 Subject: [PATCH] Strip namespaces from namespaced XML plugin response, so we get usuable output --- .../Plugin/Parsers/XmlResponseParser.php | 23 ++++++++++++++- tests/Feature/PluginResponseTest.php | 29 ++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/app/Services/Plugin/Parsers/XmlResponseParser.php b/app/Services/Plugin/Parsers/XmlResponseParser.php index b82ba80..037b3c6 100644 --- a/app/Services/Plugin/Parsers/XmlResponseParser.php +++ b/app/Services/Plugin/Parsers/XmlResponseParser.php @@ -18,7 +18,7 @@ class XmlResponseParser implements ResponseParser } try { - $xml = simplexml_load_string($response->body()); + $xml = $this->simplexml_load_string_strip_namespaces($response->body()); if ($xml === false) { throw new Exception('Invalid XML content'); } @@ -43,4 +43,25 @@ class XmlResponseParser implements ResponseParser return $array; } + + function simplexml_load_string_strip_namespaces($xml_response) { + $xml = simplexml_load_string($xml_response); + if ($xml === false) { + return false; + } + + $namespaces = array_keys($xml->getDocNamespaces(true)); + $namespaces = array_filter($namespaces, function($name) { return !empty($name); }); + if (count($namespaces) == 0) { + return $xml; + } + $namespaces = array_map(function($ns) { return "$ns:"; }, $namespaces); + + $xml_no_namespaces = str_replace( + array_merge(["xmlns="], $namespaces), + array_merge(["ns="], array_fill(0, count($namespaces), '')), + $xml_response + ); + return simplexml_load_string($xml_no_namespaces); + } } diff --git a/tests/Feature/PluginResponseTest.php b/tests/Feature/PluginResponseTest.php index 2a75c9e..bcc06f8 100644 --- a/tests/Feature/PluginResponseTest.php +++ b/tests/Feature/PluginResponseTest.php @@ -36,7 +36,7 @@ test('plugin parses JSON responses correctly', function (): void { ]); }); -test('plugin parses XML responses and wraps under rss key', function (): void { +test('plugin parses RSS XML responses and wraps under rss key', function (): void { $xmlContent = ' @@ -73,6 +73,33 @@ test('plugin parses XML responses and wraps under rss key', function (): void { expect($plugin->data_payload['rss']['channel']['item'])->toHaveCount(2); }); +test('plugin parses namespaces XML responses and wraps under root key', function (): void { + $xmlContent = ' + + + Cherry + + '; + + Http::fake([ + 'example.com/namespace.xml' => Http::response($xmlContent, 200, ['Content-Type' => 'application/xml']), + ]); + + $plugin = Plugin::factory()->create([ + 'data_strategy' => 'polling', + 'polling_url' => 'https://example.com/namespace.xml', + 'polling_verb' => 'get', + ]); + + $plugin->updateDataPayload(); + + $plugin->refresh(); + + expect($plugin->data_payload)->toHaveKey('rss'); + expect($plugin->data_payload['rss'])->toHaveKey('icing'); + expect($plugin->data_payload['rss']['icing']['ontop'])->toBe('Cherry'); +}); + test('plugin parses JSON-parsable response body as JSON', function (): void { $jsonContent = '{"title": "Test Data", "items": [1, 2, 3]}';