From 137557a7713c4662fb36103ebff2a0c2809566f8 Mon Sep 17 00:00:00 2001 From: Toon Verwerft Date: Tue, 16 Dec 2025 14:24:30 +0100 Subject: [PATCH] Allow configuration of libxml options for decoding XML --- src/Encoder/Method/ResponseEncoder.php | 2 +- src/EncoderRegistry.php | 22 ++++++++++++++++++- src/Xml/Reader/OperationReader.php | 5 +++-- src/Xml/Reader/SoapEnvelopeReader.php | 7 ++++-- tests/Unit/Xml/Reader/OperationReaderTest.php | 19 ++++++++++++++-- .../Xml/Reader/SoapEnvelopeReaderTest.php | 22 +++++++++++++++++-- 6 files changed, 67 insertions(+), 10 deletions(-) diff --git a/src/Encoder/Method/ResponseEncoder.php b/src/Encoder/Method/ResponseEncoder.php index a0ac345..3cc469e 100644 --- a/src/Encoder/Method/ResponseEncoder.php +++ b/src/Encoder/Method/ResponseEncoder.php @@ -101,7 +101,7 @@ private function decode(MethodContext $context, string $xml): array // The SoapResponse only contains the payload of the response (with no headers). // It can be parsed directly as XML. invariant($xml !== '', 'Expected a non-empty response payload. Received an empty HTTP response'); - $parts = (new OperationReader($meta))($xml)->elements(); + $parts = (new OperationReader($meta))($xml, $context->registry->decoderLibXmlOptions())->elements(); return map( $parts, diff --git a/src/EncoderRegistry.php b/src/EncoderRegistry.php index fcc921e..520b0da 100644 --- a/src/EncoderRegistry.php +++ b/src/EncoderRegistry.php @@ -26,10 +26,12 @@ final class EncoderRegistry /** * @param MutableMap> $simpleTypeMap * @param MutableMap> $complextTypeMap + * @param int $decoderLibXmlOptions - bitmask of LIBXML_* constants https://www.php.net/manual/en/libxml.constants.php */ private function __construct( private MutableMap $simpleTypeMap, - private MutableMap $complextTypeMap + private MutableMap $complextTypeMap, + private int $decoderLibXmlOptions = 0, ) { } @@ -341,4 +343,22 @@ public function detectEncoderForContext(Context $context): XmlEncoder { return EncoderDetector::default()($context); } + + /** + * @param int $libXmlOptions - bitmask of LIBXML_* constants https://www.php.net/manual/en/libxml.constants.php + */ + public function setDecoderLibXmlOptions(int $libXmlOptions): self + { + $this->decoderLibXmlOptions = $libXmlOptions; + + return $this; + } + + /** + * @return int - bitmask of LIBXML_* constants https://www.php.net/manual/en/libxml.constants.php + */ + public function decoderLibXmlOptions(): int + { + return $this->decoderLibXmlOptions; + } } diff --git a/src/Xml/Reader/OperationReader.php b/src/Xml/Reader/OperationReader.php index d881806..9190c75 100644 --- a/src/Xml/Reader/OperationReader.php +++ b/src/Xml/Reader/OperationReader.php @@ -22,14 +22,15 @@ public function __construct( * Reads all operation response message parts: * * @param non-empty-string $xml + * @param int $libXmlOptions - bitmask of LIBXML_* constants https://www.php.net/manual/en/libxml.constants.php */ - public function __invoke(string $xml): ElementList + public function __invoke(string $xml, int $libXmlOptions = 0): ElementList { $bindingStyle = BindingStyle::tryFrom($this->meta->bindingStyle()->unwrapOr(BindingStyle::DOCUMENT->value)); // The Response can contain out of multiple response parts. // Therefore, it is being wrapped by a central root element: - $body = (new SoapEnvelopeReader())($xml); + $body = (new SoapEnvelopeReader())($xml, $libXmlOptions); $bodyElement = $body->element(); $elements = match($bindingStyle) { diff --git a/src/Xml/Reader/SoapEnvelopeReader.php b/src/Xml/Reader/SoapEnvelopeReader.php index 88ddc80..1246980 100644 --- a/src/Xml/Reader/SoapEnvelopeReader.php +++ b/src/Xml/Reader/SoapEnvelopeReader.php @@ -8,15 +8,18 @@ use Soap\Xml\Locator\SoapBodyLocator; use VeeWee\Xml\Dom\Document; use function VeeWee\Xml\Dom\Assert\assert_element; +use function VeeWee\Xml\Dom\Configurator\loader; +use function VeeWee\Xml\Dom\Loader\xml_string_loader; final class SoapEnvelopeReader { /** * @param non-empty-string $xml + * @param int $libXmlOptions - bitmask of LIBXML_* constants https://www.php.net/manual/en/libxml.constants.php */ - public function __invoke(string $xml): Element + public function __invoke(string $xml, int $libXmlOptions = 0): Element { - $envelope = Document::fromXmlString($xml); + $envelope = Document::configure(loader(xml_string_loader($xml, $libXmlOptions))); // Make sure it does not contain a fault response before parsing the body parts. (new SoapFaultGuard())($envelope); diff --git a/tests/Unit/Xml/Reader/OperationReaderTest.php b/tests/Unit/Xml/Reader/OperationReaderTest.php index d108e38..8980350 100644 --- a/tests/Unit/Xml/Reader/OperationReaderTest.php +++ b/tests/Unit/Xml/Reader/OperationReaderTest.php @@ -12,15 +12,16 @@ use Soap\Engine\Metadata\Model\MethodMeta; use Soap\WsdlReader\Model\Definitions\BindingStyle; use function Psl\Vec\map; +use const LIBXML_NOCDATA; #[CoversClass(OperationReader::class)] final class OperationReaderTest extends TestCase { #[DataProvider('provideEnvelopeCases')] - public function test_it_can_read_a_soap_envelope(MethodMeta $meta, string $envelope, array $expected): void + public function test_it_can_read_a_soap_envelope(MethodMeta $meta, string $envelope, array $expected, int $libXmlOptions = 0): void { $reader = new OperationReader($meta); - $actual = $reader($envelope); + $actual = $reader($envelope, $libXmlOptions); static::assertSame( $expected, @@ -91,5 +92,19 @@ public static function provideEnvelopeCases() 'c', ], ]; + + yield 'xml-options' => [ + $methodMeta->withBindingStyle(BindingStyle::DOCUMENT->value), + << + + + + + EOXML, + ['content'], + LIBXML_NOCDATA + ]; + } } diff --git a/tests/Unit/Xml/Reader/SoapEnvelopeReaderTest.php b/tests/Unit/Xml/Reader/SoapEnvelopeReaderTest.php index 13cdcd4..a9ce5e1 100644 --- a/tests/Unit/Xml/Reader/SoapEnvelopeReaderTest.php +++ b/tests/Unit/Xml/Reader/SoapEnvelopeReaderTest.php @@ -13,16 +13,17 @@ use Soap\Encoding\Xml\Reader\SoapEnvelopeReader; use Soap\Encoding\Xml\Writer\SoapEnvelopeWriter; use Soap\WsdlReader\Model\Definitions\SoapVersion; +use const LIBXML_NOCDATA; #[CoversClass(SoapEnvelopeWriter::class)] #[CoversClass(SoapFaultGuard::class)] final class SoapEnvelopeReaderTest extends TestCase { #[DataProvider('provideEnvelopeCases')] - public function test_it_can_read_a_soap_envelope(SoapVersion $version, string $envelope, string $expected): void + public function test_it_can_read_a_soap_envelope(SoapVersion $version, string $envelope, string $expected, int $libXmlOptions = 0): void { $reader = new SoapEnvelopeReader(); - $actual = $reader($envelope); + $actual = $reader($envelope, $libXmlOptions); static::assertXmlStringEqualsXmlString($expected, $actual->value()); } @@ -83,5 +84,22 @@ public static function provideEnvelopeCases() EOXML, ]; + + yield 'xml-options' => [ + SoapVersion::SOAP_12, + << + + + + + EOXML, + << + content + + EOXML, + LIBXML_NOCDATA + ]; } }