- Overview
- Core Concepts
- W3C XML Schema Compliance
- Three Ways to Qualify Elements
- Common Patterns
- Nested Models
- Qualification Precedence
- Type Namespaces
- Troubleshooting
- Best Practices
- Architecture Notes
- See Also
- References
Overview
This document explains the critical distinction between namespace qualification (semantic) and namespace format (syntactic) in Lutaml::Model, and how the prefix: option interacts with namespace qualification rules.
Core Concepts
Namespace Qualification (Semantic)
Question: Is an element IN a namespace?
Controlled by:
-
element_form_defaultin XmlNamespace class (schema-level default) -
form:option inmap_element(mapping-level override) -
namespace:option with explicit namespace class or:inherit
Example:
class MyNamespace < Lutaml::Model::XmlNamespace
uri "http://example.com/ns"
prefix_default "ex"
element_form_default :qualified # Children inherit namespace
endNamespace Format (Syntactic)
Question: HOW to render a namespace that IS used?
Controlled by:
-
prefix: true/falseinto_xml()method -
Default: uses default namespace (
xmlns="…") -
With prefix: uses prefixed namespace (
xmlns:ex="…")
Example:
instance.to_xml(prefix: true) # Uses xmlns:ex="..." format
instance.to_xml(prefix: false) # Uses xmlns="..." format
instance.to_xml(prefix: "custom") # Uses xmlns:custom="..." formatW3C XML Schema Compliance
This section documents Lutaml::Model’s compliance with W3C XML Schema namespace qualification patterns. The implementation follows the W3C XML Schema Databinding Patterns specification.
W3C Patterns for Element Qualification
Lutaml::Model implements the following W3C-recognized patterns for element form qualification:
| W3C Pattern | XSD Declaration | Instance XML | element_form_default |
|---|---|---|---|
UnqualifiedLocalElements |
| Local elements without prefix |
|
ElementFormQualified |
| Element with namespace prefix |
|
ElementFormUnqualified |
| Element without namespace prefix | N/A (per-element override) |
UnqualifiedLocalElements Pattern (W3C)
When the XSD declares elementFormDefault="unqualified", local elements appear without namespace prefix in instance documents.
<xs:schema targetNamespace="http://example.com/ns"
elementFormDefault="unqualified">
<xs:element name="unqualifiedLocalElements" type="xs:string" />
</xs:schema><unqualifiedLocalElements>some data</unqualifiedLocalElements>ElementFormQualified Pattern (W3C)
When local elements have form="qualified", they must include the namespace prefix in instance documents.
<xs:element name="elementFormQualified" type="ex:ElementFormQualified" />
<xs:complexType name="ElementFormQualified">
<xs:sequence>
<xs:element name="premium" type="xs:string" form="qualified" />
</xs:sequence>
</xs:complexType><ex:elementFormQualified>
<ex:premium>1175</ex:premium>
</ex:elementFormQualified>ElementFormUnqualified Pattern (W3C)
When local elements have form="unqualified", they appear without namespace prefix even when elementFormDefault="qualified".
<xs:element name="elementFormUnqualified" type="ex:ElementFormUnqualified" />
<xs:complexType name="ElementFormUnqualified">
<xs:sequence>
<xs:element name="element" type="xs:string" form="unqualified" />
</xs:sequence>
</xs:complexType><ex:elementFormUnqualified>
<element>string</element>
</ex:elementFormUnqualified>W3C Patterns for Attribute Qualification
Lutaml::Model implements the following W3C-recognized patterns for attribute form qualification:
| W3C Pattern | XSD Declaration | Instance XML | attribute_form_default |
|---|---|---|---|
QualifiedLocalAttributes |
| Attributes with namespace prefix |
|
AttributeFormQualified |
| Attribute with namespace prefix | N/A (per-attribute override) |
QualifiedLocalAttributes Pattern (W3C)
When the XSD declares attributeFormDefault="qualified", local attributes must include the namespace prefix in instance documents.
<xs:schema targetNamespace="http://example.com/ns"
attributeFormDefault="qualified">
<xs:element name="qualifiedLocalAttributes" type="xs:string" />
</xs:schema><ex:qualifiedLocalAttributes>string</ex:qualifiedLocalAttributes>AttributeFormQualified Pattern (W3C)
When local attributes have form="qualified", they must include the namespace prefix.
<xs:element name="attributeFormQualified" type="ex:AttributeFormQualified" />
<xs:complexType name="AttributeFormQualified">
<xs:sequence>
<xs:element name="element1" type="xs:string" />
<xs:element name="element2" type="xs:string" />
</xs:sequence>
<xs:attribute name="attribute" form="qualified" type="xs:string" />
</xs:complexType><ex:attributeFormQualified ex:attribute="string">
<ex:element1>string</ex:element1>
<ex:element2>string</ex:element2>
</ex:attributeFormQualified>elementFormDefault Rules (Lutaml::Model)
According to W3C XML Schema specification:
| Setting | Meaning | Example | Behavior |
|---|---|---|---|
| Child elements inherit parent’s namespace |
| Children get namespace prefix when |
| Child elements are in NO namespace |
| Children get |
Not set | Defaults to | Same as | Context-dependent behavior (may inherit parent) |
| Explicit Setting Required for Predictable Behavior For predictable serialization behavior, always explicitly set When
When
Always match your XSD: |
Prefix Control Behavior
The prefix: option only affects elements that ARE qualified. |
Rule
IF element is qualified (has namespace)
THEN use `prefix:` option to determine format
ELSE
element remains unprefixed (no namespace)Examples
class MyNamespace < Lutaml::Model::XmlNamespace
uri "http://example.com/ns"
prefix_default "ex"
# element_form_default NOT set → defaults to :unqualified
end
class Parent < Lutaml::Model::Serializable
attribute :value, :string
xml do
namespace MyNamespace
element "parent"
map_element "child", to: :value
end
end
Parent.new(value: "test").to_xml(prefix: true)Output:
<ex:parent xmlns:ex="http://example.com/ns">
<child>test</child>
</ex:parent>Explanation: Child element is unqualified (no namespace), so it doesn’t get prefixed even though parent uses prefix: true.
class MyNamespace < Lutaml::Model::XmlNamespace
uri "http://example.com/ns"
prefix_default "ex"
element_form_default :qualified # Children inherit namespace
end
class Parent < Lutaml::Model::Serializable
attribute :value, :string
xml do
namespace MyNamespace
element "parent"
map_element "child", to: :value
end
end
Parent.new(value: "test").to_xml(prefix: true)Output:
<ex:parent xmlns:ex="http://example.com/ns">
<ex:child>test</ex:child>
</ex:parent>Explanation: Child element is qualified (inherits namespace), so it uses parent’s prefix format.
Three Ways to Qualify Elements
1. Schema-Level: element_form_default
Best for: All children should be qualified
class MyNamespace < Lutaml::Model::XmlNamespace
uri "http://example.com/ns"
prefix_default "ex"
element_form_default :qualified
end
class Model < Lutaml::Model::Serializable
xml do
namespace MyNamespace
# All child elements automatically qualified
end
endCommon Patterns
Pattern 1: Uniform Prefix for All Elements
# Define namespace with element_form_default
MyNS = Class.new(Lutaml::Model::XmlNamespace) do
uri "http://example.com"
prefix_default "ex"
element_form_default :qualified
end
# All elements will use prefix consistently
instance.to_xml(prefix: true)
# <ex:root xmlns:ex="..."><ex:child>...</ex:child></ex:root>Pattern 2: Mixed Qualified/Unqualified
# No element_form_default (defaults to unqualified)
MyNS = Class.new(Lutaml::Model::XmlNamespace) do
uri "http://example.com"
prefix_default "ex"
end
class Model < Lutaml::Model::Serializable
xml do
namespace MyNS
map_element "qualified", to: :val1, form: :qualified
map_element "unqualified", to: :val2 # No form specified
end
end
instance.to_xml(prefix: true)
# <ex:root xmlns:ex="...">
# <ex:qualified>...</ex:qualified>
# <unqualified>...</unqualified>
# </ex:root>Nested Models
When models are nested, namespace qualification rules apply recursively:
NS = Class.new(Lutaml::Model::XmlNamespace) do
uri "http://example.com"
prefix_default "ex"
element_form_default :qualified
end
class Child < Lutaml::Model::Serializable
attribute :value, :string
xml do
namespace NS
element "child"
map_element "value", to: :value
end
end
class Parent < Lutaml::Model::Serializable
attribute :child, Child
xml do
namespace NS
element "parent"
map_element "child", to: :child
end
end
Parent.new(child: Child.new(value: "test")).to_xml(prefix: true)Output:
<ex:parent xmlns:ex="http://example.com">
<ex:child>
<ex:value>test</ex:value>
</ex:child>
</ex:parent>All three elements (parent, child, value) use the prefix because all are qualified via element_form_default: :qualified.
Form Override on Serializable Children
The form: option works on attributes of any type — including attributes whose value type is another Serializable model, not only simple types like :string. This matters when the parent’s namespace declares element_form_default :unqualified (the W3C default) and you need a specific nested model element to be qualified anyway.
NS = Class.new(Lutaml::Model::XmlNamespace) do
uri "https://example.com/ns"
prefix_default "ex"
element_form_default :unqualified # W3C default for local elements
end
class Child < Lutaml::Model::Serializable
attribute :label, :string
xml do
element "child"
namespace NS
map_element "label", to: :label
end
end
class Parent < Lutaml::Model::Serializable
attribute :child, Child
xml do
element "item"
namespace NS
map_element "child", to: :child, form: :qualified # Force prefix
end
end
Parent.new(child: Child.new(label: "x")).to_xmlOutput:
<item xmlns="https://example.com/ns" xmlns:ex="https://example.com/ns">
<ex:child>
<label>x</label>
</ex:child>
</item>The ex: prefix on <child> is forced by form: :qualified, overriding the parent’s :unqualified schema default. The inner <label> remains unprefixed because the Child model’s own mapping rule for label has no form: override — see the next section.
Form Scope: Per-Rule, Not Transitive
form: is a per-mapping-rule override. It does not propagate transitively to grandchildren. Each level of the model tree applies its own rule against its own parent’s element_form_default.
NS = Class.new(Lutaml::Model::XmlNamespace) do
uri "https://example.com/ns"
prefix_default "ex"
element_form_default :unqualified
end
class Grandchild < Lutaml::Model::Serializable
attribute :value, :string
xml do
element "grandchild"
namespace NS
map_element "value", to: :value
end
end
class Child < Lutaml::Model::Serializable
attribute :inner, Grandchild
xml do
element "child"
namespace NS
map_element "grandchild", to: :inner # NO form: override
end
end
class Parent < Lutaml::Model::Serializable
attribute :child, Child
xml do
element "item"
namespace NS
map_element "child", to: :child, form: :qualified # Parent's rule only
end
end
Parent.new(child: Child.new(inner: Grandchild.new(value: "x"))).to_xmlOutput:
<item xmlns="https://example.com/ns" xmlns:ex="https://example.com/ns">
<ex:child>
<grandchild>
<value>x</value>
</grandchild>
</ex:child>
</item>-
<ex:child>is qualified because the Parent rule hasform: :qualified. -
<grandchild>is unprefixed because the Child rule has noform:and the Child’s namespace is:unqualified. -
The Parent’s
form: :qualifieddoes not cascade.
To qualify every level, either set form: :qualified on each mapping rule that needs it, or set element_form_default :qualified on the namespace so inheritance handles it.
Qualification Precedence
When serializing an element, the namespace is resolved by checking the following sources in priority order. The first match wins.
| Priority | Source |
|---|---|
1 (highest) | Type-level namespace: the attribute’s value type (a |
2 | Rule-level namespace: the mapping rule has an explicit |
3 |
|
4 | Parent’s |
5 |
|
6 (lowest) | No namespace. |
This ladder is implemented in Lutaml::Xml::Transformation::ElementBuilder#determine_element_namespace. The ElementFormOptionRule decision rule reads the propagated form value during the planning phase and forces prefix (:qualified) or default (:unqualified) format accordingly.
Type Namespaces
Value types can define their own namespaces:
class CustomType < Lutaml::Model::Type::String
xml_namespace MyNamespace
end
Lutaml::Model::Type.register(:custom, CustomType)
class Model < Lutaml::Model::Serializable
attribute :value, :custom # Uses CustomType's namespace
xml do
namespace ParentNamespace
map_element "value", to: :value
end
endIf type’s namespace matches parent’s namespace and parent uses prefix, the element will use that prefix.
Troubleshooting
Element Not Getting Prefix
Problem: to_xml(prefix: true) but child elements don’t have prefix
Solution: Add qualification to child elements:
# Option 1: Schema-level
class MyNamespace < Lutaml::Model::XmlNamespace
element_form_default :qualified # ADD THIS
end
# Option 2: Mapping-level
map_element "child", to: :value, form: :qualifiedUnexpected Namespace on Element
Problem: Element has namespace when it shouldn’t
Check:
-
Is
element_form_defaultexplicitly set to:unqualified? -
Is
form: :qualifiedon the mapping? -
Does the Type have a namespace?
Solution: Explicitly mark as unqualified:
class MyNamespace < Lutaml::Model::XmlNamespace
element_form_default :unqualified # EXPLICITLY SET THIS
end
# Or at mapping level
map_element "value", to: :value, form: :unqualifiedelement_form_default Not Honored
Problem: Set element_form_default :unqualified but children still get namespace prefix
Cause: The value may not be explicitly set on the namespace class
Solution: Ensure element_form_default is explicitly called in your namespace class:
# CORRECT: Explicitly set
class MyNamespace < Lutaml::Model::XmlNamespace
element_form_default :unqualified # Explicitly set
end
# INCORRECT: Relies on default (may not work predictably)
class MyNamespace < Lutaml::Model::XmlNamespace
# element_form_default NOT called
# May not produce expected unqualified behavior
endThe distinction between "explicitly set" and "default value" matters because:
-
When explicitly set, the value is properly propagated to child elements
-
When not set, context-dependent behavior may occur
-
This ensures W3C XML Schema compliance for
elementFormDefault="unqualified"
Best Practices
1. Always Explicitly Set element_form_default
# GOOD: Clear intent, matches XSD
class MyNamespace < Lutaml::Model::XmlNamespace
element_form_default :qualified # Explicitly set
end
# GOOD: Explicitly set to unqualified
class MyNamespace < Lutaml::Model::XmlNamespace
element_form_default :unqualified # Explicitly set
end
# AVOID: Relying on default
class MyNamespace < Lutaml::Model::XmlNamespace
# Defaults to :unqualified, but intent unclear
# May not behave predictably in all contexts
endWhen you explicitly set element_form_default, it is properly honored during serialization, ensuring child elements follow the expected namespace qualification rules.
2. Document Namespace Decisions
class MyNamespace < Lutaml::Model::XmlNamespace
uri "http://example.com/v1"
prefix_default "v1"
# All child elements should be qualified to match external XML schema
element_form_default :qualified
end3. Test Both Formats
it "works with default namespace" do
xml = instance.to_xml(prefix: false)
expect(xml).to include('xmlns="http://example.com"')
end
it "works with prefix" do
xml = instance.to_xml(prefix: true)
expect(xml).to include('xmlns:ex="http://example.com"')
expect(xml).to include('<ex:element>')
end4. Match External Schemas
When implementing an external schema (XSD), match its elementFormDefault:
<!-- External schema.xsd -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">
<!-- ... -->
</xs:schema># Your namespace should match
class MyNamespace < Lutaml::Model::XmlNamespace
element_form_default :qualified # Matches schema
endArchitecture Notes
Three-Phase Namespace Architecture
The implementation follows a three-phase architecture:
-
Collection Phase (NamespaceCollector): Discovers all namespaces needed in the document tree
-
Planning Phase (DeclarationPlanner): Decides where to declare each namespace and in what format
-
Rendering Phase (Adapters): Applies the plan to generate XML
The prefix: option affects the Planning Phase by creating a custom namespace class override with the specified prefix, which then propagates through the entire tree.