Introduction

XML namespaces help avoid element name conflicts and organize elements from different schemas. This tutorial introduces namespace basics in Lutaml::Model.

What are XML namespaces?

An XML namespace is a URI that uniquely identifies a set of element and attribute names. Namespaces prevent naming conflicts when combining XML from different sources.

<ceramic xmlns="http://example.com/ceramic">
  <type>Porcelain</type>
</ceramic>

The xmlns declares the default namespace for this element and its children.

Creating a namespace class

In Lutaml::Model, define namespaces using XmlNamespace classes:

Syntax:

class MyNamespace < Lutaml::Model::XmlNamespace
  uri 'namespace-uri'
  prefix_default 'prefix'
end
Example 1. Defining a Ceramic namespace
class CeramicNamespace < Lutaml::Model::XmlNamespace
  uri 'https://example.com/schemas/ceramic/v1'
  prefix_default 'cer'
end

Using namespaces in models

Assign namespaces to your models using the namespace method:

Example 2. Model with namespace
class CeramicNamespace < Lutaml::Model::XmlNamespace
  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 CeramicNamespace

    map_element 'Type', to: :type
    map_element 'Glaze', to: :glaze
  end
end

ceramic = Ceramic.new(type: "Porcelain", glaze: "Clear")
puts ceramic.to_xml

Output (default namespace):

<Ceramic xmlns='http://example.com/ceramic'>
  <Type>Porcelain</Type>
  <Glaze>Clear</Glaze>
</Ceramic>

Using prefixed namespaces

To use a prefix instead of default namespace, use prefix: true:

Example 3. Prefixed namespace output
puts ceramic.to_xml(prefix: true)

Output:

<cer:Ceramic xmlns:cer='http://example.com/ceramic'>
  <cer:Type>Porcelain</cer:Type>
  <cer:Glaze>Clear</cer:Glaze>
</cer:Ceramic>

Multiple namespaces

Real-world XML often uses multiple namespaces:

Example 4. Multi-namespace example
class CeramicNamespace < Lutaml::Model::XmlNamespace
  uri 'https://example.com/ceramic'
  prefix_default 'cer'
end

class GlazeNamespace < Lutaml::Model::XmlNamespace
  uri 'https://example.com/glaze'
  prefix_default 'glz'
end

class Ceramic < Lutaml::Model::Serializable
  attribute :type, :string
  attribute :glaze, :string

  xml do
    element 'ceramic'
    namespace CeramicNamespace

    map_element 'type', to: :type
    map_element 'glaze', to: :glaze  # Uses Glaze's type namespace
  end
end

puts Ceramic.new(type: "Porcelain", glaze: "Celadon").to_xml(prefix: true)

Output:

<cer:ceramic xmlns:cer="https://example.com/ceramic"
             xmlns:glz="https://example.com/glaze">
  <cer:type>Porcelain</cer:type>
  <glz:glaze>Celadon</glz:glaze>
</cer:ceramic>

Form Defaults: elementFormDefault and attributeFormDefault

XSD schemas support elementFormDefault and attributeFormDefault to control whether local elements and attributes must be namespace-qualified.

element_form_default

Controls whether local elements (declared inside complexType) must be namespace-qualified in instance documents:

class MyNamespace < Lutaml::Model::XmlNamespace
  uri 'http://example.com/ceramic'
  prefix_default 'cer'

  # Local elements will be namespace-qualified
  element_form_default :qualified
end

With element_form_default :qualified, local elements appear with namespace prefix:

<cer:Ceramic xmlns:cer="http://example.com/ceramic">
  <cer:type>Porcelain</cer:type>  <!-- Prefixed because elementFormDefault="qualified" -->
</cer:Ceramic>

With element_form_default :unqualified (the W3C default), local elements appear without prefix:

<cer:Ceramic xmlns:cer="http://example.com/ceramic">
  <type>Porcelain</type>  <!-- No prefix because elementFormDefault="unqualified" -->
</cer:Ceramic>

attribute_form_default

Controls whether local attributes must be namespace-qualified:

class MyNamespace < Lutaml::Model::XmlNamespace
  uri 'http://example.com/ceramic'
  prefix_default 'cer'
  attribute_form_default :qualified  # Local attributes will be prefixed
end

With attribute_form_default :qualified, local attributes appear with namespace prefix:

<cer:Ceramic xmlns:cer="http://example.com/ceramic" cer:id="123">
  <cer:type>Porcelain</cer:type>
</cer:Ceramic>

With attribute_form_default :unqualified (the W3C default), local attributes appear without prefix:

<cer:Ceramic xmlns:cer="http://example.com/ceramic" id="123">
  <cer:type>Porcelain</cer:type>
</cer:Ceramic>

Overriding Form Defaults for Individual Elements/Attributes

You can override the schema-level default for individual elements or attributes using the form option:

class MixedForms < Lutaml::Model::Serializable
  attribute :qualified_content, :string
  attribute :unqualified_content, :string

  xml do
    element "mixedForms"
    namespace MyNamespace  # elementFormDefault: :qualified

    # Override: unqualified despite schema default
    map_element "unqualified_content", to: :unqualified_content, form: :unqualified

    # Default: qualified (matches element_form_default)
    map_element "qualified_content", to: :qualified_content
  end
end

Using form Option with Attributes

class MixedAttrForms < Lutaml::Model::Serializable
  attribute :qualified_id, :string
  attribute :unqualified_id, :string

  xml do
    element "mixedAttrForms"
    namespace MyNamespace  # attributeFormDefault: :qualified

    # Override: unqualified despite schema default
    map_attribute "unqualified_id", to: :unqualified_id, form: :unqualified

    # Default: qualified (matches attribute_form_default)
    map_attribute "qualified_id", to: :qualified_id
  end
end

For more details on XSD form defaults and how they affect schema generation, see XSD Generation with Namespace Support.

Common pitfalls

Forgetting namespace declarations

Each namespace must be declared (explicitly or automatically):

# ❌ Wrong - no namespace declaration
puts ceramic.to_xml
# Lutaml::Model may not know how to prefix elements

# ✅ Correct - namespace declared in model
xml do
  namespace CeramicNamespace
end

Mixing prefixed and default namespaces

Remember: only the root element’s namespace can be default. Other namespaces must use prefixes.