diff --git a/src/Metadata/Converter/Types/Configurator/AttributeSingleConfigurator.php b/src/Metadata/Converter/Types/Configurator/AttributeSingleConfigurator.php index 499acdf..cbd3472 100644 --- a/src/Metadata/Converter/Types/Configurator/AttributeSingleConfigurator.php +++ b/src/Metadata/Converter/Types/Configurator/AttributeSingleConfigurator.php @@ -3,6 +3,7 @@ namespace Soap\WsdlReader\Metadata\Converter\Types\Configurator; +use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeRef; use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeSingle; use Soap\Engine\Metadata\Model\TypeMeta; use Soap\Engine\Metadata\Model\XsdType as EngineType; @@ -17,6 +18,10 @@ public function __invoke(EngineType $engineType, mixed $xsdType, TypesConverterC return $engineType; } + // A ref points to a global attribute declaration, which is always qualified with the target namespace + // of the schema that declares it. The form and attributeFormDefault rules only apply to local declarations. + $isQualified = $xsdType instanceof AttributeRef || $xsdType->isQualified(); + $configure = pipe( static fn (EngineType $engineType): EngineType => (new RestrictionsConfigurator())($engineType, $xsdType->getType()?->getRestriction(), $context), ); @@ -25,7 +30,7 @@ public function __invoke(EngineType $engineType, mixed $xsdType, TypesConverterC $engineType ->withMeta( static fn (TypeMeta $meta): TypeMeta => $meta - ->withIsQualified($xsdType->isQualified()) + ->withIsQualified($isQualified) ->withIsNil($xsdType->isNil()) ->withIsNullable($meta->isNullable()->unwrapOr(false) || $xsdType->isNil()) ->withUse($xsdType->getUse()) diff --git a/src/Parser/Definitions/NamespacesParser.php b/src/Parser/Definitions/NamespacesParser.php index 443d2b2..05a05fc 100644 --- a/src/Parser/Definitions/NamespacesParser.php +++ b/src/Parser/Definitions/NamespacesParser.php @@ -6,6 +6,7 @@ use Dom\NamespaceInfo; use Soap\WsdlReader\Model\Definitions\Namespaces; use VeeWee\Xml\Dom\Document; +use VeeWee\Xml\Xmlns\Xmlns; use function Psl\Dict\merge; use function Psl\Iter\reduce; use function VeeWee\Xml\Dom\Locator\document_element; @@ -27,7 +28,7 @@ public static function tryParse(Document $wsdl): Namespaces */ static fn (array $map, NamespaceInfo $node): array => merge($map, [(string)$node->prefix => (string)$node->namespaceURI]), - [] + ['xml' => Xmlns::xml()->value()] ), reduce( $allNamespaces, @@ -37,7 +38,7 @@ public static function tryParse(Document $wsdl): Namespaces */ static fn (array $map, NamespaceInfo $node): array => merge($map, [(string)$node->namespaceURI => (string)$node->prefix]), - [] + [Xmlns::xml()->value() => 'xml'] ) ); } diff --git a/tests/Unit/Metadata/Coverter/Types/Configurator/AttributeQualificationTest.php b/tests/Unit/Metadata/Coverter/Types/Configurator/AttributeQualificationTest.php new file mode 100644 index 0000000..11b6893 --- /dev/null +++ b/tests/Unit/Metadata/Coverter/Types/Configurator/AttributeQualificationTest.php @@ -0,0 +1,171 @@ +getType(); + + static::assertTrue($type->getMeta()->isAttribute()->unwrapOr(false)); + static::assertSame($expectedQualified, $type->getMeta()->isQualified()->unwrapOr(false)); + static::assertSame($expectedTargetNamespace, $type->getXmlTargetNamespace()); + } + + public static function provideAttributes(): iterable + { + yield 'ref to xml:lang' => ['Direct', 'lang', true, self::XML]; + yield 'ref to global attribute in foreign schema' => ['Direct', 'flag', true, self::OTHER]; + yield 'ref to global attribute in same schema' => ['Direct', 'glob', true, self::TNS]; + yield 'local unqualified attribute' => ['Direct', 'plain', false, self::TNS]; + yield 'local form=qualified attribute' => ['Direct', 'qual', true, self::TNS]; + + yield 'attributeFormDefault=qualified local attribute' => ['QualifiedByDefault', 'local', true, self::QUALIFIED]; + yield 'attributeFormDefault=qualified local form=unqualified attribute' => ['QualifiedByDefault', 'unq', false, self::QUALIFIED]; + + yield 'ref inside attributeGroup' => ['Grouped', 'glob', true, self::TNS]; + yield 'xml:lang inside attributeGroup' => ['Grouped', 'lang', true, self::XML]; + yield 'local inside attributeGroup' => ['Grouped', 'grouped', false, self::TNS]; + yield 'ref inside nested attributeGroup' => ['Grouped', 'flag', true, self::OTHER]; + + yield 'ref inherited from complexContent extension base' => ['Extended', 'glob', true, self::TNS]; + yield 'ref declared on complexContent extension' => ['Extended', 'lang', true, self::XML]; + yield 'local declared on complexContent extension' => ['Extended', 'extra', false, self::TNS]; + + yield 'ref declared on complexContent restriction' => ['Restricted', 'glob', true, self::TNS]; + + yield 'ref declared on simpleContent extension' => ['Text', 'lang', true, self::XML]; + yield 'local declared on simpleContent extension' => ['Text', 'plain', false, self::TNS]; + } + + private static function findProperty(string $typeName, string $propertyName): Property + { + $type = self::metadata()->getTypes()->fetchFirstByName($typeName); + foreach ($type->getProperties() as $property) { + if ($property->getName() === $propertyName) { + return $property; + } + } + + static::fail(sprintf('Property %s not found on type %s', $propertyName, $typeName)); + } + + private static function metadata(): Metadata + { + if (self::$metadata) { + return self::$metadata; + } + + $tns = self::TNS; + $other = self::OTHER; + $qualified = self::QUALIFIED; + $wsdl = << + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + EOXML; + + $info = (new Wsdl1Reader(new CallbackLoader(static fn (): string => $wsdl)))('some.wsdl'); + + return self::$metadata = (new Wsdl1MetadataProvider($info))->getMetadata(); + } +} diff --git a/tests/Unit/Parser/Definitions/NamespacesParserTest.php b/tests/Unit/Parser/Definitions/NamespacesParserTest.php new file mode 100644 index 0000000..c93e3e3 --- /dev/null +++ b/tests/Unit/Parser/Definitions/NamespacesParserTest.php @@ -0,0 +1,34 @@ +' + )); + + static::assertSame(self::XML, $namespaces->lookupNamespaceFromName('xml')->unwrap()); + static::assertSame('xml', $namespaces->lookupNameFromNamespace(self::XML)->unwrap()); + static::assertSame('https://test', $namespaces->lookupNamespaceFromName('tns')->unwrap()); + } + + public function test_it_keeps_an_explicit_xml_declaration(): void + { + $namespaces = NamespacesParser::tryParse(Document::fromXmlString( + '' + )); + + static::assertSame(self::XML, $namespaces->lookupNamespaceFromName('xml')->unwrap()); + static::assertSame('xml', $namespaces->lookupNameFromNamespace(self::XML)->unwrap()); + } +}