From a8dd63c1f1c1ff7ff2f9431b4ef9a7da7bc3c443 Mon Sep 17 00:00:00 2001 From: Toon Verwerft Date: Sun, 14 Jan 2024 15:46:00 +0100 Subject: [PATCH] Fix import of removed (optimized) root xmlns during flattening schemas --- src/Xml/Configurator/FlattenXsdImports.php | 27 ++++++++++++++- .../Configurator/FlattenXsdImportsTest.php | 33 +++++++++++++------ .../root-xmlns-import-issue-result.wsdl | 30 +++++++++++++++++ .../flattening/root-xmlns-import-issue.wsdl | 19 +++++++++++ .../xsd/root-xmlns-import-issue-core.xsd | 10 ++++++ 5 files changed, 108 insertions(+), 11 deletions(-) create mode 100644 tests/fixtures/flattening/result/root-xmlns-import-issue-result.wsdl create mode 100644 tests/fixtures/flattening/root-xmlns-import-issue.wsdl create mode 100644 tests/fixtures/flattening/xsd/root-xmlns-import-issue-core.xsd diff --git a/src/Xml/Configurator/FlattenXsdImports.php b/src/Xml/Configurator/FlattenXsdImports.php index 5589848..d76757f 100644 --- a/src/Xml/Configurator/FlattenXsdImports.php +++ b/src/Xml/Configurator/FlattenXsdImports.php @@ -18,6 +18,7 @@ use VeeWee\Xml\Exception\RuntimeException; use function Psl\Type\instance_of; use function Psl\Type\nullable; +use function VeeWee\Xml\Dom\Assert\assert_element; use function VeeWee\Xml\Dom\Locator\Node\children; use function VeeWee\Xml\Dom\Manipulator\Element\copy_named_xmlns_attributes; use function VeeWee\Xml\Dom\Manipulator\Node\append_external_node; @@ -188,7 +189,8 @@ private function registerSchemaInTypes(DOMElement $schema): void // If no schema exists yet: Add the newly loaded schema as a completely new schema in the WSDL types. if (!$existingSchema) { - append_external_node($types, $schema); + $imported = assert_element(append_external_node($types, $schema)); + $this->fixRemovedDefaultXmlnsDeclarationsDuringImport($imported, $schema); return; } @@ -196,8 +198,31 @@ private function registerSchemaInTypes(DOMElement $schema): void // This is to make sure that possible QNames (strings) get resolved in XSD. // Finally - all children of the newly loaded schema can be appended to the existing schema. copy_named_xmlns_attributes($existingSchema, $schema); + $this->fixRemovedDefaultXmlnsDeclarationsDuringImport($existingSchema, $schema); children($schema)->forEach( static fn (DOMNode $node) => append_external_node($existingSchema, $node) ); } + + /** + * @see https://gist.github.com/veewee/32c3aa94adcf878700a9d5baa4b2a2de + * + * PHP does an optimization of namespaces during `importNode()`. + * In some cases, this causes the root xmlns to be removed from the imported node which could lead to xsd qname errors. + * + * This function tries to re-add the root xmlns if it's available on the source but not on the target. + * + * It will most likely be solved in PHP 8.4's new spec compliant DOM\XMLDocument implementation. + * @see https://github.com/php/php-src/pull/13031 + * + * For now, this will do the trick. + */ + private function fixRemovedDefaultXmlnsDeclarationsDuringImport(DOMElement $target, DOMElement $source): void + { + if (!$source->getAttribute('xmlns') || $target->hasAttribute('xmlns')) { + return; + } + + $target->setAttribute('xmlns', $source->getAttribute('xmlns')); + } } diff --git a/tests/Unit/Xml/Configurator/FlattenXsdImportsTest.php b/tests/Unit/Xml/Configurator/FlattenXsdImportsTest.php index 9d9ae90..bbbec67 100644 --- a/tests/Unit/Xml/Configurator/FlattenXsdImportsTest.php +++ b/tests/Unit/Xml/Configurator/FlattenXsdImportsTest.php @@ -8,6 +8,7 @@ use Soap\Wsdl\Loader\StreamWrapperLoader; use Soap\Wsdl\Xml\Configurator\FlattenXsdImports; use VeeWee\Xml\Dom\Document; +use function VeeWee\Xml\Dom\Configurator\canonicalize; use function VeeWee\Xml\Dom\Configurator\comparable; final class FlattenXsdImportsTest extends TestCase @@ -16,47 +17,59 @@ final class FlattenXsdImportsTest extends TestCase * * @dataProvider provideTestCases */ - public function test_it_can_flatten_xsd_imports(string $wsdlUri, Document $expected): void + public function test_it_can_flatten_xsd_imports(string $wsdlUri, Document $expected, callable $xmlConfigurator): void { $wsdl = Document::fromXmlFile($wsdlUri); $configurator = new FlattenXsdImports( $wsdlUri, FlatteningContext::forWsdl($wsdlUri, $wsdl, new StreamWrapperLoader()) ); - $flattened = Document::fromUnsafeDocument($wsdl->toUnsafeDocument(), $configurator, comparable()); + $flattened = Document::fromUnsafeDocument($wsdl->toUnsafeDocument(), $configurator, $xmlConfigurator); - static::assertSame($expected->toXmlString(), $flattened->toXmlString()); + static::assertSame($expected->reconfigure($xmlConfigurator)->toXmlString(), $flattened->toXmlString()); } public function provideTestCases() { yield 'single-xsd' => [ 'wsdl' => FIXTURE_DIR.'/flattening/single-xsd.wsdl', - 'expected' => Document::fromXmlFile(FIXTURE_DIR.'/flattening/result/single-xsd-result.wsdl', comparable()), + 'expected' => Document::fromXmlFile(FIXTURE_DIR.'/flattening/result/single-xsd-result.wsdl'), + comparable(), ]; yield 'once-xsd' => [ 'wsdl' => FIXTURE_DIR.'/flattening/once-xsd.wsdl', - 'expected' => Document::fromXmlFile(FIXTURE_DIR.'/flattening/result/once-xsd-result.wsdl', comparable()), + 'expected' => Document::fromXmlFile(FIXTURE_DIR.'/flattening/result/once-xsd-result.wsdl'), + comparable(), ]; yield 'multi-xsd' => [ 'wsdl' => FIXTURE_DIR.'/flattening/multi-xsd.wsdl', - 'expected' => Document::fromXmlFile(FIXTURE_DIR.'/flattening/result/multi-xsd-result.wsdl', comparable()), + 'expected' => Document::fromXmlFile(FIXTURE_DIR.'/flattening/result/multi-xsd-result.wsdl'), + comparable(), ]; yield 'circular-xsd' => [ 'wsdl' => FIXTURE_DIR.'/flattening/circular-xsd.wsdl', - 'expected' => Document::fromXmlFile(FIXTURE_DIR.'/flattening/result/circular-xsd-result.wsdl', comparable()), + 'expected' => Document::fromXmlFile(FIXTURE_DIR.'/flattening/result/circular-xsd-result.wsdl'), + comparable(), ]; yield 'redefine-xsd' => [ 'wsdl' => FIXTURE_DIR.'/flattening/redefine-xsd.wsdl', - 'expected' => Document::fromXmlFile(FIXTURE_DIR.'/flattening/result/redefine-xsd-result.wsdl', comparable()), + 'expected' => Document::fromXmlFile(FIXTURE_DIR.'/flattening/result/redefine-xsd-result.wsdl'), + comparable(), ]; yield 'tnsless-xsd' => [ 'wsdl' => FIXTURE_DIR.'/flattening/tnsless-xsd.wsdl', - 'expected' => Document::fromXmlFile(FIXTURE_DIR.'/flattening/result/tnsless-xsd-result.wsdl', comparable()), + 'expected' => Document::fromXmlFile(FIXTURE_DIR.'/flattening/result/tnsless-xsd-result.wsdl'), + comparable(), ]; yield 'grouped-xsd' => [ 'wsdl' => FIXTURE_DIR.'/flattening/grouped-xsd.wsdl', - 'expected' => Document::fromXmlFile(FIXTURE_DIR.'/flattening/result/grouped-xsd-result.wsdl', comparable()), + 'expected' => Document::fromXmlFile(FIXTURE_DIR.'/flattening/result/grouped-xsd-result.wsdl'), + comparable(), + ]; + yield 'root-xmlns-import-issue' => [ + 'wsdl' => FIXTURE_DIR.'/flattening/root-xmlns-import-issue.wsdl', + 'expected' => Document::fromXmlFile(FIXTURE_DIR.'/flattening/result/root-xmlns-import-issue-result.wsdl'), + canonicalize(), ]; } } diff --git a/tests/fixtures/flattening/result/root-xmlns-import-issue-result.wsdl b/tests/fixtures/flattening/result/root-xmlns-import-issue-result.wsdl new file mode 100644 index 0000000..46425d0 --- /dev/null +++ b/tests/fixtures/flattening/result/root-xmlns-import-issue-result.wsdl @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + diff --git a/tests/fixtures/flattening/root-xmlns-import-issue.wsdl b/tests/fixtures/flattening/root-xmlns-import-issue.wsdl new file mode 100644 index 0000000..7ac04cf --- /dev/null +++ b/tests/fixtures/flattening/root-xmlns-import-issue.wsdl @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/tests/fixtures/flattening/xsd/root-xmlns-import-issue-core.xsd b/tests/fixtures/flattening/xsd/root-xmlns-import-issue-core.xsd new file mode 100644 index 0000000..ae760d6 --- /dev/null +++ b/tests/fixtures/flattening/xsd/root-xmlns-import-issue-core.xsd @@ -0,0 +1,10 @@ + + + + + + + + +