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
elementFormDefault Rules
According to W3C XML Schema specification:
| Setting | Meaning | Example |
|---|---|---|
| Child elements inherit parent’s namespace |
|
| Child elements are in NO namespace |
|
Not set | Defaults to | Same as |
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
root "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
root "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
root "child"
map_element "value", to: :value
end
end
class Parent < Lutaml::Model::Serializable
attribute :child, Child
xml do
namespace NS
root "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.
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: :qualifiedBest Practices
1. Be Explicit About Qualification
# GOOD: Clear intent
class MyNamespace < Lutaml::Model::XmlNamespace
element_form_default :qualified # or :unqualified
end
# AVOID: Relying on default
class MyNamespace < Lutaml::Model::XmlNamespace
# Defaults to :unqualified, but intent unclear
end2. 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.