diff --git a/src/Metadata/Converter/Types/Configurator/AttributeSingleConfigurator.php b/src/Metadata/Converter/Types/Configurator/AttributeSingleConfigurator.php index cbd3472..c3b7a12 100644 --- a/src/Metadata/Converter/Types/Configurator/AttributeSingleConfigurator.php +++ b/src/Metadata/Converter/Types/Configurator/AttributeSingleConfigurator.php @@ -18,9 +18,12 @@ 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(); + // A ref points to a global attribute declaration, which lives in the target namespace of the schema + // that declares it, or in no namespace (unqualified) when that schema has no targetNamespace. + // The form and attributeFormDefault rules only apply to local declarations. + $isQualified = $xsdType instanceof AttributeRef + ? '' !== (string) $xsdType->getReferencedAttribute()->getSchema()->getTargetNamespace() + : $xsdType->isQualified(); $configure = pipe( static fn (EngineType $engineType): EngineType => (new RestrictionsConfigurator())($engineType, $xsdType->getType()?->getRestriction(), $context), diff --git a/tests/Unit/Metadata/Coverter/Types/Configurator/AttributeQualificationTest.php b/tests/Unit/Metadata/Coverter/Types/Configurator/AttributeQualificationTest.php index 11b6893..18363b5 100644 --- a/tests/Unit/Metadata/Coverter/Types/Configurator/AttributeQualificationTest.php +++ b/tests/Unit/Metadata/Coverter/Types/Configurator/AttributeQualificationTest.php @@ -61,6 +61,49 @@ public static function provideAttributes(): iterable yield 'local declared on simpleContent extension' => ['Text', 'plain', false, self::TNS]; } + public function test_it_does_not_qualify_a_ref_to_an_attribute_without_namespace(): void + { + // No default namespace is in scope, so ref="bare" resolves to no namespace. Resetting it with xmlns="" + // does not work: xsd-reader resolves that to '' and misses the schema's null target namespace. + $tns = self::TNS; + $wsdl = << + + + + + + + + + + + + + + + + + + + + EOXML; + + $info = (new Wsdl1Reader(new CallbackLoader(static fn (): string => $wsdl)))('some.wsdl'); + $type = (new Wsdl1MetadataProvider($info))->getMetadata()->getTypes()->fetchFirstByName('NoNamespace'); + $properties = [...$type->getProperties()]; + + static::assertCount(1, $properties); + static::assertSame('bare', $properties[0]->getName()); + $meta = $properties[0]->getType()->getMeta(); + static::assertTrue($meta->isAttribute()->unwrapOr(false)); + static::assertFalse($meta->isQualified()->unwrapOr(false)); + } + private static function findProperty(string $typeName, string $propertyName): Property { $type = self::metadata()->getTypes()->fetchFirstByName($typeName);