- Introduction
- Understanding XML Namespaces
- The namespace_scope Directive
- Child Namespace Inheritance
- Type Namespaces with namespace_scope
- Multi-Namespace Documents
- W3C Compliance
- Format Preservation
- Best Practices
- Common Patterns
- Troubleshooting
- Adapter Compatibility
- Related Documentation
- Architecture Notes
Introduction
This guide provides comprehensive information about XML namespace management in Lutaml::Model, including the namespace_scope directive, W3C compliance, and best practices for multi-namespace documents.
Understanding XML Namespaces
XML namespaces provide a method to avoid element name conflicts by qualifying names used in XML documents through a URI reference.
The namespace_scope Directive
General
The namespace_scope directive controls WHERE namespace declarations appear in serialized XML. By default, Lutaml::Model follows the minimal-subtree principle: namespaces are declared locally (as close to their usage as possible).
With namespace_scope, you can consolidate (hoist) multiple namespace declarations at a parent element for cleaner, more compact XML.
namespace_scope grants ELIGIBILITY to hoist namespaces to the root, not automatic hoisting. Namespaces are only declared if actually used in the document (:auto mode) unless explicitly forced (:always mode). |
Syntax
xml do
namespace RootNamespace
# Simple list (all use :auto mode)
namespace_scope [Namespace1, Namespace2, Namespace3]
# Per-namespace control with hash format
namespace_scope [
{ namespace: Namespace1, declare: :always },
{ namespace: Namespace2, declare: :auto },
Namespace3 # Can mix hash and class
]
endDeclaration Modes
| Mode | Behavior |
|---|---|
| Namespace declared only if actually used in elements or attributes. Most common mode for clean, minimal XML. |
| Namespace always declared at root, even if unused. Required by some formats like Office Open XML that mandate specific namespace presence. |
| Namespace never declared. Error raised if namespace is used. Reserved for future extensibility. |
Child Namespace Inheritance
W3C Inheritance Semantics
Children in the same namespace as their parent automatically inherit the parent’s namespace without redeclaration. This follows W3C XML namespace specifications and prevents duplicate xmlns declarations.
Inheritance Rules:
-
Priority 0 - Inherit parent’s default namespace (highest priority)
-
Priority 0.5 - Use prefix hoisted on parent
-
Priority 1-5 - Standard namespace resolution
Basic Inheritance Example
class VcardNamespace < Lutaml::Model::XmlNamespace
uri "urn:ietf:params:xml:ns:vcard-4.0"
prefix_default "vcard"
end
class Vcard < Lutaml::Model::Serializable
attribute :version, :string
attribute :fn, :string
attribute :email, :string
xml do
root "vCard"
namespace VcardNamespace
map_element "version", to: :version
map_element "fn", to: :fn
map_element "email", to: :email
end
end
vcard = Vcard.new(
version: "4.0",
fn: "Dr. John Doe",
email: "john@example.com"
)
puts vcard.to_xmlOutput (default namespace format):
<vCard xmlns="urn:ietf:params:xml:ns:vcard-4.0">
<version>4.0</version>
<fn>Dr. John Doe</fn>
<email>john@example.com</email>
</vCard>| All child elements inherit parent’s default namespace—no xmlns redeclaration needed. |
Output with prefix: true:
puts vcard.to_xml(prefix: true)<vcard:vCard xmlns:vcard="urn:ietf:params:xml:ns:vcard-4.0">
<vcard:version>4.0</vcard:version>
<vcard:fn>Dr. John Doe</vcard:fn>
<vcard:email>john@example.com</vcard:email>
</vcard:vCard>| All elements in same namespace use consistent vcard: prefix. |
Type Namespaces with namespace_scope
Local Hoisting (Without namespace_scope)
When Type namespaces are used without namespace_scope, they are hoisted locally to the element where first used:
class DcNamespace < Lutaml::Model::XmlNamespace
uri "http://purl.org/dc/elements/1.1/"
prefix_default "dc"
end
class DcTitleType < Lutaml::Model::Type::String
xml_namespace DcNamespace
end
class Contact < Lutaml::Model::Serializable
attribute :title, DcTitleType
attribute :note, DcTitleType
xml do
root "contact"
map_element "title", to: :title
map_element "note", to: :note
end
end
class Vcard < Lutaml::Model::Serializable
attribute :contact, Contact
xml do
root "vCard"
namespace VcardNamespace
map_element "contact", to: :contact
end
end
vcard = Vcard.new(
contact: Contact.new(
title: "Dr. John Doe",
note: "Lead Researcher"
)
)
puts vcard.to_xmlOutput:
<vCard xmlns="urn:ietf:params:xml:ns:vcard-4.0">
<contact xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:title>Dr. John Doe</dc:title>
<dc:note>Lead Researcher</dc:note>
</contact>
</vCard>| DcNamespace declared once on <contact> (local hoisting), both children use dc: prefix. |
Root Hoisting (With namespace_scope)
With namespace_scope, Type namespaces are hoisted to root, eliminating intermediate declarations:
class Vcard < Lutaml::Model::Serializable
attribute :contact, Contact
xml do
root "vCard"
namespace VcardNamespace
# Hoist DcNamespace to root
namespace_scope [
{ namespace: DcNamespace, declare: :always }
]
map_element "contact", to: :contact
end
end
vcard = Vcard.new(
contact: Contact.new(
title: "Dr. John Doe",
note: "Lead Researcher"
)
)
puts vcard.to_xmlOutput:
<vCard xmlns="urn:ietf:params:xml:ns:vcard-4.0"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<contact>
<dc:title>Dr. John Doe</dc:title>
<dc:note>Lead Researcher</dc:note>
</contact>
</vCard>| DcNamespace hoisted to root. Children in <contact> recognize and use parent’s xmlns:dc declaration without redeclaration. |
Multi-Namespace Documents
Complete Example: vCard with Metadata
This example demonstrates all namespace_scope features with four namespaces:
# 1. Define namespaces
class VcardNamespace < Lutaml::Model::XmlNamespace
uri "urn:ietf:params:xml:ns:vcard-4.0"
prefix_default "vcard"
end
class DcNamespace < Lutaml::Model::XmlNamespace
uri "http://purl.org/dc/elements/1.1/"
prefix_default "dc"
end
class DctermsNamespace < Lutaml::Model::XmlNamespace
uri "http://purl.org/dc/terms/"
prefix_default "dcterms"
end
class XsiNamespace < Lutaml::Model::XmlNamespace
uri "http://www.w3.org/2001/XMLSchema-instance"
prefix_default "xsi"
end
# 2. Define Type classes
class DcTitleType < Lutaml::Model::Type::String
xml_namespace DcNamespace
end
class DcCreatorType < Lutaml::Model::Type::String
xml_namespace DcNamespace
end
class DctermsCreatedType < Lutaml::Model::Type::DateTime
xml_namespace DctermsNamespace
end
class XsiTypeType < Lutaml::Model::Type::String
xml_namespace XsiNamespace
end
# 3. Define nested model
class Metadata < Lutaml::Model::Serializable
attribute :title, DcTitleType
attribute :creator, DcCreatorType
attribute :created, DctermsCreatedType
attribute :format, XsiTypeType
xml do
root "metadata"
map_element "title", to: :title
map_element "creator", to: :creator
map_element "created", to: :created
map_attribute "type", to: :format
end
end
# 4. Define root model with namespace_scope
class Vcard < Lutaml::Model::Serializable
attribute :version, :string
attribute :fn, :string
attribute :metadata, Metadata
xml do
root "vCard"
namespace VcardNamespace
# Consolidate all namespaces at root
namespace_scope [
{ namespace: VcardNamespace, declare: :always },
{ namespace: DcNamespace, declare: :always },
{ namespace: DctermsNamespace, declare: :always },
{ namespace: XsiNamespace, declare: :always }
]
map_element "version", to: :version
map_element "fn", to: :fn
map_element "metadata", to: :metadata
end
end
# 5. Create and serialize
vcard = Vcard.new(
version: "4.0",
fn: "Dr. John Doe",
metadata: Metadata.new(
title: "Chief Technology Officer",
creator: "John Doe",
created: DateTime.parse("2024-06-01T12:00:00Z"),
format: "vcard"
)
)
puts vcard.to_xmlOutput:
<vCard xmlns="urn:ietf:params:xml:ns:vcard-4.0"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:dcterms="http://purl.org/dc/terms/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<version>4.0</version>
<fn>Dr. John Doe</fn>
<metadata xsi:type="vcard">
<dc:title>Chief Technology Officer</dc:title>
<dc:creator>John Doe</dc:creator>
<dcterms:created>2024-06-01T12:00:00+00:00</dcterms:created>
</metadata>
</vCard>Key features:
-
Four namespaces consolidated: All declared at root
-
Default namespace for vCard: <version> and <fn> use no prefix
-
Prefixed Type namespaces: dc:, dcterms:, xsi: use prefixes throughout
-
Child inheritance: Metadata children recognize parent’s declarations
-
No redeclaration: Each xmlns appears exactly once
-
W3C compliant: Full round-trip serialization support
Auto vs Always Declaration
class Vcard < Lutaml::Model::Serializable
attribute :version, :string
# No DcTitleType attributes used
xml do
root "vCard"
namespace VcardNamespace
# Try to hoist unused DcNamespace
namespace_scope [
{ namespace: DcNamespace, declare: :auto }
]
map_element "version", to: :version
end
end
vcard = Vcard.new(version: "4.0")
puts vcard.to_xmlWith :auto mode (namespace unused):
<vCard xmlns="urn:ietf:params:xml:ns:vcard-4.0">
<version>4.0</version>
</vCard> DcNamespace NOT declared because unused (:auto mode). |
With :always mode:
namespace_scope [
{ namespace: DcNamespace, declare: :always }
]<vCard xmlns="urn:ietf:params:xml:ns:vcard-4.0"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<version>4.0</version>
</vCard> DcNamespace IS declared even though unused (:always mode required by specification). |
W3C Compliance
Namespace Inheritance Rules
Lutaml::Model follows W3C XML Namespace specifications for child element qualification:
-
Default namespace inheritance: Children in same namespace as parent inherit automatically
-
Prefix consistency: Same namespace always uses same prefix throughout document
-
No duplicate declarations: xmlns declared once per scope
-
Explicit opt-out: Children in different namespace use xmlns="" when needed
element_form_default
The element_form_default setting in XmlNamespace classes controls child element namespace qualification:
:qualified-
Children inherit parent namespace (W3C default for Lutaml::Model)
:unqualified-
Children in blank namespace (requires xmlns="" in output)
class ParentNamespace < Lutaml::Model::XmlNamespace
uri "http://example.com/parent"
prefix_default "parent"
element_form_default :unqualified # Children in blank namespace
end
class Parent < Lutaml::Model::Serializable
attribute :child_value, :string
xml do
root "parent"
namespace ParentNamespace
map_element "child", to: :child_value
end
end
parent = Parent.new(child_value: "value")
puts parent.to_xml(prefix: true)Output:
<parent:parent xmlns:parent="http://example.com/parent">
<child xmlns="">value</child>
</parent:parent>| Child uses xmlns="" to explicitly declare blank namespace (opt out of parent’s namespace). |
Format Preservation
Lutaml::Model preserves the exact namespace format (default vs prefix) from input XML during round-trip serialization.
Input XML (default format):
<vCard xmlns="urn:ietf:params:xml:ns:vcard-4.0">
<version>4.0</version>
</vCard>vcard = Vcard.from_xml(xml)
vcard.to_xml # Preserves default formatOutput (default format preserved):
<vCard xmlns="urn:ietf:params:xml:ns:vcard-4.0">
<version>4.0</version>
</vCard>Input XML (prefix format):
<vcard:vCard xmlns:vcard="urn:ietf:params:xml:ns:vcard-4.0">
<vcard:version>4.0</vcard:version>
</vcard:vCard>vcard = Vcard.from_xml(xml)
vcard.to_xml # Preserves prefix formatOutput (prefix format preserved):
<vcard:vCard xmlns:vcard="urn:ietf:params:xml:ns:vcard-4.0">
<vcard:version>4.0</vcard:version>
</vcard:vCard>Best Practices
When to Use namespace_scope
Use namespace_scope when:
-
Multiple child elements share same namespaces
-
Document structure benefits from consolidated declarations
-
Format specification requires specific namespaces at root (e.g., OOXML)
-
Cleaner XML readability is important
Don’t use namespace_scope when:
-
Namespace used in limited scope only
-
Default (local) declaration is sufficient
-
Simplest possible XML output desired
Choosing Declaration Modes
Use :auto mode when:
-
Standard W3C behavior desired
-
Minimize unnecessary declarations
-
Generate clean, minimal XML
-
Most common case
Use :always mode when:
-
Schema requires specific namespace declarations
-
External tools validate namespace presence
-
Format specifications mandate unused namespaces (e.g., Office Open XML with xmlns:vt)
Use :never mode when:
-
Explicitly prevent namespace usage
-
Catch errors during development
-
Reserved for future features
Type Namespaces
Type-level namespaces (via xml_namespace on custom types) are particularly useful for:
-
Reusable types belonging to specific namespaces (Dublin Core, custom XSD types)
-
Multi-namespace document structures (Office Open XML, metadata)
-
Automatic namespace application
-
W3C-compliant round-trip serialization
class DcTitleType < Lutaml::Model::Type::String
xml_namespace DcNamespace
xsd_type 'titleType'
end
class Document < Lutaml::Model::Serializable
attribute :title, DcTitleType # Automatically uses DcNamespace
xml do
root "document"
namespace DocumentNamespace
map_element "title", to: :title # Becomes <dc:title>
end
endCommon Patterns
Pattern 1: Single Root Namespace
Simplest case—all elements in one namespace:
class Document < Lutaml::Model::Serializable
xml do
root "document"
namespace DocumentNamespace
# No namespace_scope needed
end
endPattern 2: Root + Type Namespaces (Local Hoisting)
Root namespace for structure, Type namespaces for specific attributes:
class Document < Lutaml::Model::Serializable
attribute :title, DcTitleType # DcNamespace
attribute :created, DctermsCreatedType # DctermsNamespace
xml do
root "document"
namespace DocumentNamespace
# No namespace_scope - Type namespaces hoist locally
end
endPattern 3: Full Consolidation with namespace_scope
All namespaces declared at root for clean, organized XML:
class Document < Lutaml::Model::Serializable
attribute :title, DcTitleType
attribute :created, DctermsCreatedType
xml do
root "document"
namespace DocumentNamespace
# Hoist all Type namespaces to root
namespace_scope [
{ namespace: DcNamespace, declare: :always },
{ namespace: DctermsNamespace, declare: :always }
]
end
endTroubleshooting
Duplicate xmlns Declarations
Problem: Same xmlns appears multiple times
<root xmlns:dc="...">
<child xmlns:dc="..."> <!-- Duplicate! -->
</child>
</root>Solution: Ensure child inherits parent’s declaration:
# Correct: namespace_scope hoists to root
xml do
namespace_scope [DcNamespace]
endMissing Namespace Prefixes
Problem: Elements missing expected prefixes
<title>...</title> <!-- Expected <dc:title> -->Solution: Verify Type namespace declaration:
class DcTitleType < Lutaml::Model::Type::String
xml_namespace DcNamespace # Must be declared
endUnnecessary xmlns="" Declarations
Problem: Blank xmlns on children
<parent xmlns="...">
<child xmlns="">...</child> <!-- Unnecessary if same namespace -->
</parent>Solution: Verify child is in same namespace as parent. xmlns="" only needed when child is in different namespace or blank namespace.
Redundant xmlns Declarations on Nested Elements
Problem: Nested elements re-declare the same namespace
<Unit xmlns="http://example.com/units">
<RootUnits>
<EnumeratedRootUnit xmlns="http://example.com/units"/> <!-- Redundant! -->
</RootUnits>
</Unit>Solution: This was a bug fixed in version 0.7.x. Child elements in the same namespace as their parent now correctly inherit the namespace without re-declaration.
<Unit xmlns="http://example.com/units">
<RootUnits>
<EnumeratedRootUnit/> <!-- Correctly inherits parent namespace -->
</RootUnits>
</Unit>This follows W3C XML Namespaces 1.0 §6.2 which states that child elements inherit the default namespace from their parent.
Adapter Compatibility
All four XML adapters (Nokogiri, Ox, Oga, REXML) now support the same namespace features:
| Feature | Nokogiri | Ox | Oga | REXML |
|---|---|---|---|---|
Default namespace format | ✓ | ✓ | ✓ | ✓ |
Prefixed namespace format | ✓ | ✓ | ✓ | ✓ |
Namespace inheritance | ✓ | ✓ | ✓ | ✓ |
Custom prefix strings ( | ✓ | ✓ | ✓ | ✓ |
Round-trip namespace preservation | ✓ | ✓ | ✓ | ✓ |
| All adapters now produce identical namespace output. The Oga adapter namespace issues that existed in earlier versions have been resolved. |
Architecture Notes
For developers working on Lutaml::Model internals:
-
DeclarationPlanner: Makes ALL namespace decisions (where to declare, what format)
-
Adapters: Only apply decisions (Dumb Adapter Principle)
-
Three Phases: Collect → Plan → Render
-
XmlDataModel: Content layer (WHAT to serialize)
-
DeclarationPlan: Declaration layer (WHERE and HOW to declare namespaces)
See Memory Bank for detailed architecture documentation.