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_namespaceis 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
elementdeclared: 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.,PersonTypeforPersonclass) -
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
# 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 xsdGenerated 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:
— 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
endForm 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
endThis 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 hasform="qualified"→ NOT emitted -
If
elementFormDefault="qualified"and an element hasform="unqualified"→ EMITTED -
If
elementFormDefault="unqualified"and an element hasform="unqualified"→ NOT emitted -
If
elementFormDefault="unqualified"and an element hasform="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
endGenerated 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
endValid 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