Overview

The XmlNamespace class provides declarative namespace metadata following W3C XML Namespace and XSD specifications.

Benefits:

  • Reusable namespace definitions across models

  • Full XSD generation support

  • Control over qualification behavior

  • Documentation and versioning

  • Multi-schema support via imports/includes

Creating namespace classes

Inherit from Lutaml::Model::XmlNamespace and use DSL methods:

class MyNamespace < Lutaml::Model::XmlNamespace
  uri 'namespace-uri'                     # Required
  schema_location 'schema-url'            # Optional
  prefix_default 'prefix'                 # Optional
  element_form_default :qualified         # Optional
  attribute_form_default :unqualified     # Optional
  version '1.0'                           # Optional
  documentation 'Schema description'      # Optional
  imports OtherNamespace                  # Optional
  includes 'schema-file.xsd'              # Optional
end

DSL methods reference

uri

Sets the namespace URI that uniquely identifies this namespace.

Syntax:

uri 'namespace-uri-string'

This is the fundamental identifier used in xmlns declarations.

Example 1. 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"

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 2. 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 3. 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 4. 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 5. 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 6. 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 7. 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 8. 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 9. 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 10. 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>

Runtime prefix override

Namespace prefixes can be overridden at runtime when building namespace instances.

Example 11. Overriding namespace prefix at runtime
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>

Additional XSD types

Lutaml::Model supports additional XSD types for specialized data handling:

Duration type

The :duration type handles ISO 8601 duration values conforming to xs:duration.

Duration format: P[n]Y[n]M[n]DT[n]H[n]M[n]S

Where,

P

Required prefix indicating period

[n]Y

Years (optional)

[n]M

Months (optional, before T)

[n]D

Days (optional)

T

Time prefix (required if time components present)

[n]H

Hours (optional)

[n]M

Minutes (optional, after T)

[n]S

Seconds (optional, can include decimals)

Example 12. Using the :duration type
class ProcessingTask < Lutaml::Model::Serializable
  attribute :processing_time, :duration

  xml do
    root "task"
    map_element "processingTime", to: :processing_time
  end
end

# Valid durations
task1 = ProcessingTask.new(processing_time: "P1Y2M3D")
# 1 year, 2 months, 3 days
task2 = ProcessingTask.new(processing_time: "PT4H5M6S")
# 4 hours, 5 minutes, 6 seconds
task3 = ProcessingTask.new(processing_time: "P1Y2M3DT4H5M6S")
# Combined
task4 = ProcessingTask.new(processing_time: "PT0.5S")
# 0.5 seconds

puts task1.to_xml
# => <task><processingTime>P1Y2M3D</processingTime></task>

URI type

The :uri type handles Uniform Resource Identifiers conforming to xs:anyURI.

Example 13. Using the :uri type
class Resource < Lutaml::Model::Serializable
  attribute :homepage, :uri
  attribute :schema_location, :uri

  xml do
    root "resource"
    map_element "homepage", to: :homepage
    map_attribute "schemaLocation", to: :schema_location
  end
end

resource = Resource.new(
  homepage: "https://example.com/page",
  schema_location: "https://example.com/schema.xsd"
)

puts resource.to_xml
# => <resource schemaLocation="https://example.com/schema.xsd">
#      <homepage>https://example.com/page</homepage>
#    </resource>

QName type

The :qname type handles XML qualified names conforming to xs:QName.

A QName consists of an optional namespace prefix and a local name, separated by a colon.

Example 14. Using the :qname type
class Reference < Lutaml::Model::Serializable
  attribute :ref_type, :qname
  attribute :target, :qname

  xml do
    root "reference"
    map_attribute "type", to: :ref_type
    map_element "target", to: :target
  end
end

ref = Reference.new(
  ref_type: "xsd:string",
  target: "ns:elementName"
)

puts ref.to_xml
# => <reference type="xsd:string">
#      <target>ns:elementName</target>
#    </reference>

# Accessing QName components
qname = Lutaml::Model::Type::QName.new("prefix:localName")
puts qname.prefix      # => "prefix"
puts qname.local_name  # => "localName"

Base64Binary type

The :base64_binary type handles base64-encoded binary data conforming to xs:base64Binary.

Example 15. Using the :base64_binary type
class Attachment < Lutaml::Model::Serializable
  attribute :content, :base64_binary
  attribute :filename, :string

  xml do
    root "attachment"
    map_element "content", to: :content
    map_attribute "filename", to: :filename
  end
end

# Encoding binary data
binary_data = "Hello World"
encoded = Lutaml::Model::Type::Base64Binary.encode(binary_data)

attachment = Attachment.new(
  content: encoded,
  filename: "hello.txt"
)

puts attachment.to_xml
# => <attachment filename="hello.txt">
#      <content>SGVsbG8gV29ybGQ=</content>
#    </attachment>

# Decoding
decoded = Lutaml::Model::Type::Base64Binary.decode(attachment.content)
# => "Hello World"

HexBinary type

The :hex_binary type handles hexadecimal-encoded binary data conforming to xs:hexBinary.

Example 16. Using the :hex_binary type
class Checksum < Lutaml::Model::Serializable
  attribute :hash_value, :hex_binary
  attribute :algorithm, :string

  xml do
    root "checksum"
    map_element "value", to: :hash_value
    map_attribute "algorithm", to: :algorithm
  end
end

# Encoding binary data
binary_data = "Hello"
encoded = Lutaml::Model::Type::HexBinary.encode(binary_data)

checksum = Checksum.new(
  hash_value: encoded,
  algorithm: "SHA256"
)

puts checksum.to_xml
# => <checksum algorithm="SHA256">
#      <value>48656c6c6f</value>
#    </checksum>

# Decoding
decoded = Lutaml::Model::Type::HexBinary.decode(checksum.hash_value)
# => "Hello"

Type-level namespaces

Overview

Custom Type::Value classes and Serializable models can declare their namespace using the xml_namespace directive.

This enables automatic namespace qualification based on attribute types, following W3C XML Namespace specifications.

Type-level namespaces work in both serialization and deserialization, ensuring proper round-trip behavior with namespace-qualified elements and attributes.

Use cases

Type-level namespaces are ideal for:

  • Standard vocabularies: Dublin Core, FOAF, RSS/Atom properties

  • Custom XSD types: Domain-specific types with their own namespaces

  • Multi-namespace documents: Office Open XML, XHTML with embedded metadata

  • Reusable components: Types shared across multiple models and schemas

Type::Value xml_namespace directive

Custom value types declare their namespace using class-level directives:

Syntax:

class CustomType < Lutaml::Model::Type::Value
  xml_namespace CustomNamespace  (1)
  xsd_type 'CustomType'           (2)

  def self.cast(value)
    # Type conversion logic
  end
end
1 Associates an XmlNamespace class with this type
2 Sets the XSD type name for schema generation (optional)

Where,

xml_namespace

Class-level directive that accepts an XmlNamespace class. This namespace will be automatically applied when serializing or deserializing elements and attributes of this type, unless overridden by explicit mapping namespace.

xsd_type

Class-level directive that sets the XSD type name used during schema generation. If not specified, inherits default_xsd_type from the parent class (e.g., xs:string for Type::String).

How Type namespaces work

During serialization (to_xml)

When an element or attribute uses a custom type with namespace:

  1. Type’s namespace is consulted if no explicit mapping namespace

  2. Namespace URI and prefix are added to document root

  3. Element/attribute is prefixed according to namespace resolution priority

  4. Works with both simple types (Type::Value) and complex types (Serializable)

During deserialization (from_xml)

When parsing namespace-qualified XML:

  1. Namespace-qualified elements/attributes are matched against type namespaces

  2. Both prefixed (dc:title) and default namespace elements are recognized

  3. Type casting occurs after namespace matching

  4. Works seamlessly with inherited namespaces and explicit mappings

Simple example with deserialization

Example 17. Email type with namespace (complete round-trip)
class EmailNamespace < Lutaml::Model::XmlNamespace
  uri 'https://example.com/types/email'
  prefix_default 'email'
end

class EmailType < Lutaml::Model::Type::String
  xml_namespace EmailNamespace
  xsd_type 'EmailAddress'

  def self.cast(value)
    email = super(value)
    unless email.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
      raise Lutaml::Model::TypeError, "Invalid email: #{email}"
    end
    email.downcase
  end
end

class Contact < Lutaml::Model::Serializable
  attribute :email, EmailType

  xml do
    root 'contact'
    map_element 'email', to: :email  # Uses EmailNamespace automatically
  end
end

# Serialization:
contact = Contact.new(email: "USER@EXAMPLE.COM")
xml = contact.to_xml
puts xml
# => <contact xmlns:email="https://example.com/types/email">
#      <email:email>user@example.com</email:email>
#    </contact>

# Deserialization:
parsed = Contact.from_xml(xml)
parsed.email  # => "user@example.com" (type casting applied)
parsed == contact  # => true (round-trip successful)

Serializable namespace directive

Models declare their namespace using the namespace directive:

Syntax:

class CustomModel < Lutaml::Model::Serializable
  namespace CustomNamespace

  attribute :value, :string

  xml do
    root 'customElement'
    map_element 'value', to: :value
  end
end

Complete working example

Example 18. Dublin Core metadata with Type namespaces
# Define Dublin Core namespace
class DublinCoreNamespace < Lutaml::Model::XmlNamespace
  uri 'http://purl.org/dc/elements/1.1/'
  prefix_default 'dc'
end

# Define document namespace
class DocumentNamespace < Lutaml::Model::XmlNamespace
  uri 'https://example.com/document'
  prefix_default 'doc'
end

# Define custom type with Dublin Core namespace
class DcTitleType < Lutaml::Model::Type::String
  xml_namespace DublinCoreNamespace
  xsd_type 'titleType'
end

class DcCreatorType < Lutaml::Model::Type::String
  xml_namespace DublinCoreNamespace
  xsd_type 'creatorType'
end

# Use types in document model
class Document < Lutaml::Model::Serializable
  namespace DocumentNamespace

  attribute :title, DcTitleType
  attribute :creator, DcCreatorType
  attribute :content, :string  # Regular attribute, no type namespace

  xml do
    root 'document'

    map_element 'title', to: :title      # Becomes <dc:title> from Type
    map_element 'creator', to: :creator  # Becomes <dc:creator> from Type
    map_element 'content', to: :content  # No namespace (unqualified local)
  end
end

# Serialization:
doc = Document.new(
  title: 'Example Document',
  creator: 'John Doe',
  content: 'Document content here'
)

xml = doc.to_xml
puts xml
# Output:
# <doc:document
#   xmlns:doc="https://example.com/document"
#   xmlns:dc="http://purl.org/dc/elements/1.1/">
#   <dc:title>Example Document</dc:title>
#   <dc:creator>John Doe</dc:creator>
#   <content>Document content here</content>
# </doc:document>

# Deserialization (round-trip):
parsed = Document.from_xml(xml)
parsed.title    # => "Example Document"
parsed.creator  # => "John Doe"
parsed.content  # => "Document content here"
parsed == doc   # => true

Namespace resolution priority

Namespace is determined by priority (highest to lowest):

For elements (map_element)

  1. Model-level namespace (from the attribute’s type class)

    # Child model declares its own namespace
    class Title < Lutaml::Model::Serializable
      xml do
        element 'title'
        namespace DublinCoreNamespace
      end
    end
    
    # Parent uses child's namespace
    class Document < Lutaml::Model::Serializable
      attribute :title, Title
      map_element 'title', to: :title  # Uses Title's namespace
    end
  2. Type-level namespace (from Type::Value class)

    class TitleType < Lutaml::Model::Type::String
      xml_namespace DublinCoreNamespace
    end
    attribute :title, TitleType
    map_element 'title', to: :title  # Uses DublinCoreNamespace
  3. Form default qualification (from element_form_default)

    If :qualified: inherits parent namespace
    If :unqualified: no namespace (default)

For attributes (map_attribute)

  1. Type-level namespace (from Type::Value class)

    class XsiTypeType < Lutaml::Model::Type::String
      xml_namespace XsiNamespace
    end
    attribute :type, XsiTypeType
    map_attribute 'type', to: :type  # Uses XsiNamespace, becomes xsi:type
  2. No namespace (W3C default)

    Per W3C specifications: unprefixed attributes are NEVER in a namespace.
    Only explicitly qualified attributes have namespaces.

Per W3C specifications, unprefixed XML attributes do NOT inherit their parent element’s namespace. Only explicitly qualified attributes have namespaces. This is critical for W3C compliance and correct round-tripping.
Type-level namespaces work in both serialization (to_xml) and deserialization (from_xml), ensuring proper round-trip behavior with namespace-qualified elements and attributes.

Multi-namespace example

Type-level namespaces excel when working with multi-namespace documents:

Example 19. Office Open XML Core Properties with four namespaces
# 1. Define namespace classes
class CorePropertiesNamespace < Lutaml::Model::XmlNamespace
  uri 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties'
  prefix_default 'cp'
end

class DublinCoreNamespace < Lutaml::Model::XmlNamespace
  uri 'http://purl.org/dc/elements/1.1/'
  prefix_default 'dc'
end

class DCTermsNamespace < Lutaml::Model::XmlNamespace
  uri 'http://purl.org/dc/terms/'
  prefix_default 'dcterms'
end

class XsiNamespace < Lutaml::Model::XmlNamespace
  uri 'http://www.w3.org/2001/XMLSchema-instance'
  prefix_default 'xsi'
end

# 2. Define Type::Value classes with namespaces
class DcTitleType < Lutaml::Model::Type::String
  xml_namespace DublinCoreNamespace
  xsd_type 'titleType'
end

class DcCreatorType < Lutaml::Model::Type::String
  xml_namespace DublinCoreNamespace
  xsd_type 'creatorType'
end

class CpLastModifiedByType < Lutaml::Model::Type::String
  namespace CorePropertiesNamespace
end

class CpRevisionType < Lutaml::Model::Type::Integer
  namespace CorePropertiesNamespace
end

class XsiTypeType < Lutaml::Model::Type::String
  xml_namespace XsiNamespace
  xsd_type 'type'
end

# 3. Define complex Model types with namespaces
class DctermsCreatedType < Lutaml::Model::Serializable
  namespace DCTermsNamespace

  attribute :value, :date_time
  attribute :type, XsiTypeType

  xml do
    root 'created'
    # This becomes xsi:type from XsiTypeType
    map_attribute 'type', to: :type
    map_content to: :value
  end
end

class DctermsModifiedType < Lutaml::Model::Serializable
  namespace DCTermsNamespace

  attribute :value, :date_time
  attribute :type, XsiTypeType

  xml do
    root 'modified'
    map_attribute 'type', to: :type
    map_content to: :value
  end
end

# 4. Define root model
class CoreProperties < Lutaml::Model::Serializable
  namespace CorePropertiesNamespace

  attribute :title, DcTitleType
  attribute :creator, DcCreatorType
  attribute :last_modified_by, CpLastModifiedByType
  attribute :revision, CpRevisionType
  attribute :created, DctermsCreatedType
  attribute :modified, DctermsModifiedType

  xml do
    root 'coreProperties'

    # Type namespaces automatically applied
    map_element 'title', to: :title              # Becomes <dc:title>
    map_element 'creator', to: :creator          # Becomes <dc:creator>
    map_element 'lastModifiedBy', to: :last_modified_by  # Becomes <cp:lastModifiedBy>
    map_element 'revision', to: :revision        # Becomes <cp:revision>
    map_element 'created', to: :created          # Becomes <dcterms:created>
    map_element 'modified', to: :modified        # Becomes <dcterms:modified>
  end
end

# Serialization: Create and serialize
props = CoreProperties.new(
  title: 'Untitled',
  creator: 'Uniword',
  last_modified_by: 'Uniword',
  revision: 1,
  created: DctermsCreatedType.new(
    value: DateTime.parse('2025-11-13T17:11:03Z'),
    type: 'dcterms:W3CDTF'
  ),
  modified: DctermsModifiedType.new(
    value: DateTime.parse('2025-11-13T17:11:03Z'),
    type: 'dcterms:W3CDTF'
  )
)

puts props.to_xml

Serialization output with four namespaces:

<cp:coreProperties
  xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:dcterms="http://purl.org/dc/terms/"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <dc:title>Untitled</dc:title>
  <dc:creator>Uniword</dc:creator>
  <cp:lastModifiedBy>Uniword</cp:lastModifiedBy>
  <cp:revision>1</cp:revision>
  <dcterms:created xsi:type="dcterms:W3CDTF">2025-11-13T17:11:03Z</dcterms:created>
  <dcterms:modified xsi:type="dcterms:W3CDTF">2025-11-13T17:11:03Z</dcterms:modified>
</cp:coreProperties>

Deserialization: Round-trip parsing:

# Parse the generated XML
parsed_props = CoreProperties.from_xml(props.to_xml)

# All namespaces are correctly resolved
parsed_props.title  # => "Untitled" (from dc:title)
parsed_props.creator  # => "Uniword" (from dc:creator)
parsed_props.last_modified_by  # => "Uniword" (from cp:lastModifiedBy)
parsed_props.revision  # => 1 (from cp:revision)
parsed_props.created.value  # => DateTime "2025-11-13T17:11:03Z"
parsed_props.created.type  # => "dcterms:W3CDTF" (from xsi:type)

# Verify round-trip equality
parsed_props === props  # => true

This demonstrates:

  • Four different namespaces (cp, dc, dcterms, xsi) in a single document

  • Type-level namespace for simple value types (DcTitleType, DcCreatorType)

  • Model-level namespace for complex types (DctermsCreatedType, DctermsModifiedType)

  • Attribute namespace qualification (xsi:type from XsiTypeType)

  • Full round-trip support - Type namespaces work in both serialization and deserialization

  • W3C-compliant namespace resolution following the priority rules

Namespace priority examples

Example 20. Explicit namespace overrides type namespace
class DefaultType < Lutaml::Model::Type::String
  namespace DefaultNamespace
end

class Model < Lutaml::Model::Serializable
  attribute :value, DefaultType

  xml do
    root 'model'

    # Case 1: Type namespace used (no explicit namespace)
    map_element 'value1', to: :value
    # => <default:value1>

    # Case 2: Explicit namespace overrides Type namespace
    map_element 'value2', to: :value, namespace: OtherNamespace
    # => <other:value2>

    # Case 3: Inherit parent namespace, ignoring Type namespace
    map_element 'value3', to: :value, namespace: :inherit
    # => <model_ns:value3> (if Model has namespace)
  end
end

Attribute namespace handling

Attribute namespace handling differs from elements per W3C specifications.

Attributes only belong to a namespace when explicitly qualified with a prefix. Unprefixed attributes are not considered to be in any namespace per W3C XML Namespace.

Example 21. Attribute namespace handling
class XsiTypeType < Lutaml::Model::Type::String
  xml_namespace XsiNamespace
  xsd_type 'type'
end

class Product < Lutaml::Model::Serializable
  attribute :id, :string
  attribute :schema_type, XsiTypeType

  xml do
    root 'product'
    map_attribute 'id', to: :id              # Unqualified: id="..."
    map_attribute 'type', to: :schema_type   # Qualified: xsi:type="..."
  end
end

# Serialization:
product = Product.new(id: "P001", schema_type: "ProductType")
puts product.to_xml
# => <product xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
#             id="P001"
#             xsi:type="ProductType"/>

Note that id is unprefixed (no namespace per W3C), while type uses the xsi: prefix from XsiTypeType.

XML attribute explicit declaration of XSD type

General

The :xsd_type attribute option allows explicit control over the XSD type used in schema generation.

This is particularly useful for:

  • Reference types using xs:ID and xs:IDREF

  • Custom XSD types not directly mapped to Lutaml types

  • Override automatic type inference

Syntax:

attribute :attr_name, Type, xsd_type: 'xs:typeName'

Priority order

When determining XSD type, Lutaml::Model uses this priority:

  1. Explicit :xsd_type option on attribute (highest priority)

  2. Type’s xsd_type class method

  3. Default type inference (lowest priority)

Example 22. Using :xsd_type for ID and IDREF
class Product < Lutaml::Model::Serializable
  attribute :product_id, :string, xsd_type: 'xs:ID'
  attribute :category_ref, :string, xsd_type: 'xs:IDREF'
  attribute :related_refs, :string, collection: true, xsd_type: 'xs:IDREFS'

  xml do
    element 'product'
    map_attribute 'id', to: :product_id
    map_attribute 'categoryRef', to: :category_ref
    map_attribute 'relatedRefs', to: :related_refs
  end
end

# Generated XSD uses:
# <xs:attribute name="id" type="xs:ID"/>
# <xs:attribute name="categoryRef" type="xs:IDREF"/>
# <xs:attribute name="relatedRefs" type="xs:IDREFS"/>
Example 23. Using :xsd_type for custom types
class Document < Lutaml::Model::Serializable
  attribute :language, :string, xsd_type: 'xs:language'
  attribute :content_type, :string, xsd_type: 'xs:token'
  attribute :normalized_text, :string, xsd_type: 'xs:normalizedString'

  xml do
    element 'document'
    map_attribute 'lang', to: :language
    map_attribute 'contentType', to: :content_type
    map_element 'text', to: :normalized_text
  end
end

# Generated XSD declares proper XSD built-in types

Use with Reference type

The Type::Reference type can utilize :xsd_type for proper XSD generation:

Example 24. Reference type with XSD configuration
class Catalog < Lutaml::Model::Serializable
  attribute :catalog_id, { ref: [Catalog, :id] }, xsd_type: 'xs:ID'
  attribute :parent_ref, { ref: [Catalog, :id] }, xsd_type: 'xs:IDREF'

  xml do
    element 'catalog'
    map_attribute 'id', to: :catalog_id
    map_attribute 'parent', to: :parent_ref
  end
end

XSD schema generation support

Type-level namespaces are fully integrated with XSD generation:

  • Type namespaces generate proper xs:import declarations

  • xsd_type directive controls the type name in generated schemas

  • Namespace URIs are included in schema imports

  • Schema locations are preserved from XmlNamespace definitions

Example 25. XSD generation with type namespaces
# After defining DcTitleType and Document from previous example
xsd = Lutaml::Model::Schema.to_xml(
  Document,
  namespace: DocumentNamespace.uri,
  prefix: DocumentNamespace.prefix_default
)

# Generated XSD includes:
# <xs:import namespace="http://purl.org/dc/elements/1.1/"
#            schemaLocation="..." />
#
# And uses dc:titleType for the title element type

Best practices

When using Type-level namespaces:

  1. Define namespace classes first: Create all XmlNamespace classes before defining types

  2. Use descriptive type names: Set xsd_type for custom types to generate meaningful schemas

  3. Leverage namespace resolution: Let type namespaces handle qualification automatically instead of explicit mapping namespaces

  4. Test round-trips: Verify that serialization and deserialization work correctly with your namespace configuration

  5. Document namespaces: Use XmlNamespace documentation for schema clarity

Migration from deprecated namespace directive

Overview

The namespace directive for Type::Value classes has been renamed to xml_namespace to better indicate its XML-specific nature and reserve namespace for future semantic namespace features.

Use xml_namespace instead of namespace for all Type::Value classes.

Deprecated syntax

class DcTitleType < Lutaml::Model::Type::String
  namespace DublinCoreNamespace  # ⚠️ DEPRECATED
  xsd_type 'titleType'
end

Deprecation warning shown:

[DEPRECATION] Type::Value.namespace is deprecated. Use xml_namespace instead.
This will be removed in version 1.0.0

Current syntax

class DcTitleType < Lutaml::Model::Type::String
  xml_namespace DublinCoreNamespace  # ✅ CURRENT
  xsd_type 'titleType'
end

Why the change?

The renaming serves several important purposes:

  • XML-specific clarity: The xml_namespace name clearly indicates this is XML serialization configuration, not a general semantic namespace

  • Future compatibility: Reserves namespace for semantic namespace features like JSON-LD @context and RDF IRI mappings

  • Consistency: Aligns with format-specific naming patterns throughout Lutaml::Model

  • Disambiguation: Distinguishes Type-level namespace from Model-level namespace directive

Migration steps

  1. Locate Type::Value classes with namespace: Search your codebase for classes inheriting from Lutaml::Model::Type::Value (or built-in types like Type::String) that use namespace

  2. Replace directive: Change namespace to xml_namespace

  3. Test round-trip: Verify serialization and deserialization work correctly

  4. No other changes needed: Functionality is identical

Complete migration example

Before (deprecated)
class DublinCoreNamespace < Lutaml::Model::XmlNamespace
  uri 'http://purl.org/dc/elements/1.1/'
  prefix_default 'dc'
end

class DcTitleType < Lutaml::Model::Type::String
  namespace DublinCoreNamespace  # ⚠️ DEPRECATED
  xsd_type 'titleType'
end

class DcCreatorType < Lutaml::Model::Type::String
  namespace DublinCoreNamespace  # ⚠️ DEPRECATED
  xsd_type 'creatorType'
end

class Document < Lutaml::Model::Serializable
  attribute :title, DcTitleType
  attribute :creator, DcCreatorType

  xml do
    root 'document'
    map_element 'title', to: :title
    map_element 'creator', to: :creator
  end
end
After (current)
class DublinCoreNamespace < Lutaml::Model::XmlNamespace
  uri 'http://purl.org/dc/elements/1.1/'
  prefix_default 'dc'
end

class DcTitleType < Lutaml::Model::Type::String
  xml_namespace DublinCoreNamespace  # ✅ CURRENT
  xsd_type 'titleType'
end

class DcCreatorType < Lutaml::Model::Type::String
  xml_namespace DublinCoreNamespace  # ✅ CURRENT
  xsd_type 'creatorType'
end

class Document < Lutaml::Model::Serializable
  attribute :title, DcTitleType
  attribute :creator, DcCreatorType

  xml do
    root 'document'
    map_element 'title', to: :title
    map_element 'creator', to: :creator
  end
end

Backward compatibility

The deprecated namespace directive will:

  • Continue to work in current versions

  • Show a deprecation warning on first use

  • Be removed in Lutaml::Model version 1.0.0

This gives you time to migrate at your own pace while maintaining full functionality.

Migration from xml block

The old xml block approach in Type::Value is deprecated:

Old approach (deprecated)
class EmailType < Lutaml::Model::Type::String
  xml do
    namespace EmailNamespace  # DEPRECATED
  end

  def self.xsd_type
    'EmailAddress'
  end
end
New approach (recommended)
class EmailType < Lutaml::Model::Type::String
  xml_namespace EmailNamespace  # Directive, not block
  xsd_type 'EmailAddress'       # Directive, not method
end

Benefits of new approach:

  • Cleaner, more declarative syntax

  • Consistent with XmlNamespace DSL pattern

  • Better performance (no block evaluation)

  • Enhanced XSD generation support

  • Full deserialization support

The xml block approach still works with a deprecation warning for backward compatibility but lacks full deserialization support.

Namespace scope consolidation

General

The namespace_scope directive controls where namespace declarations 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.

This feature is particularly useful for:

  • Multi-namespace documents: Documents using several namespaces throughout

  • Reduced XML verbosity: Consolidate namespace declarations at logical scoping levels

  • Standards compliance: W3C-compliant namespace handling with declaration consolidation

  • Cleaner output: More readable XML with namespaces declared once

Syntax

xml do
  namespace RootNamespace
  namespace_scope [Namespace1, Namespace2, Namespace3]
end

Where,

namespace_scope

Array of XmlNamespace class objects. These namespaces will be declared once at the root element. Any namespaces NOT in this list will be declared locally on elements that use them.

How it works

Namespaces IN scope:

  • Declared once at the root element

  • All child elements using these namespaces omit local declarations

  • Cleaner, more compact XML output

Namespaces NOT in scope:

  • Declared locally on the first element that uses them

  • Each usage includes namespace declaration

  • More verbose but clearly scoped

Basic example

Example 26. Consolidating two namespaces
class VcardNamespace < Lutaml::Model::XmlNamespace
  uri "urn:ietf:params:xml:ns:vcard-4.0"
  prefix_default "vcard"
end

class DcNamespace < Lutaml::Model::XmlNamespace
  uri "http://purl.org/dc/elements/1.1/"
  prefix_default "dc"
end

class DcTitleType < Lutaml::Model::Type::String
  xml_namespace DcNamespace
end

class Vcard < Lutaml::Model::Serializable
  attribute :version, :string
  attribute :title, DcTitleType

  xml do
    root "vCard"
    namespace VcardNamespace
    namespace_scope [VcardNamespace, DcNamespace]  (1)

    map_element "version", to: :version
    map_element "title", to: :title
  end
end

vcard = Vcard.new(
  version: "4.0",
  title: "Dr. John Doe"
)

puts vcard.to_xml
1 Both namespaces declared at root element

Output with namespace_scope:

<vcard:vCard xmlns:vcard="urn:ietf:params:xml:ns:vcard-4.0"
             xmlns:dc="http://purl.org/dc/elements/1.1/">
  <vcard:version>4.0</vcard:version>
  <dc:title>Dr. John Doe</dc:title>
</vcard:vCard>

Without namespace_scope, dc namespace declared locally:

<vcard:vCard xmlns:vcard="urn:ietf:params:xml:ns:vcard-4.0">
  <vcard:version>4.0</vcard:version>
  <dc:title xmlns:dc="http://purl.org/dc/elements/1.1/">Dr. John Doe</dc:title>
</vcard:vCard>

Complete vCard example

Example 27. Multi-namespace vCard with 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

# Define types with Dublin Core namespaces
class DcTitleType < Lutaml::Model::Type::String
  xml_namespace DcNamespace
end

class DctermsCreatedType < Lutaml::Model::Type::DateTime
  xml_namespace DctermsNamespace
end

class VcardVersion < Lutaml::Model::Type::String
  xml_namespace VcardNamespace
end

class Vcard < Lutaml::Model::Serializable
  attribute :version, VcardVersion
  attribute :title, DcTitleType
  attribute :full_name, :string
  attribute :created, DctermsCreatedType

  xml do
    root "vCard"
    namespace VcardNamespace
    namespace_scope [VcardNamespace, DcNamespace, DctermsNamespace]  (1)

    map_element "version", to: :version
    map_element "title", to: :title
    map_element "fn", to: :full_name
    map_element "created", to: :created
  end
end

vcard = Vcard.new(
  version: "4.0",
  title: "Contact: Dr. John Doe",
  full_name: "Dr. John Doe",
  created: DateTime.parse("2024-06-01T12:00:00Z")
)

puts vcard.to_xml
1 Consolidate all three namespaces at root element

Output with namespace_scope:

<vcard:vCard xmlns:vcard="urn:ietf:params:xml:ns:vcard-4.0"
             xmlns:dc="http://purl.org/dc/elements/1.1/"
             xmlns:dcterms="http://purl.org/dc/terms/">
  <vcard:version>4.0</vcard:version>
  <dc:title>Contact: Dr. John Doe</dc:title>
  <vcard:fn>Dr. John Doe</vcard:fn>
  <dcterms:created>2024-06-01T12:00:00+00:00</dcterms:created>
</vcard:vCard>

Without namespace_scope, each namespace declared locally where used:

<vcard:vCard xmlns:vcard="urn:ietf:params:xml:ns:vcard-4.0">
  <vcard:version>4.0</vcard:version>
  <dc:title xmlns:dc="http://purl.org/dc/elements/1.1/">Contact: Dr. John Doe</dc:title>
  <vcard:fn>Dr. John Doe</vcard:fn>
  <dcterms:created xmlns:dcterms="http://purl.org/dc/terms/">2024-06-01T12:00:00+00:00</dcterms:created>
</vcard:vCard>

Selective namespace consolidation

You can choose which namespaces to consolidate and which to keep local:

Example 28. Consolidating only frequently used namespaces
class Vcard < Lutaml::Model::Serializable
  attribute :version, VcardVersion
  attribute :title, DcTitleType
  attribute :created, DctermsCreatedType

  xml do
    root "vCard"
    namespace VcardNamespace
    namespace_scope [VcardNamespace, DcNamespace]  (1)

    map_element "version", to: :version
    map_element "title", to: :title
    map_element "created", to: :created
  end
end
1 Only vcard and dc namespaces at root; dcterms declared locally

Output:

<vcard:vCard xmlns:vcard="urn:ietf:params:xml:ns:vcard-4.0"
             xmlns:dc="http://purl.org/dc/elements/1.1/">
  <vcard:version>4.0</vcard:version>
  <dc:title>Contact: Dr. John Doe</dc:title>
  <dcterms:created xmlns:dcterms="http://purl.org/dc/terms/">2024-06-01T12:00:00+00:00</dcterms:created>
</vcard:vCard>

The dcterms namespace is declared locally on the <dcterms:created> element because it was not included in namespace_scope.

When to use namespace_scope

Use namespace_scope when:

  • Multiple namespaces are used throughout the document

  • You prefer cleaner XML with consolidated declarations

  • Namespaces should be declared at a logical scoping level

  • The document structure benefits from upfront namespace declarations

Do NOT use namespace_scope when:

  • Single namespace documents (not needed)

  • Namespaces are rarely used (local declaration is clearer)

  • External consumers expect local namespace declarations

  • Namespace declarations should be deferred to actual usage points

Round-trip behavior

Type-level namespaces with namespace_scope work correctly in both serialization and deserialization:

Example 29. Round-trip with namespace_scope
# Serialize with consolidated namespaces
vcard = Vcard.new(
  version: "4.0",
  title: "Dr. John Doe",
  created: DateTime.parse("2024-06-01T12:00:00Z")
)

xml = vcard.to_xml
# Namespaces declared at root

# Deserialize - namespaces correctly resolved
parsed = Vcard.from_xml(xml)
parsed == vcard  # => true

# All attributes correctly parsed regardless of where namespace was declared
parsed.version  # => "4.0"
parsed.title    # => "Dr. John Doe"
parsed.created  # => DateTime "2024-06-01T12:00:00Z"