Skip to content

YamlConverter::getElementNamespace ignores form="unqualified" when schema has elementFormDefault="qualified" #196

Description

@XenosEleatikos

Bug description

YamlConverter::getElementNamespace assigns a namespace to a local element even when that element explicitly declares form="unqualified" in the XSD, producing invalid JMS Serializer metadata.

Root cause

protected function getElementNamespace(Schema $schema, ElementItem $element)
{
    if ($element->getSchema()->getTargetNamespace() &&
        ($schema->getElementsQualification()   // ← bug
            || ($element instanceof Element && $element->isQualified())
            || !$element->isLocal())
    ) {
        return $element->getSchema()->getTargetNamespace();
    }
    return null;
}

When the containing schema has elementFormDefault="qualified", $schema->getElementsQualification() returns true and short-circuits the entire OR. An element that explicitly declares form="unqualified" still receives a namespace entry in the generated YAML — the xsd-reader's correct isQualified()=false result is silently ignored.

Reproducer

<!-- schema: elementFormDefault="qualified" -->
<xs:complexType name="CodeType">
  <xs:sequence>
    <xs:element name="code" type="xs:token"            form="unqualified"/>
    <xs:element name="name" type="xs:normalizedString" form="unqualified" minOccurs="0"/>
  </xs:sequence>
</xs:complexType>

Generated YAML (wrong):

code:
    xml_element:
        namespace: 'urn:example:schema'   # must not be here
name:
    xml_element:
        namespace: 'urn:example:schema'   # must not be here

JMS Serializer then produces <ns:code> / <ns:name> instead of <code> / <name>, which schema validators reject with:

Invalid content was found starting with element '{urn:example:schema}name'. One of '{name}' is expected.

Expected behaviour

Elements with an explicit form="unqualified" must not receive a namespace entry, regardless of the parent schema's elementFormDefault.

Suggested fix

Remove $schema->getElementsQualification() from the condition. $element->isQualified() already incorporates elementFormDefault as a fallback when no explicit form attribute is present, making the removed check both redundant and incorrect.

-    protected function getElementNamespace(Schema $schema, ElementItem $element)
+    protected function getElementNamespace(Schema $_schema, ElementItem $element)
     {
         if ($element->getSchema()->getTargetNamespace() &&
-            ($schema->getElementsQualification() || ($element instanceof Element && $element->isQualified()) || !$element->isLocal())
+            (($element instanceof Element && $element->isQualified()) || !$element->isLocal())
         ) {

A PR with this fix is already open: #195

Behaviour matrix after fix

Schema elementFormDefault Element form Before After
qualified (none) namespace set ✓ namespace set ✓
qualified form="unqualified" namespace set ✗ no namespace ✓
qualified form="qualified" namespace set ✓ namespace set ✓
unqualified (none) no namespace ✓ no namespace ✓
unqualified form="qualified" namespace set ✓ namespace set ✓

Context

Discovered while generating JMS metadata for the German XÖV/XÖV-Standard xoev-code.xsd, where code and name child elements are intentionally form="unqualified" for cross-schema interoperability — a common pattern in German e-government standards.

Version

xsd2php 0.4.13

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions