Overview

Version 0.8.0 fixes a bug where the attribute_form_default :qualified setting in XmlNamespace classes was not being respected during XML serialization. This fix ensures W3C XML Schema compliance and proper OOXML document generation.

What Was Broken

Prior to v0.8.0, when a namespace declared attribute_form_default :qualified, attributes were incorrectly serialized without namespace prefixes, violating W3C XML Schema specifications.

Example of the Bug

class MyNamespace < Lutaml::Model::XmlNamespace
  uri "http://example.com/ns"
  prefix_default "ex"
  attribute_form_default :qualified  # This was IGNORED
end

class Item < Lutaml::Model::Serializable
  attribute :id, :string
  attribute :value, :integer

  xml do
    root "item"
    namespace MyNamespace
    map_attribute "id", to: :id
    map_attribute "value", to: :value
  end
end

item = Item.new(id: "123", value: 42)
item.to_xml(prefix: true)

Buggy Output (v0.7.x and earlier):

<ex:item xmlns:ex="http://example.com/ns" id="123" value="42"/>
<!-- WRONG: Attributes should have ex: prefix -->

Correct Output (v0.8.0+):

<ex:item xmlns:ex="http://example.com/ns" ex:id="123" ex:value="42"/>
<!-- CORRECT: Attributes now have ex: prefix -->

What Changed

Implementation Changes

  1. MappingRule.resolve_attribute_namespace now checks attribute_form_default setting from parent namespace class

  2. Document.resolve_attribute_namespace now passes parent namespace class information to the resolution logic

  3. All XML adapters (Nokogiri, Ox, Oga) updated to provide mapper_class context for attribute namespace resolution

  4. Deserialization updated to match both prefixed and unprefixed attribute forms for backward compatibility

Behavior Changes

Setting v0.7.x Behavior v0.8.0+ Behavior

attribute_form_default :unqualified (default)

Attributes without prefix ✓

Attributes without prefix ✓ (no change)

attribute_form_default :qualified

Attributes without prefix ⚠️ (BUG)

Attributes WITH prefix ✓ (FIXED)

Explicit namespace: in map_attribute

Uses explicit namespace ✓

Uses explicit namespace ✓ (no change)

Type-level xml_namespace

Uses type namespace ✓

Uses type namespace ✓ (no change)

Who Is Affected

This fix affects you if:

  1. You use attribute_form_default :qualified

    • OOXML users (Word/Excel/PowerPoint document generation)

    • Custom schemas requiring qualified attributes

    • W3C-compliant XML generation

  2. You work with Office Open XML (OOXML)

    • ISO 29500 compliance requires qualified attributes

    • WordProcessingML, SpreadsheetML, PresentationML namespaces

  3. You interchange XML documents with schema-aware validators

    • Documents will now validate correctly against XSD schemas

Migration Guide

For Most Users

No action required. If you were using attribute_form_default :qualified and experiencing issues, the fix will automatically correct your XML output.

Testing Your Changes

Run your existing tests. The fix should make documents more compliant, not less:

RSpec.describe MyModel do
  it "generates W3C-compliant XML" do
    model = MyModel.new(id: "123", value: 42)
    xml = model.to_xml(prefix: true)

    # After fix, attributes will have namespace prefix
    expect(xml).to include('ex:id="123"')
    expect(xml).to include('ex:value="42"')
  end
end

For OOXML Users

If you were working around the bug with custom serialization, you can now remove those workarounds:

# BEFORE (workaround for bug):
class Spacing < Lutaml::Model::Serializable
  xml do
    root "spacing"
    namespace WordProcessingML
    # Had to explicitly set namespace on each attribute
    map_attribute "val", to: :val, namespace: WordProcessingML
    map_attribute "after", to: :after, namespace: WordProcessingML
  end
end

# AFTER (clean, uses attribute_form_default):
class Spacing < Lutaml::Model::Serializable
  xml do
    root "spacing"
    namespace WordProcessingML  # attribute_form_default :qualified applies
    map_attribute "val", to: :val
    map_attribute "after", to: :after
  end
end

Backward Compatibility

Deserialization

The parser now accepts both prefixed and unprefixed attribute forms for maximum compatibility:

<!-- Both of these will parse correctly: -->
<ex:item xmlns:ex="http://example.com/ns" ex:id="123" ex:value="42"/>
<ex:item xmlns:ex="http://example.com/ns" id="123" value="42"/>

This ensures old documents (with unprefixed attributes) continue to parse correctly.

Serialization

Documents generated with v0.8.0+ will be more compliant but may differ from older versions:

  • Schema validation: New documents validate against XSD schemas

  • OOXML compliance: New documents are ISO 29500 compliant

  • Interoperability: Improved compatibility with schema-aware parsers

If You Need Old Behavior

If you need to maintain the old (non-compliant) behavior temporarily:

class MyNamespace < Lutaml::Model::XmlNamespace
  uri "http://example.com/ns"
  prefix_default "ex"
  attribute_form_default :unqualified  # Explicitly set to unqualified
end

However, this is not recommended as it produces non-W3C-compliant XML.

Examples

Simple Case

class ExampleNamespace < Lutaml::Model::XmlNamespace
  uri "http://example.com/ns"
  prefix_default "ex"
  attribute_form_default :qualified
end

class Product < Lutaml::Model::Serializable
  attribute :sku, :string
  attribute :price, :float

  xml do
    root "product"
    namespace ExampleNamespace
    map_attribute "sku", to: :sku
    map_attribute "price", to: :price
  end
end

product = Product.new(sku: "ABC123", price: 29.99)

Output:

<ex:product xmlns:ex="http://example.com/ns" ex:sku="ABC123" ex:price="29.99"/>

OOXML WordProcessingML

class WordProcessingML < Lutaml::Model::XmlNamespace
  uri 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'
  prefix_default 'w'
  element_form_default :qualified
  attribute_form_default :qualified
end

class Paragraph < Lutaml::Model::Serializable
  attribute :style_id, :string

  xml do
    root "p"
    namespace WordProcessingML
    map_attribute "styleId", to: :style_id
  end
end

para = Paragraph.new(style_id: "Heading1")

Output:

<w:p xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
     w:styleId="Heading1"/>

Technical Details

Priority of Attribute Namespace Resolution

Attributes now follow this resolution priority:

  1. Explicit namespace in mapping (namespace: parameter)

  2. Type-level namespace (xml_namespace in Type::Value)

  3. Schema-level attribute_form_default :qualified (NEW in v0.8.0)

  4. No namespace (W3C default for unqualified attributes)

W3C XML Schema Compliance

This fix implements W3C XML Schema Part 1: Structures, Section 3.2.2:

If the attribute form default is 'qualified', then locally declared attributes (those declared in complexType definitions) must be qualified in instance documents.

— W3C XML Schema Part 1: Structures

Support

If you encounter issues after upgrading:

  1. Check your attribute_form_default settings

  2. Verify your XML output against your XSD schema

  3. Review test failures for expected vs actual XML format

  4. Consult the documentation at docs/xml/namespace-semantics.adoc

For bugs or questions, please file an issue on GitHub.