- General
- Namespace Format Preservation (Round Trip)
- Automatic Preservation
- Implementation Architecture
- Programmatic Models
- Format Priority
- Adapter Support
- W3C Standard Namespaces
- XML namespace
- General
- W3C XML namespace types
- Creating namespace classes
- DSL methods
- Complete namespace example
- Namespace identity of element or type (
namespacemethod) - Namespace scope consolidation (
namespace_scopemethod) - Form override (W3C Schema compliance)
- XSD annotation (
documentationmethod) - XSD type name declaration (
type_namemethod) - XSD sequence requirements (
sequenceblock) - Automatic support of
xsi:schemaLocation
- Deprecated ways of working with namespaces
- Custom namespace prefixes
General
XML namespaces provide a way to qualify element and attribute names to avoid conflicts. Lutaml::Model provides comprehensive namespace support following W3C specifications.
Namespace prefix independence
General
Lutaml::Model implements W3C-compliant XML namespace processing where namespace URIs, not prefixes, determine element identity. This means that models will correctly parse XML documents regardless of which prefix (or no prefix) is used, as long as the namespace URI matches.
This W3C-compliant behavior ensures maximum interoperability when consuming XML from different sources.
The namespace URI determines identity, not the prefix.
Supported prefix formats
When a model is defined with a namespace, it will successfully parse XML using any of these formats:
- Default namespace (unprefixed)
-
xmlns="http://example.com/ns" - Prefixed namespace with model’s default prefix
-
xmlns:cer="http://example.com/ns" - Prefixed namespace with ANY arbitrary prefix
-
xmlns:pottery="http://example.com/ns"
All three formats are semantically identical according to W3C XML Namespaces specification.
When there is no default namespace declared, unprefixed elements are treated as having the "blank" namespace, i.e. xmlns="". It is possible to force elements to have a blank namespace using #to_xml(prefix: "") or #to_xml(prefix: nil).
class CeramicNamespace < Lutaml::Model::XmlNamespace
uri 'http://example.com/ceramic'
prefix_default 'cer'
end
class Ceramic < Lutaml::Model::Serializable
attribute :type, :string
xml do
element 'Ceramic'
namespace CeramicNamespace
map_element 'Type', to: :type
end
end
# All these XML formats parse successfully to the same result:
# Format 1: Default namespace
xml1 = '<Ceramic xmlns="http://example.com/ceramic"><Type>Porcelain</Type></Ceramic>'
Ceramic.from_xml(xml1).type # => "Porcelain"
# Format 2: Model's defined prefix
xml2 = '<cer:Ceramic xmlns:cer="http://example.com/ceramic"><cer:Type>Porcelain</cer:Type></cer:Ceramic>'
Ceramic.from_xml(xml2).type # => "Porcelain"
# Format 3: Arbitrary prefix
xml3 = '<pottery:Ceramic xmlns:pottery="http://example.com/ceramic"><pottery:Type>Porcelain</pottery:Type></pottery:Ceramic>'
Ceramic.from_xml(xml3).type # => "Porcelain"All three examples produce identical Ceramic objects. The namespace URI http://example.com/ceramic matches, which is what matters according to W3C specifications.
Nested models with different namespaces
Prefix independence extends to nested models where parent and child use different namespaces:
class GlazeNamespace < Lutaml::Model::XmlNamespace
uri 'http://example.com/glaze'
prefix_default 'glz'
end
class Glaze < Lutaml::Model::Serializable
attribute :color, :string
xml do
element 'Glaze'
namespace GlazeNamespace
map_element 'Color', to: :color
end
end
class Ceramic < Lutaml::Model::Serializable
attribute :type, :string
attribute :glaze, Glaze
xml do
element 'Ceramic'
namespace CeramicNamespace
map_element 'Type', to: :type
map_element 'Glaze', to: :glaze
end
end
# Parent and child can use any prefixes as long as URIs match:
xml = <<~XML
<x:Ceramic xmlns:x="http://example.com/ceramic"
xmlns:y="http://example.com/glaze">
<x:Type>Porcelain</x:Type>
<y:Glaze>
<y:Color>Clear</y:Color>
</y:Glaze>
</x:Ceramic>
XML
result = Ceramic.from_xml(xml)
result.type # => "Porcelain"
result.glaze.color # => "Clear"Type-level namespace prefix independence
When attribute types declare their own namespaces via xml_namespace, those types also support prefix-independent parsing:
class ColorNamespace < Lutaml::Model::XmlNamespace
uri 'http://example.com/color'
prefix_default 'clr'
end
class ColorType < Lutaml::Model::Type::String
xml_namespace ColorNamespace
end
class Ceramic < Lutaml::Model::Serializable
attribute :type, :string
attribute :color, ColorType
xml do
element 'Ceramic'
namespace CeramicNamespace
map_element 'Type', to: :type
map_element 'Color', to: :color
end
end
# Color element can use any prefix for the color namespace:
xml = <<~XML
<Ceramic xmlns="http://example.com/ceramic"
xmlns:c="http://example.com/color">
<Type>Porcelain</Type>
<c:Color>Navy Blue</c:Color>
</Ceramic>
XML
result = Ceramic.from_xml(xml)
result.color # => "Navy Blue"Round-trip serialization
Models support round-trip serialization between different namespace formats. You can parse XML with any prefix format and serialize to either default or prefixed format:
# Parse XML with arbitrary prefix
xml_arbitrary = '<pottery:Ceramic xmlns:pottery="http://example.com/ceramic"><pottery:Type>Porcelain</pottery:Type></pottery:Ceramic>'
ceramic = Ceramic.from_xml(xml_arbitrary)
# Serialize to default namespace format
ceramic.to_xml
# => '<Ceramic xmlns="http://example.com/ceramic"><Type>Porcelain</Type></Ceramic>'
# Serialize to prefixed format with model's defined prefix
ceramic.to_xml(prefix: true)
# => '<cer:Ceramic xmlns:cer="http://example.com/ceramic"><cer:Type>Porcelain</cer:Type></cer:Ceramic>'Both serialized formats can be parsed back successfully, demonstrating full prefix independence.
Namespace Format Preservation (Round Trip)
Automatic Preservation
Lutaml::Model automatically preserves the namespace format from input XML:
# Parse XML with default format
xml = '<Model xmlns="http://example.com"><field>value</field></Model>'
model = Model.from_xml(xml)
# Serialize preserves default format
model.to_xml
# => '<Model xmlns="http://example.com">...</Model>'# Parse XML with prefix format
xml = '<ex:Model xmlns:ex="http://example.com"><ex:field>value</ex:field></ex:Model>'
model = Model.from_xml(xml)
# Serialize preserves prefix format
model.to_xml
# => '<ex:Model xmlns:ex="http://example.com">...</ex:Model>'Implementation Architecture
The format preservation uses a four-phase process:
-
Phase 1: Input Format Capture - During XML parsing, namespace declarations are extracted with their format (
:defaultor:prefix) -
Phase 2: Metadata Storage - Format information is stored on the model instance as
@__input_namespaces -
Phase 3: Declaration Planning - The DeclarationPlanner receives input namespaces and matches by URI to preserve format
-
Phase 4: Serialization - The adapter applies the planned declarations with preserved format
Programmatic Models
For models created programmatically (not from XML), the format is determined by:
-
Explicit
prefixoption:model.to_xml(prefix: true/false) -
Namespace configuration:
prefix_defaultin XmlNamespace class -
W3C rules: Attributes in element’s namespace require prefix format
-
Default: Prefer default format (cleaner output)
Format Priority
When multiple format sources exist, priority is:
-
Input format (from parsed XML) - Highest priority, always preserved
-
Explicit option (
prefix: true/false) -
W3C compliance (e.g., attributes require prefix)
-
Namespace configuration (
prefix_default) -
Default format - Lowest priority
Adapter Support
All three XML adapters support format preservation:
-
Nokogiri - Full support ✅
-
Ox - Full support ✅
-
Oga - Full support ✅
Elements in blank namespace
When you need an element to have NO namespace (not even inherit parent namespace), create a child model class without a namespace declaration.
# Child model with no namespace
class Note < Lutaml::Model::Serializable
attribute :value, :string
xml do
element "Note"
# No namespace declaration = blank namespace
map_content to: :value
end
end
class MixedModel < Lutaml::Model::Serializable
attribute :type, :string
attribute :note, Note
xml do
element "Ceramic"
namespace CeramicNamespace
map_element "Type", to: :type
map_element "Note", to: :note # Uses Note's namespace (blank)
end
endWhen parent uses prefixed namespace (recommended):
Unprefixed child elements naturally have no namespace:
<cer:Ceramic xmlns:cer="http://example.com/ceramic">
<cer:Type>Porcelain</cer:Type>
<Note>This element has no namespace</Note>
</cer:Ceramic>When parent uses default namespace:
Must use xmlns="" to explicitly remove the namespace:
<Ceramic xmlns="http://example.com/ceramic">
<Type>Porcelain</Type>
<Note xmlns="">This element has no namespace</Note>
</Ceramic>Per W3C XML Namespace specification, unprefixed elements normally inherit their parent’s default namespace. The xmlns="" declaration explicitly removes that inheritance.
Parsing support: All three adapters (Nokogiri, Ox, Oga) correctly handle both patterns.
Namespace assignment priority
Namespace resolution follows a strict priority system when multiple namespace sources are available:
| Priority | Source | Example |
|---|---|---|
1 (HIGHEST) | Model-level namespace |
|
2 | Type level |
|
3 (LOWEST) | Context level (schema defaults) |
|
class ItemNamespace < Lutaml::Model::XmlNamespace
uri "http://example.com/items"
prefix_default "item"
element_form_default :qualified
end
class BlankNamespace < Lutaml::Model::XmlNamespace
uri "" # Blank namespace
end
class PriceType < Lutaml::Model::Type::String
xml_namespace PriceNamespace # Type-level namespace
end
# Child model in blank namespace
class Name < Lutaml::Model::Serializable
attribute :value, :string
xml do
element "name"
namespace BlankNamespace # Model-level namespace (blank)
map_content to: :value
end
end
class Product < Lutaml::Model::Serializable
attribute :name, Name
attribute :price, PriceType
attribute :note, :string
xml do
element "product"
namespace ItemNamespace
# Priority 1: Model-level namespace (blank)
map_element "name", to: :name
# Priority 2: Type-level namespace
map_element "price", to: :price
# Priority 3: Context from schema (qualified by default)
map_element "note", to: :note
end
endResult with prefix: true:
<item:product xmlns:item="http://example.com/items"
xmlns:price="http://example.com/prices">
<name>Widget</name> <!-- Priority 1: model namespace (blank) -->
<price:price>19.99</price:price> <!-- Priority 2: type namespace -->
<item:note>Description</item:note> <!-- Priority 3: schema qualified -->
</item:product>W3C Standard Namespaces
The xml Namespace
The xml namespace (http://www.w3.org/XML/1998/namespace) has special status in W3C XML specifications:
-
The prefix
xmlis reserved and implicitly bound to this URI -
MUST NOT be declared with
xmlns:xml="…" -
Always available for xml:lang, xml:space, xml:base, xml:id attributes
Lutaml::Model correctly handles this W3C requirement - the xml namespace is never declared in serialized XML.
class XmlNamespace < Lutaml::Model::XmlNamespace
uri "http://www.w3.org/XML/1998/namespace"
prefix_default "xml"
end
class XmlLangType < Lutaml::Model::Type::String
xml_namespace XmlNamespace
end
class XmlSpaceType < Lutaml::Model::Type::String
xml_namespace XmlNamespace
end
class Document < Lutaml::Model::Serializable
attribute :lang, XmlLangType
attribute :space, XmlSpaceType
attribute :content, :string
xml do
root "doc"
map_attribute "lang", to: :lang
map_attribute "space", to: :space
map_content to: :content
end
end
doc = Document.new(lang: "en", space: "preserve", content: " Text ")
puts doc.to_xmlOutput:
<doc xml:lang="en" xml:space="preserve"> Text </doc> No xmlns:xml declaration appears - the xml namespace is implicitly bound per W3C specification. |
| Attribute | Purpose |
|---|---|
| Specifies the natural language of content (RFC 5646 language tags) |
| Controls whitespace handling ( |
| Provides unique identifiers (W3C XML ID) |
| Sets base URI for relative URI resolution |
XML namespace
General
An XML namespace is represented in Lutaml::Model as an inherited class from XmlNamespace. Model and value classes can declare their namespace using such XML namespace class.
The XmlNamespace class provides a declarative way to define XML namespace metadata following W3C XML Namespace and XSD specifications.
W3C XML namespace types
Lutaml::Model provides pre-defined type classes for W3C xml namespace attributes with built-in validation and serialization support.
| Type Class | Purpose | Validation |
|---|---|---|
| Language identification (RFC 5646) | Accepts any valid language tag |
| Whitespace handling control | Only "default" or "preserve" |
| Base URI for relative references | Accepts any valid URI string |
| Unique identifier (XML ID type) | Must be valid NCName |
class Article < Lutaml::Model::Serializable
attribute :lang, Lutaml::Model::Xml::W3c::XmlLangType
attribute :space, Lutaml::Model::Xml::W3c::XmlSpaceType
attribute :id, Lutaml::Model::Xml::W3c::XmlIdType
attribute :base, Lutaml::Model::Xml::W3c::XmlBaseType
attribute :title, :string
attribute :content, :string
xml do
root "article"
map_attribute "lang", to: :lang
map_attribute "space", to: :space
map_attribute "id", to: :id
map_attribute "base", to: :base
map_element "title", to: :title
map_content to: :content
end
end
# Valid usage
article = Article.new(
lang: "en-US",
space: "preserve",
id: "article1",
base: "http://example.com/",
title: "Title",
content: " Content with spaces "
)
puts article.to_xmlOutput:
<article xml:lang="en-US" xml:space="preserve" xml:id="article1" xml:base="http://example.com/">
<title>Title</title>
Content with spaces
</article>Invalid usage raises errors:
# Invalid xml:space value
Article.new(space: "compact")
# => ArgumentError: xml:space must be 'default' or 'preserve'
# All W3C xml attributes serialize with xml: prefix
# No xmlns:xml declaration is ever generatedThis enables automatic namespace qualification of elements and attributes based on their type, following W3C XML Namespace specifications.
This approach centralizes namespace configuration and enables:
-
Reusable namespace definitions across models
-
Full XSD generation support with proper namespace declarations
-
Control over element and attribute qualification
-
Documentation and versioning of schemas
-
Schema imports and includes
According to the W3C XML Namespace specification, unprefixed elements and attributes are handled differently:
-
unprefixed elements are considered to be in the default namespace (the declared default namespace of the parent, or the blank namespace if none is declared)
-
unprefixed attributes are considered to be in the blank namespace unless explicitly declared with a prefix.
Lutaml Models and Value types can each be specified to belong to an XML namespace via their respective namespace methods (model: namespace method in the xml block, value: xml_namespace method in the class). These models and types will always serialize their elements and attributes qualified with their namespace, either as the default namespace or with a prefix.
For models (i.e. serialized into XSD Types) and value types, the element_form_default and attribute_form_default settings in the XmlNamespace class are used to control the qualification of them within the parent.
For example:
class MyNamespace < Lutaml::Model::XmlNamespace
uri 'http://example.com/myns'
prefix_default 'myns'
element_form_default :qualified
attribute_form_default :unqualified
end
class MyModel < Lutaml::Model::Serializable
attribute :name, :string
attribute :age, :integer
xml do
element 'MyType'
namespace MyNamespace
map_element 'name', to: :name
map_attribute 'age', to: :age
end
end
my_model = MyModel.new(name: "Example", age: 30)
puts my_model.to_xml # defaults to using default prefix
#=>
<MyType xmlns:hello="http://example.com/myns" age="30">
<name>Example</name>
</MyType>
# Note: No prefix on elements (W3C Namespace standard says unprefixed elements
# are in default namespace), no prefix on attribute (W3C Namespace standard says
# unprefixed attributes are in blank namespace)
puts my_model.to_xml(prefix: "hello") # Force custom prefix usage
#=>
<hello:MyType xmlns:hello="http://example.com/myns" age="30">
<hello:name>Example</hello:name>
</hello:MyType>
# Note: Prefix on elements (qualified), no prefix on attribute (unqualified)class MyNamespace < Lutaml::Model::XmlNamespace
uri 'http://example.com/myns'
prefix_default 'myns'
element_form_default :qualified
attribute_form_default :qualified
end
class MyModel < Lutaml::Model::Serializable
attribute :name, :string
attribute :age, :integer
xml do
element 'MyType'
namespace MyNamespace
map_element 'name', to: :name
map_attribute 'age', to: :age
end
end
my_model = MyModel.new(name: "Example", age: 30)
puts my_model.to_xml # defaults to using default prefix, prefix is forced
# because attribute_form_default is :qualified, an attribute MUST be prefixed
# to indicate its namespace, hence the parent element must provide a prefixed
# namespace.
#=>
<myns:MyType xmlns:myns="http://example.com/myns" myns:age="30">
<myns:name>Example</myns:name>
</myns:MyType>Remember it is only the no-namespace model attributes that are affected by these settings of element_form_default and attribute_form_default. If an attribute or element is mapped to a type that belongs to a namespace, it will be qualified according to that namespace’s rules.
class ElementNamespace < Lutaml::Model::XmlNamespace
uri 'http://example.com/elementns'
prefix_default 'elns'
end
class AttributeNamespace < Lutaml::Model::XmlNamespace
uri 'http://example.com/attributens'
prefix_default 'atns'
end
class AttributeValue < Lutaml::Model::Type::String
xml_namespace AttributeNamespace
end
class MyModel < Lutaml::Model::Serializable
attribute :name, :string
attribute :type, AttributeValue
xml do
element 'MyType'
namespace ElementNamespace
map_element 'name', to: :name
map_attribute 'type', to: :type
end
end
my_model = MyModel.new(name: "Example", type: "Special")
puts my_model.to_xml # element uses element namespace (which defaults to the
# default namespace), attribute uses attribute namespace, forcing a prefixed
# namespace
#=>
<MyType xmlns="http://example.com/elementns"
xmlns:atns="http://example.com/attributens"
atns:type="Special">
<name>Example</name>
</MyType>Creating namespace classes
A namespace class inherits from Lutaml::Model::XmlNamespace and uses a DSL to declare metadata.
Syntax:
class MyNamespace < Lutaml::Model::XmlNamespace
uri 'namespace-uri' # Required: namespace URI
schema_location 'schema-url' # Optional: XSD location
prefix_default 'default-prefix' # Optional: default prefix
element_form_default :qualified # Optional: element qualification
attribute_form_default :unqualified # Optional: attribute qualification
version 'version-string' # Optional: schema version
documentation 'description' # Optional: schema documentation
imports OtherNamespace # Optional: imported namespaces
includes 'schema-file.xsd' # Optional: included schemas
endDSL methods
uri
Sets the namespace URI that uniquely identifies this namespace.
Syntax:
uri 'namespace-uri-string'This is the fundamental identifier for the namespace, used in xmlns declarations.
class CeramicNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/schemas/ceramic/v1'
end
# Results in XML: xmlns="https://example.com/schemas/ceramic/v1"
# Or with prefix: xmlns:cer="https://example.com/schemas/ceramic/v1" schema_location
Sets the URL where the XSD schema file can be found.
Syntax:
schema_location 'schema-url-or-path'Used in xsi:schemaLocation attributes for schema validation.
class CeramicNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/schemas/ceramic/v1'
schema_location 'https://example.com/schemas/ceramic/v1/ceramic.xsd'
end prefix_default
Sets the default prefix for this namespace.
Syntax:
prefix_default 'prefix' # or :prefix (symbol)The prefix can be overridden at runtime when creating namespace instances.
class CeramicNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/schemas/ceramic/v1'
prefix_default 'cer'
end
# Default usage: xmlns:cer="https://..."
# Runtime override: xmlns:ceramic="https://..." element_form_default
Controls whether locally declared elements must be namespace-qualified by default.
Syntax:
element_form_default :qualified # or :unqualified (default)Values:
:qualified-
Local elements must include namespace prefix
:unqualified-
Local elements have no namespace prefix (default)
class CeramicNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/schemas/ceramic/v1'
prefix_default 'cer'
element_form_default :qualified
end
# With :qualified, child elements get: <cer:type>...</cer:type>
# With :unqualified, child elements get: <type>...</type> attribute_form_default
Controls whether locally declared attributes must be namespace-qualified by default.
Syntax:
attribute_form_default :qualified # or :unqualified (default)Values:
:qualified-
Local attributes must include namespace prefix
:unqualified-
Local attributes have no namespace prefix (default)
| Per W3C conventions, attributes are typically unqualified. |
class CeramicNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/schemas/ceramic/v1'
prefix_default 'cer'
attribute_form_default :unqualified # Typically left unqualified
end imports
Declares dependencies on other namespaces via XSD import directive.
Syntax:
imports OtherNamespace1, OtherNamespace2, ...Used when referencing types from other namespaces.
class AddressNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/schemas/address/v1'
prefix_default 'addr'
end
class ContactNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/schemas/contact/v1'
prefix_default 'contact'
imports AddressNamespace # Import address namespace
end
# Generates in XSD:
# <xs:import namespace="https://example.com/schemas/address/v1"
# schemaLocation="..." /> includes
Declares schema components from the same namespace via XSD include directive.
Syntax:
includes 'schema-file.xsd', 'another-file.xsd', ...Used for modular schema organization within the same namespace.
class ContactNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/schemas/contact/v1'
prefix_default 'contact'
includes 'contact-common.xsd', 'contact-extensions.xsd'
end
# Generates in XSD:
# <xs:include schemaLocation="contact-common.xsd" />
# <xs:include schemaLocation="contact-extensions.xsd" /> version
Sets the schema version for documentation and tracking.
Syntax:
version 'version-string'class ContactNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/schemas/contact/v1'
version '1.0.0'
end
# Used in XSD: <xs:schema version="1.0.0"> documentation
Provides human-readable description for XSD annotation.
Syntax:
documentation 'description text'class ContactNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/schemas/contact/v1'
documentation "Contact information schema for Example Corp"
end
# Generates in XSD:
# <xs:annotation>
# <xs:documentation>Contact information schema for Example Corp</xs:documentation>
# </xs:annotation>Complete namespace example
# Define dependent namespace
class AddressNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/schemas/address/v1'
schema_location 'https://example.com/schemas/address/v1/address.xsd'
prefix_default 'addr'
end
# Define main namespace with all features
class ContactNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/schemas/contact/v1'
schema_location 'https://example.com/schemas/contact/v1/contact.xsd'
prefix_default 'contact'
element_form_default :qualified
attribute_form_default :unqualified
version '1.0'
documentation "Contact information schema for Example Corp"
imports AddressNamespace
includes 'contact-common.xsd', 'contact-types.xsd'
end
# Use namespace in model
class Person < Lutaml::Model::Serializable
attribute :name, :string
attribute :email, :string
xml do
element "person"
namespace ContactNamespace
sequence do
map_element "name", to: :name
map_element "email", to: :email
end
end
end
person = Person.new(name: "John Doe", email: "john@example.com")
puts person.to_xml
# => <contact:person xmlns:contact="https://example.com/schemas/contact/v1">
# <contact:name>John Doe</contact:name>
# <contact:email>john@example.com</contact:email>
# </contact:person> Namespace identity of element or type (namespace method)
Declaration of namespace
The namespace method in the xml block indicates that the element or type belongs to the specified XML namespace.
Syntax:
# Assume we have defined an XmlNamespace class called ExampleXmlNamespace
xml do
namespace ExampleXmlNamespace
endnamespace method to set namespace for an elementclass CeramicXmlNamespace
uri 'http://example.com/ceramic'
prefix_default 'cer'
end
class Ceramic < Lutaml::Model::Serializable
attribute :type, :string
attribute :glaze, :string
xml do
element 'Ceramic'
namespace CeramicXmlNamespace
map_element 'Type', to: :type
map_element 'Glaze', to: :glaze
end
end<Ceramic xmlns='http://example.com/ceramic'>
<Type>Porcelain</Type>
<Glaze>Clear</Glaze>
</Ceramic>By default, serialization using to_xml uses the namespace as the XML default namespace.
> Ceramic.from_xml(xml_file)
> #<Ceramic:0x0000000104ac7240 @type="Porcelain", @glaze="Clear">
> Ceramic.new(type: "Porcelain", glaze: "Clear").to_xml
> #<Ceramic xmlns="http://example.com/ceramic">
# <Type>Porcelain</Type>
# <Glaze>Clear</Glaze>
#</Ceramic>Use the #to_xml prefix: true option to force the defined prefix:
> Ceramic.new(type: "Porcelain", glaze: "Clear").to_xml(prefix: true)
> #<cer:Ceramic xmlns:cer="http://example.com/ceramic">
# <cer:Type>Porcelain</cer:Type>
# <cer:Glaze>Clear</cer:Glaze>
#</cer:Ceramic>Use the #to_xml prefix: "custom" option to forces a custom prefix:
> Ceramic.new(type: "Porcelain", glaze: "Clear").to_xml(prefix: "custom")
> #<custom:Ceramic xmlns:custom="http://example.com/ceramic">
# <custom:Type>Porcelain</custom:Type>
# <custom:Glaze>Clear</custom:Glaze>
#</custom:Ceramic>Force display of unused prefixes
In some cases, you may want to declare additional namespaces in the XML output even if they are not used by any elements.
This is technically not needed by W3C standards conformant XML processors, but some legacy XML processors may require it.
In this case, the namespace_scope method can be used to force declaration of additional namespaces to be included in the output.
class AppNamespace < Lutaml::Model::XmlNamespace
uri 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties'
prefix_default 'app'
end
class VtNamespace < Lutaml::Model::XmlNamespace
uri 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'
prefix_default 'vt'
end
class Properties < Lutaml::Model::Serializable
attribute :template, :string
xml do
element "Properties"
namespace AppNamespace
# Force vt namespace to be declared even if unused
namespace_scope [
{ namespace: VtNamespace, declare: :always }
]
map_element "Template", to: :template
end
end
props = Properties.new(template: "Normal.dotm")
puts props.to_xml # Default: root namespace is default, others prefixedOutput:
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
<Template>Normal.dotm</Template>
</Properties>With prefix: true option:
puts props.to_xml(prefix: true)Output:
<app:Properties xmlns:app="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
<app:Template>Normal.dotm</app:Template>
</app:Properties> The VtNamespace always uses "vt:" prefix regardless of the prefix: option. |
Namespace prefix override
Namespace prefixes can be overridden when building namespace instances.
class ContactNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/schemas/contact/v1'
prefix_default 'contact'
end
# Use default prefix
class Person < Lutaml::Model::Serializable
xml do
element "person"
namespace ContactNamespace # Uses 'contact' prefix
end
end
# Override prefix at mapping level
class ShortPerson < Lutaml::Model::Serializable
xml do
element "person"
namespace ContactNamespace, 'c' # Override to 'c' prefix
end
end
Person.new.to_xml
# => <contact:person xmlns:contact="...">...</contact:person>
ShortPerson.new.to_xml
# => <c:person xmlns:c="...">...</c:person> Namespace scope consolidation (namespace_scope method)
| For detailed information on xmlns declaration strategies and the minimal-subtree principle, see XML Namespace Declarations Guide. |
General
The namespace_scope directive controls where namespace declarations are eligible to appear in serialized XML.
By default, namespaces are declared on the elements where they are used. With namespace_scope, you can consolidate multiple namespace declarations at a parent element for cleaner, more compact XML.
The namespace_scope directive supports a declare: option to force unused namespaces to be included in the output.
Syntax:
xml do
namespace RootNamespace
namespace_scope [Namespace1, Namespace2, Namespace3] (1)
# is identical to:
# namespace_scope [
# { namespace: Namespace1, declare: :auto },
# { namespace: Namespace2, declare: :auto },
# { namespace: Namespace3, declare: :auto }
# ]
# Per-namespace control with hash format
namespace_scope [ (2)
{ namespace: Namespace1, declare: :always },
{ namespace: Namespace2, declare: :auto },
# Namespace3 # CANNOT mix hash and class
]
end| 1 | Simple list of XmlNamespace classes to declare at root (default: :auto) |
| 2 | Per-namespace control with individual declare: settings using hash format |
Where,
namespace_scope-
Array of XmlNamespace class objects or Hash configurations. These namespaces will be declared at the root element based on their declaration mode.
declare:-
Controls when namespace is declared:
:auto-
(default) Declare only if namespace is actually used in child elements/attributes (or the element itself)
:always-
Always declare namespace, even if unused in child elements/attributes or the element itself
:never-
Never declare (error if used)
Basic namespace consolidation
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
# Types with different namespaces
class DcTitleType < Lutaml::Model::Type::String
xml_namespace DcNamespace
end
class DctermsCreatedType < Lutaml::Model::Type::DateTime
xml_namespace DctermsNamespace
end
class Vcard < Lutaml::Model::Serializable
attribute :title, DcTitleType
attribute :created, DctermsCreatedType
xml do
element "vCard"
namespace VcardNamespace
namespace_scope [VcardNamespace, DcNamespace, DctermsNamespace] (1)
map_element "title", to: :title
map_element "created", to: :created
end
end
vcard = Vcard.new(
title: "Dr. John Doe",
created: DateTime.parse("2024-06-01T12:00:00Z")
)
puts vcard.to_xml| 1 | Consolidate all three namespaces at root element (default :auto mode) |
Output with default namespace (no prefix option):
<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/">
<dc:title>Dr. John Doe</dc:title>
<dcterms:created>2024-06-01T12:00:00+00:00</dcterms:created>
</vCard>Without namespace_scope, each namespace would be declared locally:
<vCard xmlns="urn:ietf:params:xml:ns:vcard-4.0">
<dc:title xmlns:dc="http://purl.org/dc/elements/1.1/">Dr. John Doe</dc:title>
<dcterms:created xmlns:dcterms="http://purl.org/dc/terms/">2024-06-01T12:00:00+00:00</dcterms:created>
</vCard>Forcing unused namespace declarations
Use declare: :always to force namespace declarations even when not used:
declare: :alwaysclass AppNamespace < Lutaml::Model::XmlNamespace
uri 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties'
prefix_default 'app'
end
class VtNamespace < Lutaml::Model::XmlNamespace
uri 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'
prefix_default 'vt'
end
class Properties < Lutaml::Model::Serializable
attribute :template, :string
xml do
element "Properties"
namespace AppNamespace
# Force vt namespace declaration even though unused
namespace_scope [
{ namespace: VtNamespace, declare: :always }
]
map_element "Template", to: :template
end
end
props = Properties.new(template: "Normal.dotm")
puts props.to_xmlOutput:
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
<Template>Normal.dotm</Template>
</Properties> The vt: namespace is declared even though no elements use it. This is required by some XML consumers like Office Open XML. |
Per-namespace declaration control
Use Hash format for fine-grained control over individual namespaces:
class CorePropertiesNamespace < Lutaml::Model::XmlNamespace
uri 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties'
prefix_default 'cp'
end
class DcNamespace < Lutaml::Model::XmlNamespace
uri 'http://purl.org/dc/elements/1.1/'
prefix_default 'dc'
end
class XsiNamespace < Lutaml::Model::XmlNamespace
uri 'http://www.w3.org/2001/XMLSchema-instance'
prefix_default 'xsi'
end
class CoreProperties < Lutaml::Model::Serializable
attribute :title, :string
xml do
element "coreProperties"
namespace CorePropertiesNamespace
# Per-namespace declaration control
namespace_scope [
{ namespace: DcNamespace, declare: :auto }, (1)
{ namespace: XsiNamespace, declare: :always } (2)
]
map_element "title", to: :title
end
end| 1 | DcNamespace declared only if used (:auto mode) |
| 2 | XsiNamespace always declared even if unused (:always mode) |
When DcNamespace is used:
<coreProperties xmlns="http://schemas.openxmlformats.org/package/2006/metadata/core-properties"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dc:title>Document Title</dc:title>
</coreProperties>When DcNamespace is not used (title remains unset or uses different namespace):
<coreProperties xmlns="http://schemas.openxmlformats.org/package/2006/metadata/core-properties"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
</coreProperties> XsiNamespace is always declared (:always), while DcNamespace is omitted when unused (:auto). |
Declaration modes
| Mode | Behavior |
|---|---|
| Namespace declared only if actually used in elements or attributes |
| Namespace always declared at root, even if unused. Use for schema compliance or when external tools require namespace presence. |
| Namespace never declared. Error raised if namespace is used in elements. Reserved for future use. |
Use cases for declaration modes
Use :auto mode (default) when:
-
Standard W3C namespace behavior desired
-
Minimize unnecessary namespace declarations
-
Generate clean, minimal XML
-
Examples: Most XML documents, APIs
Use :always mode when:
-
Schema requires specific namespace declarations
-
External tools validate namespace presence
-
Format specifications mandate unused namespaces
-
Examples: Office Open XML (xmlns:vt required), SOAP envelopes
Use :never mode when:
-
Explicitly prevent namespace usage
-
Catch errors during development
-
Reserved for future extensibility
Form override (W3C Schema compliance)
Per W3C XML Schema specification, the form attribute controls whether elements must be qualified (prefixed) or unqualified (no prefix) on a per-element basis.
When to use form override
Use form when:
-
Schema specifies
elementFormDefault="qualified"but some elements should be local -
Schema specifies
elementFormDefault="unqualified"but some elements need qualification -
Mixing qualified and unqualified elements in same parent
Syntax
xml do
map_element 'name', to: :attr, form: :qualified # Force prefix
map_element 'name', to: :attr, form: :unqualified # Force no prefix
endExample: Local elements in qualified schema
class ReportNamespace < Lutaml::Model::XmlNamespace
uri "http://example.com/reports"
prefix_default "r"
element_form_default :qualified # All elements qualified by default
end
class Report < Lutaml::Model::Serializable
attribute :title, :string
attribute :metadata, :string
xml do
element "report"
namespace ReportNamespace
map_element "title", to: :title # Qualified (follows default)
map_element "metadata", to: :metadata, form: :unqualified # Local override
end
end
report = Report.new(title: "Annual Report", metadata: "Internal use")
puts report.to_xml(prefix: true)Output:
<r:report xmlns:r="http://example.com/reports">
<r:title>Annual Report</r:title>
<metadata>Internal use</metadata> <!-- No prefix, local scope -->
</r:report> The metadata element is unqualified despite element_form_default: :qualified. The form: :unqualified override forces local scope. |
Priority order
Form takes highest priority in qualification decisions:
-
form: :unqualified→ Forces NO prefix (local scope) -
form: :qualified→ Forces prefix usage -
namespace: :inherit→ Uses parent namespace -
Type namespace → Uses type’s namespace
-
Schema
elementFormDefault→ Follows default -
Unqualified → No namespace
XSD annotation (documentation method)
The documentation method adds human-readable description for XSD generation.
Syntax:
xml do
element 'element-name'
documentation 'Description text for XSD annotation'
endUsed in generated XSD <xs:annotation>…<xs:documentation> elements.
class Product < Lutaml::Model::Serializable
attribute :name, :string
attribute :price, :float
xml do
element 'product'
documentation "Represents a product in the catalog"
map_element 'name', to: :name
map_element 'price', to: :price
end
end
# When generating XSD from this model:
# <xs:complexType name="ProductType">
# <xs:annotation>
# <xs:documentation>Represents a product in the catalog</xs:documentation>
# </xs:annotation>
# ...
# </xs:complexType> XSD type name declaration (type_name method)
The type_name method sets an explicit type name for XSD generation.
By default, type names are inferred as {ClassName}Type. This method is only to override that value.
This method is only useful for customizing a generated XSD schema file from the models, the XSD type name does not affect XML processing or the functioning of the resulting XSD schema.
Customizing the type name is typically used to make the XSD compatible with external schemas that define or reuse the same XSD type.
Syntax:
xml do
element 'element-name'
type_name 'CustomTypeName'
endclass Product < Lutaml::Model::Serializable
attribute :name, :string
xml do
element 'product'
type_name 'CatalogItemType' # Override default 'ProductType'
map_element 'name', to: :name
end
end
# Generated XSD uses: <xs:complexType name="CatalogItemType"> XSD sequence requirements (sequence block)
Lutaml::Model supports the XSD notions of declaring structural requirements on XML elements and types.
Usage of these declarations allows for precise control over the structure and validation of XML documents. These notions directly translate to the XSD equivalents and is reflected in the corresponding XSD generated by Lutaml::Model as XML complex types.
The sequence directive specifies that the defined attributes must appear in a specified order in XML.
Sequence only supports map_element mappings. |
Syntax:
xml do
sequence do
map_element 'xml_element_name_1', to: :name_of_attribute_1
map_element 'xml_element_name_2', to: :name_of_attribute_2
# Add more map_element lines as needed to establish a complete sequence
end
endThe appearance of the elements in the XML document must match the order defined in the sequence block. In this case, the <xml_element_name_1> element should appear before the <xml_element_name_2> element.
sequence keyword to define a set of elements in desired order.class Kiln < Lutaml::Model::Serializable
attribute :id, :string
attribute :name, :string
attribute :type, :string
attribute :color, :string
xml do
sequence do
map_element :id, to: :id
map_element :name, to: :name
map_element :type, to: :type
map_element :color, to: :color
end
end
end
class KilnCollection < Lutaml::Model::Serializable
attribute :kiln, Kiln, collection: 1..2
xml do
element "collection"
map_element "kiln", to: :kiln
end
end<collection>
<kiln>
<id>1</id>
<name>Nick</name>
<type>Hard</type>
<color>Black</color>
</kiln>
<kiln>
<id>2</id>
<name>John</name>
<type>Soft</type>
<color>White</color>> parsed = Kiln.from_xml(xml)
# => [
#<Kiln:0x0000000104ac7240 @id="1", @name="Nick", @type="Hard", @color="Black">,
#<Kiln:0x0000000104ac7240 @id="2", @name="John", @type="Soft", @color="White">
#]
> bad_xml = <<~HERE
<collection>
<kiln>
<name>Nick</name>
<id>1</id>
<color>Black</color>
<type>Hard</type>
</kiln>
</collection>
HERE
> parsed = Kiln.from_xml(bad_xml)
# => Lutaml::Model::ValidationError: Element 'name' is out of order in 'kiln' element For importing model mappings inside a sequence block, refer to Importing model mappings inside a sequence. |
Automatic support of xsi:schemaLocation
The W3C "XMLSchema-instance" namespace describes a number of attributes that can be used to control the behavior of XML processors. One of these attributes is xsi:schemaLocation.
The xsi:schemaLocation attribute locates schemas for elements and attributes that are in a specified namespace. Its value consists of pairs of a namespace URI followed by a relative or absolute URL where the schema for that namespace can be found.
Usage of xsi:schemaLocation in an XML element depends on the declaration of the XML namespace of xsi, i.e. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance". Without this namespace LutaML will not be able to serialize the xsi:schemaLocation attribute.
| It is most commonly attached to the root element but can appear further down the tree. |
The following snippet shows how xsi:schemaLocation is used in an XML document:
<cera:Ceramicın
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cera="http://example.com/ceramic"
xmlns:clr='http://example.com/color'
xsi:schemaLocation=
"http://example.com/ceramic http://example.com/ceramic.xsd
http://example.com/color http://example.com/color.xsd"
clr:color="navy-blue">
<cera:Type>Porcelain</cera:Type>
<Glaze>Clear</Glaze>
</cera:Ceramicın>LutaML::Model supports the xsi:schemaLocation attribute in all XML serializations by default, through the schema_location attribute on the model instance object.
xsi:schemaLocation attribute in XML serializationIn this example, the xsi:schemaLocation attribute will be automatically supplied without the explicit need to define in the model, and allows for round-trip serialization.
class ColorXmlNamespace < Lutaml::Model::XmlNamespace
uri 'http://example.com/color'
default_prefix 'clr'
end
class Ceramic < Lutaml::Model::Serializable
attribute :type, :string
attribute :glaze, :string
attribute :color, :string
xml do
element 'Ceramic'
namespace 'http://example.com/ceramic', 'cera'
map_element 'Type', to: :type # Inherits parent namespace
map_element 'Glaze', to: :glaze
map_attribute 'color', to: :color # Per W3C, attributes don't inherit namespace
end
end
xml_content = <<~HERE
<cera:Ceramic
xmlns:cera="http://example.com/ceramic"
xmlns:clr="http://example.com/color"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
clr:color="navy-blue"
xsi:schemaLocation="
http://example.com/ceramic http://example.com/ceramic.xsd
http://example.com/color http://example.com/color.xsd
">
<cera:Type>Porcelain</cera:Type>
<Glaze>Clear</Glaze>
</cera:Ceramic>
HERE> c = Ceramic.from_xml(xml_content)
=>
#<Ceramic:0x00000001222bdd60
...
> schema_loc = c.schema_location
#<Lutaml::Model::SchemaLocation:0x0000000122773760
...
> schema_loc
=>
#<Lutaml::Model::SchemaLocation:0x0000000122773760
@namespace="http://www.w3.org/2001/XMLSchema-instance",
@original_schema_location="http://example.com/ceramic http://example.com/ceramic.xsd http://example.com/color http://example.com/color.xsd",
@prefix="xsi",
@schema_location=
[#<Lutaml::Model::Location:0x00000001222bd018 @location="http://example.com/ceramic.xsd", @namespace="http://example.com/ceramic">,
#<Lutaml::Model::Location:0x00000001222bcfc8 @location="http://example.com/color.xsd", @namespace="http://example.com/color">]>
> new_c = Ceramic.new(type: "Porcelain", glaze: "Clear", color: "navy-blue", schema_location: schema_loc).to_xml
> puts new_c
# <cera:Ceramic
# xmlns:cera="http://example.com/ceramic"
# xmlns:clr="http://example.com/color"
# xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
# clr:color="navy-blue"
# xsi:schemaLocation="
# http://example.com/ceramic http://example.com/ceramic.xsd
# http://example.com/color http://example.com/color.xsd
# ">
# <cera:Type>Porcelain</cera:Type>
# <cera:Glaze>Clear</cera:Glaze>
# </cera:Ceramic> For details on xsi:schemaLocation, please refer to the W3C XML standard. |
Custom namespace prefixes
LutaML Model allows you to customize the namespace prefix used during XML serialization through the prefix option on to_xml.
Usage
The prefix option accepts three types of values:
- String
-
Use a custom prefix for serialization
- Boolean
true -
Use the namespace’s configured
prefix_default - Boolean
false -
Use default namespace format (
xmlns="…", no prefix)
class Document < Lutaml::Model::Serializable
attribute :title, :string
xml do
namespace DocumentNamespace # has prefix_default "doc"
element "Document"
map_element "title", to: :title
end
end
doc = Document.new(title: "Example")
# Default format
doc.to_xml
# => <Document xmlns="http://example.com">...</Document>
# Use namespace's default prefix
doc.to_xml(prefix: true)
# => <doc:Document xmlns:doc="http://example.com">...</doc:Document>
# Use custom prefix
doc.to_xml(prefix: "d")
# => <d:Document xmlns:d="http://example.com">...</d:Document>Prefix inheritance
Custom prefixes are automatically inherited by child elements in the same namespace:
parent.to_xml(prefix: "custom")
# All elements in same namespace use "custom:" prefixNamespace identity vs presentation
The prefix option controls presentation (default vs prefix format), not namespace identity (which URI). The same namespace URI with different prefixes is still the same namespace semantically. |
xml do
namespace 'http://example.com/namespace'
endxml do
namespace 'http://example.com/namespace', 'prefix'
end