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
-
MappingRule.resolve_attribute_namespacenow checksattribute_form_defaultsetting from parent namespace class -
Document.resolve_attribute_namespacenow passes parent namespace class information to the resolution logic -
All XML adapters (Nokogiri, Ox, Oga) updated to provide mapper_class context for attribute namespace resolution
-
Deserialization updated to match both prefixed and unprefixed attribute forms for backward compatibility
Behavior Changes
| Setting | v0.7.x Behavior | v0.8.0+ Behavior |
|---|---|---|
| Attributes without prefix ✓ | Attributes without prefix ✓ (no change) |
| Attributes without prefix ⚠️ (BUG) | Attributes WITH prefix ✓ (FIXED) |
Explicit | Uses explicit namespace ✓ | Uses explicit namespace ✓ (no change) |
Type-level | Uses type namespace ✓ | Uses type namespace ✓ (no change) |
Who Is Affected
This fix affects you if:
-
You use
attribute_form_default :qualified-
OOXML users (Word/Excel/PowerPoint document generation)
-
Custom schemas requiring qualified attributes
-
W3C-compliant XML generation
-
-
You work with Office Open XML (OOXML)
-
ISO 29500 compliance requires qualified attributes
-
WordProcessingML, SpreadsheetML, PresentationML namespaces
-
-
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
endFor 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
endBackward 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.
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
endHowever, 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:
-
Explicit namespace in mapping (
namespace:parameter) -
Type-level namespace (
xml_namespacein Type::Value) -
Schema-level
attribute_form_default :qualified(NEW in v0.8.0) -
No namespace (W3C default for unqualified attributes)
Support
If you encounter issues after upgrading:
-
Check your
attribute_form_defaultsettings -
Verify your XML output against your XSD schema
-
Review test failures for expected vs actual XML format
-
Consult the documentation at
docs/xml/namespace-semantics.adoc
For bugs or questions, please file an issue on GitHub.