- Overview
- Core Principles
- Namespace Resolution Algorithm
- Qualified vs Unqualified Forms
- Namespace Inheritance
- Common Patterns
- W3C Compliance
- Best Practices
- See Also
Overview
Lutaml::Model implements W3C XML Namespace specification with a focus on attribute-based namespace resolution. Each XML element’s namespace is determined by its attribute’s type and parent context, following clear and consistent rules.
This document explains the core namespace principles that govern how namespaces are applied during XML serialization and deserialization.
Core Principles
Principle 1: All Attributes Belong to Their Own Namespaces
Each model attribute’s namespace is determined by examining the attribute’s value type and the parent model’s namespace configuration. The namespace assignment follows a clear hierarchy of rules.
Native Type Elements
When an attribute has a native type (:string, :integer, :float, etc.), these types have no inherent namespace. The namespace they use depends on the parent model’s configuration.
class FirstItemNamespace < Lutaml::Model::XmlNamespace
prefix_default "first"
uri "http://example.com/first"
end
class NativeItem < Lutaml::Model::Serializable
attribute :name, :string # Native type with no inherent namespace
xml do
root "first_item"
namespace FirstItemNamespace
map_element "name", to: :name
end
endResult: The name element inherits the FirstItemNamespace from its parent model.
Default namespace format:
<first_item xmlns="http://example.com/first">
<name>Item Name</name>
</first_item>Prefixed namespace format:
<first:first_item xmlns:first="http://example.com/first">
<first:name>Item Name</first:name>
</first:first_item>Type::Value with Namespace
When an attribute’s type is a Type::Value subclass with its own namespace declaration, that namespace takes precedence:
class FirstNamespace < Lutaml::Model::XmlNamespace
prefix_default "first"
uri "http://example.com/first"
end
class SecondNamespace < Lutaml::Model::XmlNamespace
prefix_default "second"
uri "http://example.com/second"
end
class FirstNamespacedName < Lutaml::Model::Type::String
xml_namespace FirstNamespace
end
class SecondNamespacedName < Lutaml::Model::Type::String
xml_namespace SecondNamespace
end
class NamespacedItem < Lutaml::Model::Serializable
attribute :name, FirstNamespacedName # Has FirstNamespace
attribute :alt_name, SecondNamespacedName # Has SecondNamespace
xml do
root "second_item"
namespace SecondNamespace
map_element "name", to: :name
map_element "alt_name", to: :alt_name
end
endResult: When multiple namespaces are present, the implementation uses prefixed format to avoid conflicts (cannot have two default namespaces):
<second_item xmlns="http://example.com/second" xmlns:first="http://example.com/first">
<first:name>Item Name</first:name>
<alt_name>Alt Item Name</alt_name>
</second_item>Model-Valued Attributes
When an attribute’s type is another Serializable model:
If the nested model has its own namespace, that namespace is used:
class NestedItem < Lutaml::Model::Serializable
attribute :value, :string
xml do
root "nested"
namespace FirstNamespace
map_element "value", to: :value
end
end
class Container < Lutaml::Model::Serializable
attribute :item, NestedItem
xml do
root "container"
namespace SecondNamespace
map_element "item", to: :item
end
endIf the nested model has NO namespace:
-
With qualified elements (default): Uses parent’s namespace
-
With unqualified elements: No namespace
XML Attributes vs Elements
Elements: Follow the namespace rules described above.
XML Attributes: Always unqualified by default (per W3C specification), unless:
-
The attribute’s value type (Model or Type::Value) has its own namespace
-
An explicit namespace is set on the mapping rule
-
The parent namespace has
attribute_form_default :qualified
xml do
root "item"
namespace MyNamespace
map_attribute "id", to: :id # Unqualified (no namespace)
map_element "name", to: :name # Qualified (uses MyNamespace)
endAttribute Qualification with attributeFormDefault
W3C XML Schema provides the attributeFormDefault directive to control whether locally declared attributes must be qualified (prefixed) in instance documents.
W3C Default Behavior (Unqualified)
By default, attributes are unqualified and do NOT inherit the parent element’s namespace:
class MyNamespace < Lutaml::Model::XmlNamespace
uri "http://example.com/ns"
prefix_default "ex"
element_form_default :qualified
attribute_form_default :unqualified # W3C default
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
endOutput (attributes WITHOUT namespace prefix):
<ex:item xmlns:ex="http://example.com/ns" id="123" value="42"/>Qualified Attributes (attributeFormDefault :qualified)
When attribute_form_default :qualified is set, attributes MUST be qualified with the namespace prefix:
class MyNamespace < Lutaml::Model::XmlNamespace
uri "http://example.com/ns"
prefix_default "ex"
element_form_default :qualified
attribute_form_default :qualified # Attributes must be prefixed
endOutput (attributes WITH namespace prefix):
<ex:item xmlns:ex="http://example.com/ns" ex:id="123" ex:value="42"/>OOXML (Office Open XML) Use Case
OOXML specifications (ISO 29500) require qualified attributes for WordProcessingML, SpreadsheetML, and PresentationML namespaces:
class WordProcessingML < Lutaml::Model::XmlNamespace
uri 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'
prefix_default 'w'
element_form_default :qualified
attribute_form_default :qualified # Required by OOXML spec
end
class Spacing < Lutaml::Model::Serializable
attribute :val, :integer
attribute :after, :integer
attribute :before, :integer
xml do
root "spacing"
namespace WordProcessingML
map_attribute "val", to: :val
map_attribute "after", to: :after
map_attribute "before", to: :before
end
end
spacing = Spacing.new(val: 20, after: 100, before: 0)
spacing.to_xml(prefix: true)OOXML-compliant output:
<w:spacing xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
w:val="20" w:after="100" w:before="0"/>Attribute Namespace Resolution Priority
For XML attributes, namespace is resolved with the following priority:
-
Explicit namespace in mapping (highest)
-
map_attribute "id", to: :id, namespace: SpecificNamespace
-
-
Type-level namespace
-
If attribute’s type (Type::Value or Model) declares
xml_namespace
-
-
Schema-level attributeFormDefault :qualified
-
Attributes inherit parent element’s namespace
-
-
No namespace (W3C default)
-
Unprefixed attributes have no namespace
-
class Namespace1 < Lutaml::Model::XmlNamespace
uri "http://example.com/ns1"
prefix_default "ns1"
attribute_form_default :unqualified
end
class Namespace2 < Lutaml::Model::XmlNamespace
uri "http://example.com/ns2"
prefix_default "ns2"
end
class Item < Lutaml::Model::Serializable
attribute :normal, :string
attribute :explicit, :string
xml do
root "item"
namespace Namespace1
map_attribute "normal", to: :normal # Uses form_default (unqualified)
map_attribute "explicit", to: :explicit, namespace: Namespace2 # Explicit wins
end
endOutput:
<ns1:item xmlns:ns1="http://example.com/ns1" xmlns:ns2="http://example.com/ns2"
normal="value1" ns2:explicit="value2"/>Principle 2: Prefix vs Default is ONLY Presentation
These two XML documents are semantically identical - they have the same XML Infoset:
<!-- Default namespace -->
<first_item xmlns="http://example.com/first">
<name>Item Name</name>
</first_item>
<!-- Prefixed namespace -->
<first:first_item xmlns:first="http://example.com/first">
<first:name>Item Name</first:name>
</first:first_item>Both parse to the same model structure:
item = NativeItem.new(name: "Item Name")
# Both produce semantically identical results
xml_default = item.to_xml
xml_prefixed = item.to_xml(prefix: true)
# Both parse to the same data
NativeItem.from_xml(xml_default) # => #<NativeItem name="Item Name">
NativeItem.from_xml(xml_prefixed) # => #<NativeItem name="Item Name">Key Insight: The choice between default and prefixed namespaces is purely a presentation detail. The underlying data structure is identical.
Principle 3: Collections Follow Same Rules
Collection items follow the exact same namespace rules as single items, based on their realized type.
Simple Collections
class NativeItemNames < Lutaml::Model::Serializable
attribute :name, :string, collection: true
xml do
root "item_names"
namespace FirstItemNamespace
map_element "name", to: :name
end
endEach collection item follows the same rules:
<item_names xmlns="http://example.com/first">
<name>Item Name 1</name>
<name>Item Name 2</name>
</item_names>Model Collections
class NativeItemCollection < Lutaml::Model::Serializable
attribute :items, NativeItem, collection: true
xml do
root "items"
namespace FirstItemNamespace
map_element "item", to: :items
end
endEach item in the collection follows namespace rules based on its type (NativeItem).
Polymorphic Collections
For polymorphic attributes (accepting multiple types), the namespace rules apply based on the actual realized type of each item:
class Container < Lutaml::Model::Serializable
attribute :items, BaseItem, collection: true, polymorphic: [TypeA, TypeB]
xml do
root "container"
map_element "item", to: :items
end
endEach item uses the namespace of its actual class (TypeA or TypeB), not the base class.
Namespace Resolution Algorithm
The namespace for any attribute’s serialization is determined by this algorithm:
1. Does the attribute have an explicit namespace in its mapping rule?
YES → Use that namespace
NO → Continue to step 2
2. Is the attribute's type a Type::Value with xml_namespace declared?
YES → Use Type's namespace
NO → Continue to step 3
3. Is the attribute's type a Model with its own namespace?
YES → Use Model's namespace
NO → Continue to step 4
4. Is this an XML attribute (not element)?
YES → No namespace (unqualified by default)
NO → Continue to step 5
5. Does the parent model have a namespace?
YES → Use parent's namespace (for qualified elements)
NO → No namespaceQualified vs Unqualified Forms
XML Schema defines two element/attribute forms:
Qualified Form (Default for Elements)
Elements inherit their parent’s namespace:
<parent xmlns="http://example.com/ns">
<child>value</child> <!-- Inherits parent's namespace -->
</parent>Unqualified Form (Default for Attributes)
Elements/attributes have no namespace:
<parent xmlns="http://example.com/ns" attr="value">
<!-- attr has no namespace -->
</parent>Configure via XmlNamespace:
class MyNamespace < Lutaml::Model::XmlNamespace
uri "http://example.com/ns"
element_form_default :qualified # or :unqualified
attribute_form_default :unqualified # or :qualified
endNamespace Inheritance
Child models inherit namespace context from parents during serialization:
class Child < Lutaml::Model::Serializable
attribute :value, :string
xml do
root "child"
# No namespace declared - will inherit from parent
map_element "value", to: :value
end
end
class Parent < Lutaml::Model::Serializable
attribute :child, Child
xml do
root "parent"
namespace ParentNamespace
map_element "child", to: :child
end
endResult:
<parent xmlns="http://parent.example.com">
<child>
<value>text</value> <!-- Inherits parent namespace -->
</child>
</parent>Common Patterns
Multiple Namespaces in One Document
class Wrapper < Lutaml::Model::Serializable
attribute :items, NamespacedItem, collection: true
xml do
root "wrapper"
namespace WrapperNamespace
map_element "item", to: :items
end
endResult (using prefixes to avoid conflicts):
<wrapper xmlns="http://wrapper.example.com"
xmlns:second="http://second.example.com"
xmlns:first="http://first.example.com">
<second:item>
<first:name>Name</first:name>
<second:alt_name>Alt Name</second:alt_name>
</second:item>
</wrapper>W3C Compliance
Lutaml::Model follows W3C XML Namespace Recommendation:
-
Namespace URIs: Identify namespaces uniquely
-
Prefix Binding: Associates prefixes with namespace URIs
-
Default Namespace: Elements without prefix use default namespace
-
Attribute Qualification: Attributes unqualified by default
-
QNames: Qualified names use prefix:localname format
Best Practices
-
Define namespaces as XmlNamespace classes: Encapsulates URI, prefix, and form defaults
-
Use Type::Value namespaces for reusable types: Email, phone numbers, etc.
-
Prefer default namespace for single-namespace documents: Cleaner, less verbose
-
Use prefixes for multi-namespace documents: Avoid conflicts, improve clarity
-
Test both presentations: Verify default and prefixed formats parse correctly
-
Document namespace decisions: Explain why specific namespaces are chosen