Table of Contents

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).

Example 1. Parsing XML with different prefix formats
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:

Example 2. Nested models with arbitrary prefixes
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:

Example 3. Type namespace with arbitrary prefixes
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:

Example 4. Round-trip with different formats
# 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:

Example 5. Default format preservation
# 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>'
Example 6. Prefix format preservation
# 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:

  1. Phase 1: Input Format Capture - During XML parsing, namespace declarations are extracted with their format (:default or :prefix)

  2. Phase 2: Metadata Storage - Format information is stored on the model instance as @__input_namespaces

  3. Phase 3: Declaration Planning - The DeclarationPlanner receives input namespaces and matches by URI to preserve format

  4. 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:

  1. Explicit prefix option: model.to_xml(prefix: true/false)

  2. Namespace configuration: prefix_default in XmlNamespace class

  3. W3C rules: Attributes in element’s namespace require prefix format

  4. Default: Prefer default format (cleaner output)

Format Priority

When multiple format sources exist, priority is:

  1. Input format (from parsed XML) - Highest priority, always preserved

  2. Explicit option (prefix: true/false)

  3. W3C compliance (e.g., attributes require prefix)

  4. Namespace configuration (prefix_default)

  5. 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.

Example 7. Using child model class for elements in blank namespace
# 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
end

When 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:

Table 1. Namespace priority (highest to lowest)
Priority Source Example

1 (HIGHEST)

Model-level namespace

attribute :attr, ChildModel where ChildModel has namespace

2

Type level

attribute :attr, CustomType where CustomType has xml_namespace

3 (LOWEST)

Context level (schema defaults)

element_form_default: :qualified in parent namespace

Example 8. Priority in action
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
end

Result 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>

Implementation note

This prefix-independent behavior is achieved through namespace URI matching during XML parsing. The implementation compares namespace URIs (not prefix strings) when matching elements to model attributes, ensuring W3C compliance and maximum interoperability.

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 xml is 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.

Example 9. Using W3C xml namespace attributes
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
    element "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_xml

Output:

<doc xml:lang="en" xml:space="preserve">  Text  </doc>
No xmlns:xml declaration appears - the xml namespace is implicitly bound per W3C specification.
Table 2. Common xml namespace attributes
Attribute Purpose

xml:lang

Specifies the natural language of content (RFC 5646 language tags)

xml:space

Controls whitespace handling (default or preserve)

xml:id

Provides unique identifiers (W3C XML ID)

xml:base

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.

Table 3. Available W3C XML attribute types
Type Class Symbol Purpose Validation

Lutaml::Model::Xml::W3c::XmlLangType

:xml_lang

Language identification (RFC 5646)

Accepts any valid language tag

Lutaml::Model::Xml::W3c::XmlSpaceType

:xml_space

Whitespace handling control

Only "default" or "preserve"

Lutaml::Model::Xml::W3c::XmlBaseType

:xml_base

Base URI for relative references

Accepts any valid URI string

Lutaml::Model::Xml::W3c::XmlIdType

:xml_id

Unique identifier (XML ID type)

Must be valid NCName

W3C XSI namespace types

The XSI namespace (xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance") is used for schema validation hints.

Table 4. Available W3C XSI attribute types
Type Class Symbol Purpose Validation

Lutaml::Model::Xml::W3c::XsiType

:xsi_type

Element type annotation (QName)

Accepts any QName string

Lutaml::Model::Xml::W3c::XsiNil

:xsi_nil

Nil indicator

Only "true" or "false"

Lutaml::Model::Xml::W3c::XsiSchemaLocationType

:xsi_schema_location

Schema location pairs

Whitespace-separated URI pairs

Lutaml::Model::Xml::W3c::XsiNoNamespaceSchemaLocationType

:xsi_no_namespace_schema_location

Schema location without namespace

Single URI

Example 10. Using W3C XSI attribute types
class Item < Lutaml::Model::Serializable
  attribute :nil_attr, :xsi_nil
  attribute :type_attr, :xsi_type

  xml do
    element "Item"
  end
end

item = Item.new(nil_attr: "true", type_attr: "string")
puts item.to_xml
# => <Item xsi:nil="true" xsi:type="string"
#        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>

# Invalid xsi:nil value raises error
Item.new(nil_attr: "maybe")
# => ArgumentError: xsi:nil must be 'true' or 'false'

The XLink namespace (xmlns:xlink="http://www.w3.org/1999/xlink") is used for hyperlinks and references.

Table 5. Available W3C XLink attribute types
Type Class Symbol Purpose Validation

Lutaml::Model::Xml::W3c::XlinkHrefType

:xlink_href

Link target URI

Any valid URI

Lutaml::Model::Xml::W3c::XlinkTypeAttrType

:xlink_type

Link type

"simple", "extended", "locator", "arc", "resource", "title"

Lutaml::Model::Xml::W3c::XlinkRoleType

:xlink_role

Role URI

Any valid URI

Lutaml::Model::Xml::W3c::XlinkArcroleType

:xlink_arcrole

Arc role URI

Any valid URI

Lutaml::Model::Xml::W3c::XlinkTitleType

:xlink_title

Human-readable title

Any string

Lutaml::Model::Xml::W3c::XlinkShowType

:xlink_show

Display behavior

"new", "replace", "embed", "other", "none"

Lutaml::Model::Xml::W3c::XlinkActuateType

:xlink_actuate

Timing

"onLoad", "onRequest", "other", "none"

Example 11. Using W3C XLink attribute types
class Link < Lutaml::Model::Serializable
  attribute :href, :xlink_href
  attribute :title, :xlink_title
  attribute :show, :xlink_show
  attribute :actuate, :xlink_actuate

  xml do
    element "Link"
  end
end

link = Link.new(
  href: "http://example.com",
  title: "Example",
  show: "new",
  actuate: "onRequest"
)

puts link.to_xml
# => <Link xlink:href="http://example.com" xlink:title="Example"
#          xlink:show="new" xlink:actuate="onRequest"
#          xmlns:xlink="http://www.w3.org/1999/xlink"/>

# Invalid xlink:show value raises error
Link.new(show: "invalid")
# => ArgumentError: xlink:show must be one of: new, replace, embed, other, none

Symbol-based access for W3C types

All W3C types are registered with Lutaml::Model::Type.register, enabling convenient symbol-based attribute access:

# Instead of:
attribute :lang, Lutaml::Model::Xml::W3c::XmlLangType

# You can use:
attribute :lang, :xml_lang

# All symbols map to their corresponding type:
# :xml_lang       => XmlLangType
# :xml_space      => XmlSpaceType
# :xml_base       => XmlBaseType
# :xml_id         => XmlIdType
# :xsi_type       => XsiType
# :xsi_nil        => XsiNil
# :xsi_schema_location => XsiSchemaLocationType
# :xlink_href     => XlinkHrefType
# :xlink_type     => XlinkTypeAttrType
# :xlink_role     => XlinkRoleType
# :xlink_arcrole  => XlinkArcroleType
# :xlink_title    => XlinkTitleType
# :xlink_show     => XlinkShowType
# :xlink_actuate  => XlinkActuateType

W3C reserved namespace warning

When defining a namespace, if you use a W3C-reserved URI or prefix, Lutaml::Model will issue a warning:

# WARNING: Defining namespace with W3C-reserved URI/prefix
class BadNamespace < Lutaml::Model::Xml::Namespace
  uri "http://www.w3.org/XML/1998/namespace"  # W3C-reserved URI
  prefix_default "xsi"  # W3C-reserved prefix
end

ns = BadNamespace.new
# => [WARNING] Defining a namespace with W3C-reserved URI 'http://www.w3.org/XML/1998/namespace'.
#    Use Lutaml::Model::Xml::W3c::XmlNamespace for xml: attributes...

This helps ensure you use the official Lutaml-provided W3C namespace classes instead of redefining them.

Example 12. Using W3C XML attribute types with validation
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
    element "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_xml

Output:

<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 generated

This 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:

Example 13. Unqualified elements and attributes by default
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)
Example 14. Qualified attributes force prefixes
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.

Example 15. Different element and attribute namespaces
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
end

DSL 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.

Example 16. Setting namespace URI
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"
uri_aliases

Declares additional URI strings that should resolve to this namespace class. This enables a single namespace class to accept multiple URI variants during parsing and preserves the original URI during serialization.

Syntax:

uri_aliases 'alias-uri-1', 'alias-uri-2', ...

Use this when a schema uses multiple URI forms for the same namespace (e.g., a schema with trailing slash variant like http://example.com/ns/ alongside the canonical http://example.com/ns/schema.xsd).

Validating URI aliases

All alias URIs must be non-empty strings. Attempting to set an invalid alias (like a number or empty string) raises an ArgumentError.

Example 17. Setting URI aliases
class ReqIfNamespace < Lutaml::Model::XmlNamespace
  uri "http://www.omg.org/spec/ReqIF/20110401/reqif.xsd"
  uri_aliases "http://www.omg.org/spec/ReqIF/20110401/"
  prefix_default "reqif"
end

# During parsing, both URIs resolve to ReqIfNamespace:
# - "http://www.omg.org/spec/ReqIF/20110401/reqif.xsd" (canonical)
# - "http://www.omg.org/spec/ReqIF/20110401/" (alias)

# The namespace class methods:
ReqIfNamespace.uri
# => "http://www.omg.org/spec/ReqIF/20110401/reqif.xsd"

ReqIfNamespace.uri_aliases
# => ["http://www.omg.org/spec/ReqIF/20110401/"]

ReqIfNamespace.all_uris
# => ["http://www.omg.org/spec/ReqIF/20110401/reqif.xsd",
#     "http://www.omg.org/spec/ReqIF/20110401/"]

ReqIfNamespace.is_alias?("http://www.omg.org/spec/ReqIF/20110401/")
# => true
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.

Example 18. Setting schema location
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.

Example 19. Setting default prefix
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)

Example 20. Setting element qualification 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.
Example 21. Setting attribute qualification default
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.

Example 22. Importing 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.

Example 23. Including schema files
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'
Example 24. Setting schema version
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'
Example 25. Adding documentation
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

Example 26. Fully configured XML namespace
# 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:

Setting namespace of the XML element or XML type using a XmlNamespace
# Assume we have defined an XmlNamespace class called ExampleXmlNamespace

xml do
  namespace ExampleXmlNamespace
end
Example 27. Using the namespace method to set namespace for an element
class 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.

Example 28. Default namespace with additional prefixed namespaces
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 prefixed

Output:

<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.

Example 29. Overriding namespace prefix
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
Example 30. Namespace consolidation with vCard (using default :auto mode)
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:

Example 31. Always declare namespace with declare: :always
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 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_xml

Output:

<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:

Example 32. Mixed declaration modes with per-namespace control
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

:auto (default)

Namespace declared only if actually used in elements or attributes

:always

Namespace always declared at root, even if unused. Use for schema compliance or when external tools require namespace presence.

:never

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
end
Example: Local elements in qualified schema
Example 33. Overriding element qualification with form
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:

  1. form: :unqualified → Forces NO prefix (local scope)

  2. form: :qualified → Forces prefix usage

  3. namespace: :inherit → Uses parent namespace

  4. Type namespace → Uses type’s namespace

  5. Schema elementFormDefault → Follows default

  6. 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'
end

Used in generated XSD <xs:annotation>…​<xs:documentation> elements.

Example 34. Adding documentation to model
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'
end
Example 35. Setting custom type name
class 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
end

The 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.

Example 36. Using the 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.

Example 37. Retrieving and setting the xsi:schemaLocation attribute in XML serialization

In 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.

Namespace Aliases (URI Aliases)

Some XML schemas use multiple URI variants for the same logical namespace. For example, ReqIF schemas commonly use both http://www.omg.org/spec/ReqIF/20110401/reqif.xsd and http://www.omg.org/spec/ReqIF/20110401/ as namespace URIs. Lutaml::Model supports this through the uri_aliases DSL method.

Defining URI Aliases

Use uri_aliases to declare additional URIs that should resolve to the same namespace class:

class ReqIfNamespace < Lutaml::Model::XmlNamespace
  uri "http://www.omg.org/spec/ReqIF/20110401/reqif.xsd"
  uri_aliases "http://www.omg.org/spec/ReqIF/20110401/"
  prefix_default "reqif"
end

The uri_aliases method accepts multiple alias URIs:

class MyNamespace < Lutaml::Model::XmlNamespace
  uri "http://example.com/schema/main.xsd"
  uri_aliases "http://example.com/schema/", "http://example.com/schema"
  prefix_default "ex"
end

How URI Aliases Work

When parsing XML, Lutaml::Model accepts either the canonical URI or any alias URI. When serializing, the original URI from the input XML is preserved for round-trip fidelity.

Method Description

uri

The canonical namespace URI (required)

uri_aliases

Additional URIs that resolve to this namespace class

is_alias?(uri)

Returns true if the URI is an alias of this namespace

all_uris

Returns array of all URIs (canonical + aliases)

Parsing with Aliases

XML with either canonical or alias URIs parses successfully:

# Parse with canonical URI
xml1 = '<Item xmlns="http://example.com/schema/main.xsd"><Name>Item1</Name></Item>'
model1 = Item.from_xml(xml1)

# Parse with alias URI
xml2 = '<Item xmlns="http://example.com/schema/"><Name>Item2</Name></Item>'
model2 = Item.from_xml(xml2)

Both parse successfully because both URIs resolve to the same namespace class.

Round-Trip Preservation

When you parse XML with an alias URI and serialize it back, the original alias URI is preserved:

xml = '<Item xmlns="http://example.com/schema/"><Name>Item1</Name></Item>'
model = Item.from_xml(xml)

# Serialization preserves the alias URI from the original XML
model.to_xml
# => <Item xmlns="http://example.com/schema/"><Name>Item1</Name></Item>

If you create a new model instance programmatically (without parsing from XML), the canonical URI is used:

model = Item.new(name: "New Item")
model.to_xml
# => <Item xmlns="http://example.com/schema/main.xsd"><Name>New Item</Name></Item>

Mixed Content with Aliases

When a parent element uses one URI variant and a child element uses another variant for the same namespace class, both are correctly preserved during round-trip:

xml = <<~XML
  <p:Parent xmlns:p="http://example.com/parent/">
    <c:childName xmlns:c="http://example.com/child/">mixed content</c:childName>
  </p:Parent>
XML

model = Parent.from_xml(xml)
model.to_xml  # Preserves original URIs
# => <p:Parent xmlns:p="http://example.com/parent/">
#      <c:childName xmlns:c="http://example.com/child/">mixed content</c:childName>
#    </p:Parent>

Doubly-Defined Namespace Prefixes

In some XML documents, the same namespace URI appears with multiple different prefixes. For example, an element might be declared with both xmlns:a="http://example.com/items" and xmlns:b="http://example.com/items". Lutaml::Model automatically handles this through the format preservation system.

How It Works

Lutaml::Model tracks the specific prefix used for each element during parsing. When serializing, it preserves:

  1. The original prefix format (default or prefixed) from the input XML

  2. For doubly-defined namespaces, the specific prefix variant used at each element

# Input XML with doubly-defined prefixes
xml = <<~XML
  <root xmlns:a="http://example.com/items" xmlns:b="http://example.com/items">
    <a:item>using a</a:item>
    <b:item>using b</b:item>
  </root>
XML

model = ItemContainer.from_xml(xml)
output = model.to_xml

# Output preserves both prefix declarations
# => <root xmlns:a="http://example.com/items" xmlns:b="http://example.com/items">
#      <a:item>using a</a:item>
#      <b:item>using b</b:item>
#    </root>

Prefix Independence

The parsing side is already prefix-independent (per W3C spec) - elements are matched by namespace URI, not prefix. So <a:item> and <b:item> both correctly map to the same attribute regardless of which prefix was used in the input.

Format Priority for Doubly-Defined Namespaces

When the same namespace URI appears with multiple prefix variants, the serialization uses the same prefix variant that was present in the original input:

# Elements parsed with prefix "a" serialize with prefix "a"
# Elements parsed with prefix "b" serialize with prefix "b"
# Elements parsed with default namespace serialize with default namespace

This behavior is automatic and transparent - you don’t need to configure anything in your model definitions. The system tracks which prefix was used for each element during parsing and preserves it during serialization.

Invalid Namespace Handling (Formal Public Identifiers)

The Problem

Some legacy XML documents use Formal Public Identifiers (FPIs) in namespace positions. FPIs are SGML-era identifiers like -//OASIS//DTD XML Exchange Table Model 19990315//EN used in DOCTYPE declarations, not valid XML namespace URIs.

Using FPIs as namespace URIs is invalid according to W3C XML Namespaces specification. XML namespaces must be valid URIs - FPIs are not valid URIs and cannot be used in xmlns: declarations.

What Happens

When you parse XML that has an FPI in a namespace position, the parsing will succeed because the underlying XML parser can store the value. However, when serializing back to XML, the underlying parser (Nokogiri, Oga, Ox, REXML) will reject the FPI as an invalid namespace URI:

Canon::Error: Relative namespace URI not allowed: -//OASIS//DTD XML Exchange Table Model 19990315//EN

How Lutaml::Model Handles This

Lutaml::Model automatically converts FPIs to valid URNs per RFC 3151 during serialization. This allows you to parse and serialize XML that contains FPI namespaces, even though the original XML is technically invalid.

FPI to URN Conversion

FPIs are converted to urn:publicid: URNs following RFC 3151:

FPI:  -//OASIS//DTD XML Exchange Table Model 19990315//EN
URN:  urn:publicid:-//OASIS//DTD+XML+Exchange+Table+Model+19990315//EN

Conversion rules:

  1. Replace spaces with +

  2. Replace // with :/ in the FPI body (after the leading -// or +//)

  3. Prepend urn:publicid:

Example

# XML with FPI namespace (invalid but parseable)
xml = <<~XML
  <table xmlns="-//OASIS//DTD XML Exchange Table Model 19990315//EN">
    <entry>Data</entry>
  </table>
XML

# Parsing succeeds
model = Table.from_xml(xml)

# Serialization converts FPI to URN
model.to_xml
# => <table xmlns="urn:publicid:-//OASIS//DTD+XML+Exchange+Table+Model+19990315//EN">
#      <entry>Data</entry>
#    </table>

Round-Trip Is Not Possible

Since FPIs in namespace positions are invalid XML, true round-trip (input = output) is not possible. The output will always use the URN form because:

  1. The original FPI is not a valid namespace URI

  2. XML parsers reject FPIs during serialization

  3. The URN form is the valid representation of the FPI

This is expected behavior - the library converts invalid input to valid output.

DOCTYPE PUBLIC Identifiers Are Separate

Note that FPI handling is separate from DOCTYPE PUBLIC identifiers. The PUBLIC identifier in a DOCTYPE declaration correctly uses FPIs and is properly preserved during round-trip:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

The FPI -//W3C//DTD XHTML 1.0 Transitional//EN belongs in the DOCTYPE PUBLIC field, not in namespace declarations. Lutaml::Model correctly handles DOCTYPE preservation separately from namespace handling.

Deprecated ways of working with namespaces

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:" prefix

Namespace 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.
(DEPRECATED) Setting default namespace at the root element
xml do
  namespace 'http://example.com/namespace'
end
(DEPRECATED) Setting a prefixed namespace at the root element
xml do
  namespace 'http://example.com/namespace', 'prefix'
end

Table of contents