General

The Lutaml::Model::Schema.to_xml method generates W3C XML Schema (XSD) from LutaML models with full namespace support.

Generated XSD includes:

  • Proper namespace declarations

  • Element and attribute qualification rules

  • Complex and simple type definitions

  • Schema imports and includes

  • Documentation annotations

Schema generation syntax

xsd_string = Lutaml::Model::Schema.to_xml(ModelClass, options)

Options:

namespace

Namespace URI for the schema

prefix

Namespace prefix

location

Schema location URL

output_dir

Directory to save generated XSD file

create_files

Boolean, whether to write files to disk (default: false)

module_namespace

(Optional) Ruby module namespace for generated classes (e.g., "UnitsMLV0919")

register_id

(Optional) Register ID for model registration, required when module_namespace is provided

When module_namespace is provided but module_namespace is not, the module namespace will be auto-generated from the output_dir parameter using PascalCase conversion.

Element and type inference

The XSD generator infers structure from model definitions:

Elements vs types:

  • Models with element declared: Generate both element declaration and type definition

  • Models without element (type-only): Generate only type definition

  • Collections: Generate elements with maxOccurs="unbounded"

Type naming:

  • Default: ClassNameType (e.g., PersonType for Person class)

  • Override via type_name

  • Nested models get separate type definitions

Adapter support

XSD generation uses SchemaBuilder adapters:

  • Nokogiri: Default, full XSD 1.0 support

  • Oga: Pure Ruby, feature complete

Both adapters produce identical, standards-compliant XSD output.

Complete example

Example 1. XSD generation with full namespace features
# Define namespace
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"
end

# Define address namespace (imported)
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

# Type-only address model (no element)
class Address < Lutaml::Model::Serializable
  attribute :street, :string
  attribute :city, :string
  attribute :postal_code, :string

  xml do
    # No element declaration - type-only
    namespace AddressNamespace

    sequence do
      map_element 'street', to: :street
      map_element 'city', to: :city
      map_element 'postalCode', to: :postal_code
    end
  end
end

# Main contact model
class Contact < Lutaml::Model::Serializable
  attribute :contact_id, :string, xsd_type: 'xs:ID'
  attribute :name, :string
  attribute :email, :uri
  attribute :address, Address

  xml do
    element 'contact'
    namespace ContactNamespace
    documentation "A contact record"
    type_name 'ContactRecordType'

    sequence do
      map_attribute 'id', to: :contact_id
      map_element 'name', to: :name
      map_element 'email', to: :email
      map_element 'address', to: :address
    end
  end
end

# Generate XSD
xsd = Lutaml::Model::Schema.to_xml(
  Contact,
  namespace: ContactNamespace.uri,
  prefix: ContactNamespace.prefix_default,
  output_dir: 'schemas',
  create_files: true,
  module_namespace: 'ContactML',
  register_id: :contacts
)

puts xsd

Generated XSD:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:contact="https://example.com/schemas/contact/v1"
           xmlns:addr="https://example.com/schemas/address/v1"
           targetNamespace="https://example.com/schemas/contact/v1"
           elementFormDefault="qualified"
           attributeFormDefault="unqualified"
           version="1.0">

  <xs:annotation>
    <xs:documentation>Contact information schema for Example Corp</xs:documentation>
  </xs:annotation>

  <xs:import namespace="https://example.com/schemas/address/v1"
             schemaLocation="https://example.com/schemas/address/v1/address.xsd"/>

  <!-- Global element declaration -->
  <xs:element name="contact" type="contact:ContactRecordType">
    <xs:annotation>
      <xs:documentation>A contact record</xs:documentation>
    </xs:annotation>
  </xs:element>

  <!-- Complex type definition -->
  <xs:complexType name="ContactRecordType">
    <xs:sequence>
      <xs:element name="name" type="xs:string"/>
      <xs:element name="email" type="xs:anyURI"/>
      <xs:element name="address" type="addr:AddressType"/>
    </xs:sequence>
    <xs:attribute name="id" type="xs:ID"/>
  </xs:complexType>

  <!-- Address type from imported namespace -->
  <xs:complexType name="AddressType">
    <xs:sequence>
      <xs:element name="street" type="xs:string"/>
      <xs:element name="city" type="xs:string"/>
      <xs:element name="postalCode" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

Custom registers

A LutaML::Model Register allows for dynamic modification and reconfiguration of model hierarchies without altering the original model definitions. For more information, refer to the Custom Registers Guide.

Before using the Lutaml::Model::Register instance, make sure to register it in Lutaml::Model::GlobalRegister.
By default, a default_register with the id :default is created and registered in the GlobalRegister. This default register is also set in Lutaml::Model::Config.default_register as the default value.

The default register can be set at the configuration level using the following syntax:

Lutaml::Model::Config.default_register = :default # the register id goes here.

Element and Attribute Form Defaults

XSD generation supports W3C XML Schema’s elementFormDefault and attributeFormDefault attributes to control whether local elements and attributes must be namespace-qualified in instance documents.

Understanding Form Defaults

The W3C XML Schema specification defines two important attributes on <xs:schema>:

  • elementFormDefault - Controls whether local elements must be namespace-qualified

  • attributeFormDefault - Controls whether local attributes must be namespace-qualified

The W3C default for both is "unqualified", meaning elements and attributes appear without namespace prefixes in instance documents unless explicitly qualified.

Per W3C XML Schema Part 1: Structures, Section 3.2.2:

If the attribute form default is 'qualified', then locally declared attributes (those declared in complexType definitions) must be qualified in instance documents.

— W3C XML Schema Part 1: Structures

Configuring Form Defaults

Use the element_form_default and attribute_form_default methods in your XmlNamespace class:

class MyNamespace < Lutaml::Model::XmlNamespace
  uri "http://example.com/ns"
  prefix_default "ex"

  # Elements in instance documents will be namespace-qualified
  element_form_default :qualified

  # Attributes in instance documents will be namespace-qualified
  attribute_form_default :qualified
end

Form Attribute on Individual Elements/Attributes

When a local element or attribute differs from the schema’s form default, the XSD generator emits an explicit form attribute. The form option on map_element and map_attribute allows you to override the default:

class FormUnqualifiedExample < Lutaml::Model::Serializable
  attribute :content, :string

  xml do
    element "elementFormUnqualified"
    type_name "ElementFormUnqualifiedType"
    namespace MyNamespace  # elementFormDefault="qualified"

    # Override: This element should be unqualified despite schema default
    map_element "content", to: :content, form: :unqualified
  end
end

This generates:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:ex="http://example.com/ns"
           targetNamespace="http://example.com/ns"
           elementFormDefault="qualified">

  <xs:element name="elementFormUnqualified" type="ex:ElementFormUnqualifiedType"/>

  <xs:complexType name="ElementFormUnqualifiedType">
    <xs:sequence>
      <!-- form="unqualified" is emitted because it differs from elementFormDefault -->
      <xs:element name="content" type="xs:string" form="unqualified"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

When the form Attribute is Emitted

Per XSD specification, the form attribute on local declarations should only be emitted when it differs from the schema-level default:

  • If elementFormDefault="qualified" and an element has form="qualified" → NOT emitted

  • If elementFormDefault="qualified" and an element has form="unqualified" → EMITTED

  • If elementFormDefault="unqualified" and an element has form="unqualified" → NOT emitted

  • If elementFormDefault="unqualified" and an element has form="qualified" → EMITTED

This optimization reduces XSD verbosity while maintaining correctness.

Complete Example: ElementFormUnqualified Pattern

This example demonstrates the W3C ElementFormUnqualified pattern where local elements appear without namespace prefixes:

class ExampleNamespace < Lutaml::Model::XmlNamespace
  uri "http://example.com/ns"
  prefix_default "ex"
  element_form_default :unqualified  # Local elements unqualified
end

class ElementFormUnqualified < Lutaml::Model::Serializable
  attribute :premium, :string

  xml do
    element "elementFormUnqualified"
    type_name "ElementFormUnqualifiedType"
    namespace ExampleNamespace
    map_element "premium", to: :premium
  end
end

Generated XSD:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:ex="http://example.com/ns"
           targetNamespace="http://example.com/ns"
           elementFormDefault="unqualified">

  <xs:element name="elementFormUnqualified" type="ex:ElementFormUnqualifiedType"/>

  <xs:complexType name="ElementFormUnqualifiedType">
    <xs:sequence>
      <!-- form attribute NOT emitted because it matches elementFormDefault -->
      <xs:element name="premium" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

Valid Instance Document:

<ex:elementFormUnqualified xmlns:ex="http://example.com/ns">
  <premium>1175</premium>
</ex:elementFormUnqualified>

Complete Example: AttributeFormUnqualified Pattern

This example demonstrates the W3C AttributeFormUnqualified pattern:

class ExampleNamespace < Lutaml::Model::XmlNamespace
  uri "http://example.com/ns"
  prefix_default "ex"
  element_form_default :qualified
  attribute_form_default :unqualified  # Local attributes unqualified
end

class AttributeFormUnqualified < Lutaml::Model::Serializable
  attribute :id, :string

  xml do
    element "attributeFormUnqualified"
    type_name "AttributeFormUnqualifiedType"
    namespace ExampleNamespace
    map_attribute "id", to: :id
  end
end

Valid Instance Document:

<ex:attributeFormUnqualified xmlns:ex="http://example.com/ns" id="id01">
  <ex:premium>1175</ex:premium>
</ex:attributeFormUnqualified>

Mixing Qualified and Unqualified Forms

You can mix qualified and unqualified forms within the same schema:

class MixedFormNamespace < Lutaml::Model::XmlNamespace
  uri "http://example.com/ns"
  prefix_default "ex"
  element_form_default :qualified  # Schema default
  attribute_form_default :qualified # Schema default
end

class MixedFormExample < Lutaml::Model::Serializable
  attribute :qualified_element, :string
  attribute :unqualified_element, :string
  attribute :qualified_attr, :string
  attribute :unqualified_attr, :string

  xml do
    element "mixedFormExample"
    type_name "MixedFormExampleType"
    namespace MixedFormNamespace

    # Explicitly unqualified - overrides element_form_default
    map_element "unqualified_element", to: :unqualified_element, form: :unqualified

    # Defaults to qualified (matches element_form_default)
    map_element "qualified_element", to: :qualified_element

    # Explicitly unqualified - overrides attribute_form_default
    map_attribute "unqualified_attr", to: :unqualified_attr, form: :unqualified

    # Defaults to qualified (matches attribute_form_default)
    map_attribute "qualified_attr", to: :qualified_attr
  end
end