Basic element and attribute mapping
Simple model with elements
Example 1. Basic ceramic model with element mappings
class Ceramic < Lutaml::Model::Serializable
attribute :name, :string
attribute :description, :string
attribute :temperature, :integer
xml do
root 'ceramic'
map_element 'name', to: :name
map_attribute 'temperature', to: :temperature
map_content to: :description
end
end<ceramic temperature="1200">
<name>Porcelain Vase</name>
with celadon glaze.
</ceramic>> Ceramic.from_xml(xml)
> #<Ceramic @name="Porcelain Vase",
@description=" with celadon glaze.",
@temperature=1200>Nested models
Example 2. Model with nested object
class Glaze < Lutaml::Model::Serializable
attribute :color, :string
attribute :temperature, :integer
xml do
root 'Glaze'
map_element 'color', to: :color
map_element 'temperature', to: :temperature
end
end
class Ceramic < Lutaml::Model::Serializable
attribute :type, :string
attribute :glaze, Glaze
xml do
root 'Ceramic'
map_element 'Type', to: :type
map_element 'Glaze', to: :glaze
end
end<Ceramic>
<Type>Porcelain</Type>
<Glaze>
<color>Clear</color>
<temperature>1050</temperature>
</Glaze>
</Ceramic>Namespace patterns
Single namespace
Example 3. Model with single namespace
class Ceramic < Lutaml::Model::Serializable
attribute :type, :string
attribute :glaze, :string
xml do
root 'Ceramic'
namespace 'http://example.com/ceramic'
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>Prefixed namespace
Example 4. Model with prefixed namespace
class Ceramic < Lutaml::Model::Serializable
attribute :type, :string
attribute :glaze, :string
xml do
root 'Ceramic'
namespace 'http://example.com/ceramic', 'cer'
map_element 'Type', to: :type
map_element 'Glaze', to: :glaze
end
end<cer:Ceramic xmlns:cer='http://example.com/ceramic'>
<cer:Type>Porcelain</cer:Type>
<cer:Glaze>Clear</cer:Glaze>
</cer:Ceramic>Multi-namespace with XmlNamespace class
Example 5. Model using multiple namespaces with XmlNamespace classes
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,
namespace: GlazeNamespace
end
end<cer:ceramic xmlns:cer="https://example.com/ceramic"
xmlns:glz="https://example.com/glaze">
<type>Porcelain</type>
<glz:glaze>Celadon</glz:glaze>
</cer:ceramic>Namespace inheritance pattern
Example 6. Element explicitly inheriting parent namespace
class CeramicNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/ceramic'
prefix_default 'cer'
element_form_default :unqualified
end
class SpecialType < Lutaml::Model::Serializable
attribute :value, :string
xml do
element 'specialType'
namespace CeramicNamespace # Inherit parent namespace
map_content to: :value
end
end
class Ceramic < Lutaml::Model::Serializable
attribute :type, :string
attribute :special_type, SpecialType
xml do
element 'ceramic'
namespace CeramicNamespace
map_element 'type', to: :type # Unqualified (follows element_form_default)
map_element 'specialType', to: :special_type # Uses SpecialType's namespace
end
end<cer:ceramic xmlns:cer="https://example.com/ceramic">
<type>Porcelain</type>
<cer:specialType>Fine</cer:specialType>
</cer:ceramic>Rich content patterns
Mixed content
Example 7. Model with mixed content (text + elements)
class Paragraph < Lutaml::Model::Serializable
attribute :bold, :string, collection: true
attribute :italic, :string
xml do
root 'p', mixed: true
map_element 'bold', to: :bold
map_element 'i', to: :italic
end
end<p>My name is <bold>John Doe</bold>, and I'm <i>28</i> years old</p>> Paragraph.from_xml(xml)
> #<Paragraph @bold="John Doe", @italic="28">Ordered content
Example 8. Model preserving element order
class RootOrderedContent < Lutaml::Model::Serializable
attribute :bold, :string
attribute :italic, :string
attribute :underline, :string
xml do
root "RootOrderedContent", ordered: true
map_element :bold, to: :bold
map_element :italic, to: :italic
map_element :underline, to: :underline
end
end<RootOrderedContent>
<underline>Moon</underline>
<italic>384,400 km</italic>
<bold>bell</bold>
</RootOrderedContent>When serialized back, the order is preserved:
> instance = RootOrderedContent.from_xml(xml)
> instance.to_xml
> #<RootOrderedContent>
<underline>Moon</underline>
<italic>384,400 km</italic>
<bold>bell</bold>
</RootOrderedContent>Sequence patterns
Basic sequence
Example 9. Model with strict element ordering
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<collection>
<kiln>
<id>1</id>
<name>Nick</name>
<type>Hard</type>
<color>Black</color>
</kiln>
</collection>If elements appear out of order, an error is raised.
Sequence with namespace
Example 10. Combining sequence with namespace and XmlNamespace class
class ContactNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/schemas/contact/v1'
prefix_default 'contact'
element_form_default :qualified
end
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<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>Type-only models (no element)
Embedded type pattern
Example 11. Type-only model used within parent
# Type-only model - no element() or root() call
class Address < Lutaml::Model::Serializable
attribute :street, :string
attribute :city, :string
attribute :postal_code, :string
xml do
# No element declaration - this is a type-only model
sequence do
map_element 'street', to: :street
map_element 'city', to: :city
map_element 'postalCode', to: :postal_code
end
end
end
# Parent model using the type
class Contact < Lutaml::Model::Serializable
attribute :name, :string
attribute :address, Address
xml do
element 'contact'
sequence do
map_element 'name', to: :name
map_element 'address', to: :address
end
end
end<contact>
<name>John Doe</name>
<address>
<street>123 Main St</street>
<city>Metropolis</city>
<postalCode>12345</postalCode>
</address>
</contact>CDATA patterns
Forcing CDATA output
Example 12. Using cdata option to preserve special characters
class Example < Lutaml::Model::Serializable
attribute :name, :string
attribute :description, :string
attribute :title, :string
attribute :note, :string
xml do
root 'example'
map_element :name, to: :name, cdata: true
map_content to: :description, cdata: true
map_element :title, to: :title, cdata: false
map_element :note, to: :note, cdata: false
end
end<example>
<name><![CDATA[John]]></name>
<![CDATA[here is the description]]>
<title>Lutaml</title>
<note>Careful</note>
</example>XSD generation patterns
Basic XSD generation
Example 13. Generating XSD from model
class Contact < Lutaml::Model::Serializable
attribute :name, :string
attribute :email, :string
xml do
element 'contact'
map_element 'name', to: :name
map_element 'email', to: :email
end
end
# Generate XSD
xsd = Lutaml::Model::Schema.to_xml(Contact)
puts xsdXSD with namespace and documentation
Example 14. Complete XSD generation with metadata
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
version '1.0'
documentation "Contact information schema"
end
class Contact < Lutaml::Model::Serializable
attribute :name, :string
attribute :email, :string
xml do
element 'contact'
namespace ContactNamespace
documentation "A contact record"
sequence do
map_element 'name', to: :name
map_element 'email', to: :email
end
end
end
# Generate XSD with options
xsd = Lutaml::Model::Schema.to_xml(
Contact,
namespace: ContactNamespace.uri,
prefix: ContactNamespace.prefix_default,
output_dir: 'schemas',
create_files: true
)Qualification patterns
Qualified elements pattern
Example 15. All elements namespace-qualified
class CeramicNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/ceramic'
prefix_default 'cer'
element_form_default :qualified # All local elements qualified
end
class Ceramic < Lutaml::Model::Serializable
attribute :type, :string
attribute :color, :string
xml do
element 'ceramic'
namespace CeramicNamespace
map_element 'type', to: :type
map_element 'color', to: :color
end
end<cer:ceramic xmlns:cer="https://example.com/ceramic">
<cer:type>Porcelain</cer:type>
<cer:color>White</cer:color>
</cer:ceramic>Selective qualification
Example 16. Override qualification for specific elements
class CeramicNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/ceramic'
prefix_default 'cer'
element_form_default :unqualified # Default: unqualified
end
class Ceramic < Lutaml::Model::Serializable
attribute :type, :string
attribute :glaze, :string
attribute :id, :string
xml do
element 'ceramic'
namespace CeramicNamespace
# Override to qualified
map_element 'type', to: :type, form: :qualified
# Use default (unqualified)
map_element 'glaze', to: :glaze
# Force attribute qualified
map_attribute 'id', to: :id, form: :qualified
end
end<cer:ceramic xmlns:cer="https://example.com/ceramic" cer:id="C001">
<cer:type>Porcelain</cer:type>
<glaze>Clear</glaze>
</cer:ceramic>Collection patterns
Element collections
Example 17. Model with element collection
class CeramicCollection < Lutaml::Model::Serializable
attribute :items, Ceramic, collection: true
xml do
root 'ceramics'
map_element 'ceramic', to: :items
end
end<ceramics>
<ceramic>...</ceramic>
<ceramic>...</ceramic>
<ceramic>...</ceramic>
</ceramics>Attribute collections with delimiter
Example 18. XML attribute containing delimited values
class TitleCollection < Lutaml::Model::Collection
instances :items, :string
xml do
root "titles"
map_attribute "title", to: :items, delimiter: "; "
end
end<titles title="Title One; Title Two; Title Three"/>collection = TitleCollection.from_xml(xml)
collection.items
# => ["Title One", "Title Two", "Title Three"]Character encoding patterns
Per-instance encoding
Example 19. Setting encoding on model instance
class JapaneseCeramic < Lutaml::Model::Serializable
attribute :glaze_type, :string
attribute :description, :string
xml do
root 'JapaneseCeramic'
map_attribute 'glazeType', to: :glaze_type
map_element 'description', to: :description
end
end
# Create instance with UTF-8 data
instance = JapaneseCeramic.new(
glaze_type: "志野釉",
description: "東京国立博物館コレクション"
)
# Set character encoding to Shift_JIS
instance.encoding = "Shift_JIS"
# Serialize with specified encoding
serialization_output = instance.to_xmlPer-export encoding
Example 20. Setting encoding during serialization
ceramic_instance = Ceramic.new(
potter: "John & Jane",
description: " A ∑ series of ∏ porcelain µ vases."
)
# Using default encoding of UTF-8
ceramic_instance.to_xml
# => <ceramic><potter>John & Jane</potter> A ∑ series...</ceramic>
# Using ASCII encoding
ceramic_instance.to_xml(encoding: "ASCII")
# => <ceramic><potter>John & Jane</potter> A ∑ series...</ceramic>Import patterns with sequence
Importing mappings in sequence
Example 21. Using import_model_mappings inside sequence
class Address < Lutaml::Model::Serializable
attribute :street, :string
attribute :city, :string
attribute :zip, :string
xml do
no_root
map_element :street, to: :street
map_element :city, to: :city
map_element :zip, to: :zip
end
end
class Person < Lutaml::Model::Serializable
attribute :name, :string
import_model_attributes Address
xml do
root "Person"
map_element :name, to: :name
sequence do
import_model_mappings Address
end
end
end<Person>
<name>John Doe</name>
<street>123 Main St</street>
<city>Metropolis</city>
<zip>12345</zip>
</Person>XSD type override patterns
Using xsd_type for IDs
Example 22. Specify XSD types for ID/IDREF
class Product < Lutaml::Model::Serializable
attribute :product_id, :string, xsd_type: 'xs:ID'
attribute :category_ref, :string, xsd_type: 'xs:IDREF'
xml do
element 'product'
map_attribute 'id', to: :product_id
map_attribute 'categoryRef', to: :category_ref
end
end
# Generated XSD:
# <xs:attribute name="id" type="xs:ID"/>
# <xs:attribute name="categoryRef" type="xs:IDREF"/>Using xsd_type for custom types
Example 23. Override automatic XSD type inference
class Document < Lutaml::Model::Serializable
attribute :language, :string, xsd_type: 'xs:language'
attribute :content_type, :string, xsd_type: 'xs:token'
xml do
element 'document'
map_attribute 'lang', to: :language
map_attribute 'contentType', to: :content_type
end
endType-level namespace pattern
Type with own namespace
Example 24. Custom type declaring its namespace
# Define namespace for email types
class EmailNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/types/email'
prefix_default 'email'
end
# Define type with namespace
class EmailType < Lutaml::Model::Type::String
xml do
namespace EmailNamespace (1)
xsd_type 'EmailAddress' (2)
end
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
element 'contact'
namespace 'https://example.com/contact', 'c'
map_element 'email', to: :email # Uses EmailNamespace from EmailType
end
end
contact = Contact.new(email: "USER@EXAMPLE.COM")
puts contact.to_xml
# => <c:contact xmlns:c="https://example.com/contact"
# xmlns:email="https://example.com/types/email">
# <email:email>user@example.com</email:email>
# </c:contact>
# Round-trip deserialization works
parsed = Contact.from_xml(contact.to_xml)
parsed.email # => "user@example.com"| 1 | Namespace directive associates EmailNamespace with this type |
| 2 | XSD type name for schema generation |
Multi-namespace document with Type namespaces
Example 25. Dublin Core metadata in document
# Define namespaces
class DocumentNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/document'
prefix_default 'doc'
end
class DublinCoreNamespace < Lutaml::Model::XmlNamespace
uri 'http://purl.org/dc/elements/1.1/'
prefix_default 'dc'
end
# Define DC types with namespaces
class DcTitleType < Lutaml::Model::Type::String
xml do
namespace DublinCoreNamespace
xsd_type 'titleType'
end
end
class DcCreatorType < Lutaml::Model::Type::String
xml do
namespace DublinCoreNamespace
xsd_type 'creatorType'
end
end
# Use in document model
class Document < Lutaml::Model::Serializable
namespace DocumentNamespace
attribute :title, DcTitleType
attribute :creator, DcCreatorType
attribute :content, :string
xml do
root 'document'
map_element 'title', to: :title # Uses DublinCoreNamespace
map_element 'creator', to: :creator # Uses DublinCoreNamespace
map_element 'content', to: :content # No type namespace
end
end
doc = Document.new(
title: 'Example Document',
creator: 'John Doe',
content: 'Document content'
)
puts doc.to_xml
# => <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</content>
# </doc:document>
# Round-trip works correctly
parsed = Document.from_xml(doc.to_xml)
parsed == doc # => trueMulti-namespace document with consolidated declarations
Example 26. Using namespace_scope for cleaner multi-namespace XML
# Define 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 DctermsNamespace < Lutaml::Model::XmlNamespace
uri "http://purl.org/dc/terms/"
prefix_default "dcterms"
end
# Define types with namespaces
class DcTitleType < Lutaml::Model::Type::String
xml do
namespace DcNamespace
end
end
class DctermsCreatedType < Lutaml::Model::Type::DateTime
xml do
namespace DctermsNamespace
end
end
class VcardVersion < Lutaml::Model::Type::String
xml do
namespace VcardNamespace
end
end
# Use namespace_scope to consolidate declarations
class Vcard < Lutaml::Model::Serializable
namespace VcardNamespace
attribute :version, VcardVersion
attribute :title, DcTitleType
attribute :full_name, :string
attribute :created, DctermsCreatedType
xml do
root "vCard"
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
# => <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>
# Round-trip deserialization works
parsed = Vcard.from_xml(vcard.to_xml)
parsed == vcard # => true| 1 | All three namespaces declared at root for cleaner output |
schemaLocation pattern
Automatic xsi:schemaLocation
Example 27. Using schema_location metadata attribute
class CeramicNamespace < Lutaml::Model::XmlNamespace
uri 'http://example.com/ceramic'
prefix_default 'cera'
element_form_default :qualified
end
class ColorNamespace < Lutaml::Model::XmlNamespace
uri 'http://example.com/color'
prefix_default 'clr'
end
class Ceramic < Lutaml::Model::Serializable
attribute :type, :string
attribute :glaze, :string
attribute :color, :string
xml do
root 'Ceramic'
namespace CeramicNamespace
map_element 'Type', to: :type # Inherits parent namespace (qualified)
map_element 'Glaze', to: :glaze
map_attribute 'color', to: :color # Per W3C, attributes don't inherit namespace
end
end
xml_content = <<~XML
<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>
XML
c = Ceramic.from_xml(xml_content)
schema_loc = c.schema_location # Automatically captured
# Round-trip with schema location preserved
new_c = Ceramic.new(
type: "Porcelain",
glaze: "Clear",
color: "navy-blue",
schema_location: schema_loc
)
puts new_c.to_xml
# xsi:schemaLocation automatically included