- Overview
- The Two Presentation Formats
- Semantic Equivalence
- XML Infoset Perspective
- Controlling Presentation
- When Prefixes Are Required
- Presentation Best Practices
- Presentation in Collections
- Preserving Presentation
- Namespace Declaration Location
- Testing Both Presentations
- Common Pitfalls
- Summary
- See Also
Overview
This document explains XML namespace presentation formats in Lutaml::Model. While namespace semantics determine which namespaces apply to which elements, presentation controls how those namespaces appear in the serialized XML.
Key Principle: Presentation is a purely cosmetic choice that does not affect the meaning of the XML document.
The Two Presentation Formats
XML namespaces can be declared in two ways, both producing semantically identical documents:
Default Namespace Format
Uses xmlns without a prefix. All unprefixed elements in scope belong to this namespace:
<item xmlns="http://example.com/ns">
<name>Value</name>
<description>Text</description>
</item>Characteristics: * Cleaner, more readable * Less verbose * Better for documents with primarily one namespace * Cannot be used for XML attributes (attributes are unqualified by default)
Prefixed Namespace Format
Uses xmlns:prefix and prefixes all elements:
<ex:item xmlns:ex="http://example.com/ns">
<ex:name>Value</ex:name>
<ex:description>Text</ex:description>
</ex:item>Characteristics: * More explicit * Required when multiple namespaces in same scope * Necessary for namespace-qualified attributes * Can coexist with default namespace for other namespaces
Semantic Equivalence
These two XML documents are completely identical in their meaning:
<!-- 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>Proof of Equivalence:
xml_default = '<first_item xmlns="http://example.com/first">
<name>Item Name</name>
</first_item>'
xml_prefixed = '<first:first_item xmlns:first="http://example.com/first">
<first:name>Item Name</first:name>
</first:first_item>'
# Both parse to identical model
item1 = NativeItem.from_xml(xml_default)
item2 = NativeItem.from_xml(xml_prefixed)
item1 == item2 # => true
item1.name == item2.name # => "Item Name"XML Infoset Perspective
The XML Information Set (Infoset) specification defines the abstract data model of an XML document. From the Infoset perspective:
-
An element has a namespace name (the URI)
-
An element has a local name (without prefix)
-
The prefix is merely a syntactic binding, not part of the element’s identity
Therefore: * <name xmlns="http://ex.com/ns"> * <ex:name xmlns:ex="http://ex.com/ns">
Both have: * Namespace name: http://ex.com/ns * Local name: name * Different prefix binding (none vs "ex")
The Infoset is identical - only the serialization differs.
Controlling Presentation
Lutaml::Model provides several ways to control presentation format:
Default Behavior
Without explicit control, Lutaml::Model chooses format automatically:
item = NativeItem.new(name: "Test")
xml = item.to_xml
# Uses default namespace if only one namespace
# Uses prefixes if multiple namespaces to avoid conflictsForce Prefixed Format
xml = item.to_xml(prefix: true)
# Always uses prefixed format, even with single namespaceResult:
<first:first_item xmlns:first="http://example.com/first">
<first:name>Test</first:name>
</first:first_item>When Prefixes Are Required
Lutaml::Model automatically uses prefixes when necessary:
Multiple Namespaces
Cannot have two default namespaces simultaneously:
class NamespacedItem < Lutaml::Model::Serializable
attribute :name, FirstNamespacedName # FirstNamespace
attribute :alt_name, SecondNamespacedName # SecondNamespace
xml do
root "item"
namespace SecondNamespace # Root uses SecondNamespace
map_element "name", to: :name
map_element "alt_name", to: :alt_name
end
endResult (automatic prefix for conflicting namespace):
<item xmlns="http://example.com/second"
xmlns:first="http://example.com/first">
<first:name>Value</first:name>
<alt_name>Value</alt_name>
</item>Namespace-Qualified Attributes
Per W3C specification, attributes in the same namespace as their element must use a prefix:
<!-- INVALID: Attribute cannot use default namespace -->
<item xmlns="http://example.com/ns" xmlns:ns="http://example.com/ns"
value="123">
</item>
<!-- VALID: Attribute uses prefix -->
<item xmlns="http://example.com/ns"
ns:value="123">
</item>Lutaml::Model handles this automatically.
Presentation Best Practices
Single Namespace Documents
For documents primarily in one namespace, use default format:
# Cleaner, more readable
item.to_xml # or item.to_xml(prefix: false)Result:
<item xmlns="http://example.com/ns">
<name>Value</name>
<description>Text</description>
</item>Multi-Namespace Documents
For documents mixing namespaces, use prefixes for clarity:
wrapper.to_xml(prefix: true)Result:
<wr:wrapper xmlns:wr="http://wrapper.example.com"
xmlns:first="http://first.example.com"
xmlns:second="http://second.example.com">
<second:item>
<first:name>Name</first:name>
<second:alt_name>Alt</second:alt_name>
</second:item>
</wr:wrapper>Consistent Style
Choose one style per document type:
# Good: Consistent default namespace style
class ApiResponse < Lutaml::Model::Serializable
xml do
namespace ApiNamespace
# All children use default namespace
end
end
# Good: Consistent prefixed style
class MixedDocument < Lutaml::Model::Serializable
xml do
namespace BaseNamespace
# Mix with other namespaces using prefixes
end
endPresentation in Collections
Collections follow the same presentation rules:
collection = NativeItemCollection.new(items: items)
# Default namespace for all items
xml_default = collection.to_xml
# Prefixed namespace for all items
xml_prefixed = collection.to_xml(prefix: true)Both parse identically:
parsed1 = NativeItemCollection.from_xml(xml_default)
parsed2 = NativeItemCollection.from_xml(xml_prefixed)
parsed1 == parsed2 # => truePreserving Presentation
Important: Lutaml::Model does NOT preserve the original presentation format during round-tripping:
# Parse prefixed format
xml_input = '<first:item xmlns:first="http://ex.com/ns">
<first:name>Test</first:name>
</first:item>'
item = Item.from_xml(xml_input)
# Serialize (may use different format)
xml_output = item.to_xml # Might use default namespace
# But semantics are preserved
Item.from_xml(xml_output) == item # => trueRationale: Presentation is not semantic information. The model stores data, not XML formatting choices.
If you need specific format, explicitly request it:
xml_output = item.to_xml(prefix: true) # Guarantee prefixed formatNamespace Declaration Location
Namespaces are typically declared at the root element:
<wrapper xmlns="http://wrapper.ex.com"
xmlns:first="http://first.ex.com"
xmlns:second="http://second.ex.com">
<!-- All namespaces declared here -->
<second:item>
<first:name>Value</first:name>
</second:item>
</wrapper>Benefit: "Declare once, use everywhere" principle * More efficient * Easier to read * Follows XML best practices
Local Declarations: Only when namespace scope restrictions apply (advanced feature).
Testing Both Presentations
Always test that your models work with both presentation formats:
RSpec.describe MyModel do
let(:instance) { MyModel.new(name: "Test") }
it "round-trips with default namespace" do
xml = instance.to_xml(prefix: false)
parsed = MyModel.from_xml(xml)
expect(parsed).to eq(instance)
end
it "round-trips with prefixed namespace" do
xml = instance.to_xml(prefix: true)
parsed = MyModel.from_xml(xml)
expect(parsed).to eq(instance)
end
it "produces semantically identical output" do
xml_default = instance.to_xml(prefix: false)
xml_prefixed = instance.to_xml(prefix: true)
parsed_default = MyModel.from_xml(xml_default)
parsed_prefixed = MyModel.from_xml(xml_prefixed)
expect(parsed_default).to eq(parsed_prefixed)
end
endCommon Pitfalls
Expecting Preserved Presentation
# ❌ Don't assume format is preserved
xml_in = '<prefix:item xmlns:prefix="http://ex.com">...</prefix:item>'
item = Item.from_xml(xml_in)
xml_out = item.to_xml
# xml_out might use default namespace, not prefix
# ✓ Explicitly request format if needed
xml_out = item.to_xml(prefix: true)Summary
-
Prefix vs Default: Purely presentation, semantically identical
-
XML Infoset: Same namespace URI + local name = same element
-
Auto Selection: Lutaml::Model chooses format automatically
-
Explicit Control: Use
to_xml(prefix: true/false/"custom") -
Multiple Namespaces: Automatically use prefixes to avoid conflicts
-
Testing: Verify both formats parse correctly
-
Don’t Preserve: Format not preserved during round-trip (by design)