General
General
XML is a widely used structured serialization format standardized by the W3C.
At a high level, XML defines the following primitives:
-
XML element (
<element-name>content</element-name>) -
XML attribute (
<element-name … attribute-name="attribute value">) -
XML namespace (
xmlns='namespace-uri') -
XSD (XML Schema) constructs:
-
XML simple type: primitive values (
xs:…) -
XML complex type: structural definition (an element can require a complex type, complex types can be constructed from other types)
-
XML complex type declaration definitions: sequence, order, etc.
-
It is imperative that the developer fully understands these concepts before embarking on developing XML mappings for models.
XML elements and XML types
XML elements and XML types (for Lutaml::Model)
General
In Lutaml::Model, XML serialization mappings are defined using the xml block.
Syntax:
class Example < Lutaml::Model::Serializable
xml do
# Type-level methods
# Mapping methods
end
end Defining element name (element)
The element method is the primary way to declare the XML element name ("tag name") for an XML element.
The root method was previously used for the same purpose as element, and is now an alias to element. It is considered deprecated usage due to more accurate naming of element. |
An XML mapping that does not use the element declaration means it is an "XML type".
If element is not given, but used as a root of an XML element without a tag name defined (an ad-hoc tag name can be defined in a mapping), then the snake-cased class name will be used as the tag name. |
element 'example' sets the tag name for in XML as <example>…</example>. Syntax:
xml do
element 'element-name'
endexampleclass Example < Lutaml::Model::Serializable
xml do
element 'example'
end
end> Example.new.to_xml
> #<example></example>class Ceramic < Lutaml::Model::Serializable
attribute :type, :string
xml do
element 'ceramic'
map_element 'type', to: :type
end
end
puts Ceramic.new(type: "Porcelain").to_xml
# => <ceramic><type>Porcelain</type></ceramic>The root method is maintained as a backward-compatible alias to element that also supports the mixed: and ordered: options.
| In v0.8.0 onwards, these options are deprecated in favor of:
|
Syntax:
xml do
root 'element-name', mixed: false, ordered: false
endValues:
mixed-
(optional)
trueto enable mixed content (text + elements),falseotherwise (default) ordered-
(optional)
trueto preserve element order,falseotherwise (default)
root with optionsclass Paragraph < Lutaml::Model::Serializable
attribute :bold, :string, collection: true
attribute :italic, :string
xml do
root 'p', mixed: true # Enable mixed content
map_element 'bold', to: :bold
map_element 'i', to: :italic
end
end Declaring an XML type (element omitted)
For XML type-only models (models used only as embedded types without their own element), simply omit the element declaration.
Syntax:
class Address < Lutaml::Model::Serializable
xml do
# No element() or root() call - this is a type-only model
sequence do
map_element 'street', to: :street
map_element 'city', to: :city
end
end
end Type-only models can only be parsed when embedded in parent models, not standalone. Attempting to call Address.from_xml(xml) will raise NoRootMappingError. |
The no_root method is deprecated.
Syntax:
xml do
no_root
endno_root method (deprecated)class Address < Lutaml::Model::Serializable
xml do
no_root # DEPRECATED
map_element 'street', to: :street
end
endWhen no_root is used, only map_element can be used because without a root element there cannot be attributes.
class NameAndCode < Lutaml::Model::Serializable
attribute :name, :string
attribute :code, :string
xml do
no_root
map_element "code", to: :code
map_element "name", to: :name
end
end<name>Name</name>
<code>ID-001</code>> parsed = NameAndCode.from_xml(xml)
> # <NameAndCode:0x0000000107a3ca70 @code="ID-001", @name="Name">
> parsed.to_xml
> # <code>ID-001</code><name>Name</name> Mixed content elements (mixed_content method)
The mixed_content method explicitly enables mixed content mode.
Mixed content means that the XML element or type is whitespace and order sensitive, and therefore preserves them.
This is most typically used encoding rich-text or semantically-tagged text.
<description><p>My name is <bold>John Doe</bold>, and I'm <i>28</i> years old</p></description>A mixed content mode:
-
Preserves text nodes interspersed with elements
-
Automatically enables ordered mode
-
Required for rich text content
Syntax:
xml do
element 'element-name'
mixed_content # Enables mixed content + ordered
endmixed_content explicitlyclass RichText < Lutaml::Model::Serializable
attribute :bold, :string, collection: true
attribute :italic, :string, collection: true
xml do
element 'text'
mixed_content # Explicit mixed content declaration
map_element 'b', to: :bold
map_element 'i', to: :italic
end
end
xml_input = "<text>This is <b>bold</b> and <i>italic</i> text</text>"
parsed = RichText.from_xml(xml_input)
# Preserves: "This is ", "<b>bold</b>", " and ", "<i>italic</i>", " text" (DEPRECATED) Mixed content declaration (root method with mixed:)
To map this to Lutaml::Model we can use the mixed option in either way:
-
when defining the model;
-
when referencing the model.
| This feature is not supported by Shale. |
To specify mixed content, the mixed: true option needs to be set at the xml block’s root method.
(DEPRECATED) Syntax:
xml do
root 'xml_element_name', mixed: true
endmixed to treat root as mixed contentclass Paragraph < Lutaml::Model::Serializable
attribute :bold, :string, collection: true # allows multiple bold tags
attribute :italic, :string
xml do
root 'p', mixed: true
map_element 'bold', to: :bold
map_element 'i', to: :italic
end
end> Paragraph.from_xml("<p>My name is <bold>John Doe</bold>, and I'm <i>28</i> years old</p>")
> #<Paragraph:0x0000000104ac7240 @bold="John Doe", @italic="28">
> Paragraph.new(bold: "John Doe", italic: "28").to_xml
> #<p>My name is <bold>John Doe</bold>, and I'm <i>28</i> years old</p>Ordered content
ordered: true maintains the order of XML Elements, while mixed: true preserves the order of XML Elements and Content.
When both options are used, mixed: true takes precedence. |
To specify ordered content, the ordered: true option needs to be set at the xml block’s root method.
Syntax:
xml do
root 'xml_element_name', ordered: true
endordered to treat root as ordered contentclass 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>> instance = RootOrderedContent.from_xml(xml)
> # <RootOrderedContent:0x0000000104ac7240 @bold="bell", @italic="384,400 km", @underline="Moon">
> instance.to_xml
> # <RootOrderedContent>
# <underline>Moon</underline>
# <italic>384,400 km</italic>
# <bold>bell</bold>
# </RootOrderedContent>Without Ordered True:
class RootOrderedContent < Lutaml::Model::Serializable
attribute :bold, :string
attribute :italic, :string
attribute :underline, :string
xml do
root "RootOrderedContent"
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>> instance = RootOrderedContent.from_xml(xml)
> # <RootOrderedContent:0x0000000104ac7240 @bold="bell", @italic="384,400 km", @underline="Moon">
> instance.to_xml # The order now follows attribute declaration order
> # <RootOrderedContent>
# <bold>bell</bold>
# <italic>384,400 km</italic>
# <underline>Moon</underline>
# </RootOrderedContent>XML content mapping
XML content mapping
Mapping elements
General
The map_element method maps an XML element to a data model attribute.
<name> tag in <example><name>John Doe</name></example>. The value will be set to John Doe. Syntax:
xml do
map_element 'xml_element_name', to: :name_of_attribute
endname tag to the name attributeclass Example < Lutaml::Model::Serializable
attribute :name, :string
xml do
root 'example'
map_element 'name', to: :name
end
end<example><name>John Doe</name></example>> Example.from_xml(xml)
> #<Example:0x0000000104ac7240 @name="John Doe">
> Example.new(name: "John Doe").to_xml
> #<example><name>John Doe</name></example>If an element is mapped to a model object with the XML root tag name set, the mapped tag name will be used as the root name, overriding the root name.
class RecordDate < Lutaml::Model::Serializable
attribute :content, :string
xml do
root "recordDate"
map_content to: :content
end
end
class OriginInfo < Lutaml::Model::Serializable
attribute :date_issued, RecordDate, collection: true
xml do
root "originInfo"
map_element "dateIssued", to: :date_issued
end
end> RecordDate.new(date: "2021-01-01").to_xml
> #<recordDate>2021-01-01</recordDate>
> OriginInfo.new(date_issued: [RecordDate.new(date: "2021-01-01")]).to_xml
> #<originInfo><dateIssued>2021-01-01</dateIssued></originInfo>Using elements in different namespaces
General
When elements need different namespaces, create separate model classes for each namespace. The namespace: parameter on map_element and map_attribute has been removed. See the migration guide for details.
Elements with different namespaces
Create child model classes for elements in different namespaces:
xml do
# Parent model namespace
namespace ParentNamespace
map_element 'child', to: :child # Child type determines namespace
endclass 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
# Child model with its own namespace
class Glaze < Lutaml::Model::Serializable
attribute :value, :string
xml do
element 'glaze'
namespace GlazeNamespace # Model-level namespace
map_content to: :value
end
end
class Ceramic < Lutaml::Model::Serializable
attribute :type, :string
attribute :glaze, Glaze # Typed attribute
xml do
element 'ceramic'
namespace CeramicNamespace
# This element uses parent namespace (follows element_form_default)
map_element 'type', to: :type
# This element uses Glaze's namespace (GlazeNamespace)
map_element 'glaze', to: :glaze
end
end
puts Ceramic.new(type: "Porcelain", glaze: Glaze.new(value: "Celadon")).to_xml
# => <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>Inheriting parent namespace
Use element_form_default :qualified on the namespace class to make child elements inherit the parent namespace.
class MyNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/my'
prefix_default 'my'
element_form_default :qualified # Children inherit namespace
endelement_form_default :qualified for namespace inheritanceclass CeramicNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/ceramic'
prefix_default 'cer'
element_form_default :qualified # All child elements inherit namespace
end
class Ceramic < Lutaml::Model::Serializable
attribute :type, :string
attribute :special_type, :string
xml do
element 'ceramic'
namespace CeramicNamespace
# Both elements inherit parent namespace (qualified)
map_element 'type', to: :type
map_element 'specialType', to: :special_type
end
end
puts Ceramic.new(type: "Porcelain", special_type: "Fine").to_xml
# => <cer:ceramic xmlns:cer="https://example.com/ceramic">
# <cer:type>Porcelain</cer:type>
# <cer:specialType>Fine</cer:specialType>
# </cer:ceramic>Mapping attributes
General
The map_attribute method maps an XML attribute to a data model attribute.
Syntax:
xml do
map_attribute 'xml_attribute_name', to: :name_of_attribute
endmap_attribute to map the value attributeThe following class will parse the XML snippet below:
class Example < Lutaml::Model::Serializable
attribute :value, :integer
xml do
root 'example'
map_attribute 'value', to: :value
end
end<example value="12"><name>John Doe</name></example>> Example.from_xml(xml)
> #<Example:0x0000000104ac7240 @value=12>
> Example.new(value: 12).to_xml
> #<example value="12"></example>The map_attribute method does not inherit the root element’s namespace. To specify a namespace for an attribute, please explicitly declare the namespace and prefix in the map_attribute method.
The following class will parse the XML snippet below:
class TechXmiXmlNamespace < Lutaml::Model::Xml::Namespace
uri "http://www.tech.co/XMI"
default_prefix "xl"
end
class TechXmiIntegerType < Lutaml::Model::Value::String
xml_namespace TechXmiXmlNamespace
end
class Attribute < Lutaml::Model::Serializable
attribute :value, TechXmiIntegerType
xml do
root 'example'
map_attribute 'value', to: :value
end
end<example xl:value="20" xmlns:xl="http://www.tech.co/XMI"></example>> Attribute.from_xml(xml)
> #<Attribute:0x0000000109436db8 @value=20>
> Attribute.new(value: 20).to_xml
> #<example xmlns:xl=\"http://www.tech.co/XMI\" xl:value=\"20\"/>Namespace on attribute
If the namespace is defined on a model attribute that already has a namespace, the mapped namespace will be given priority over the one defined in the class.
Syntax (with reuseable XmlNamespace):
xml do
map_element 'xml_element_name', to: :name_of_attribute,
namespace: ExampleXmlNamespaceClass
endWhere:
namespace-
The XML namespace used by this element, as an XmlNamespace class
Syntax (ad-hoc definition of namespace, results in an anonymous XmlNamespace class):
xml do
map_element 'xml_element_name', to: :name_of_attribute,
namespace: 'http://example.com/namespace',
prefix: 'prefix'
endWhere:
namespace-
The XML namespace used by this element, as a URI string
prefix-
The XML namespace prefix used by this element (optional)
namespace option to set the namespace for an elementIn this example, glz will be used for Glaze if it is added inside the Ceramic class, and glaze will be used otherwise.
class GlazeXmlNamespace < Lutaml::Model::XmlNamespace
uri 'http://example.com/glaze'
default_prefix 'glz'
end
class CeramicXmlNamespace < Lutaml::Model::XmlNamespace
uri 'http://example.com/ceramic'
default_prefix 'cera'
end
class OldGlazeXmlNamespace < Lutaml::Model::XmlNamespace
uri 'http://example.com/old_glaze'
default_prefix 'glaze'
end
class Ceramic < Lutaml::Model::Serializable
attribute :type, :string
attribute :glaze, Glaze
xml do
element 'Ceramic'
namespace CeramicXmlNamespace
map_element 'Type', to: :type
# This will use the GlazeXmlNamespace through the Glaze class
map_element 'Glaze', to: :glaze
end
end
class Glaze < Lutaml::Model::Serializable
attribute :color, :string
attribute :temperature, :integer
xml do
element 'Glaze'
namespace OldGlazeXmlNamespace
map_element 'color', to: :color
map_element 'temperature', to: :temperature
end
end<Ceramic xmlns='http://example.com/ceramic'>
<Type>Porcelain</Type>
<glz:Glaze xmlns='http://example.com/glaze'>
<color>Clear</color>
<temperature>1050</temperature>
</glz:Glaze>
</Ceramic>> # Using the original Glaze class namespace
> Glaze.new(color: "Clear", temperature: 1050).to_xml
> #<glaze:Glaze xmlns="http://example.com/old_glaze"><color>Clear</color><temperature>1050</temperature></glaze:Glaze>
> # Using the Ceramic class namespace for Glaze
> Ceramic.from_xml(xml_file)
> #<Ceramic:0x0000000104ac7240 @type="Porcelain", @glaze=#<Glaze:0x0000000104ac7240 @color="Clear", @temperature=1050>>
> Ceramic.new(type: "Porcelain", glaze: Glaze.new(color: "Clear", temperature: 1050)).to_xml
> #<Ceramic xmlns="http://example.com/ceramic"><Type>Porcelain</Type><glz:Glaze xmlns="http://example.com/glaze"><color>Clear</color><temperature>1050</temperature></glz:Glaze></Ceramic>Mapping content
Content represents the text inside an XML element, inclusive of whitespace.
The map_content method maps an XML element’s content to a data model attribute.
Syntax:
xml do
map_content to: :name_of_attribute
endmap_content to map content of the description tagThe following class will parse the XML snippet below:
class Example < Lutaml::Model::Serializable
attribute :description, :string
xml do
root 'example'
map_content to: :description
end
end<example>John Doe is my moniker.</example>> Example.from_xml(xml)
> #<Example:0x0000000104ac7240 @description="John Doe is my moniker.">
> Example.new(description: "John Doe is my moniker.").to_xml
> #<example>John Doe is my moniker.</example>Mapping entire XML element into an attribute
The map_all tag in XML mapping captures and maps all content within an XML element into a single attribute in the target Ruby object.
The use case for map_all is to tell Lutaml::Model to not parse the content of the XML element at all, and instead handle it as an XML string.
| The corresponding method for key-value formats is at [key-value-map-all]. |
| Notice that usage of mapping all will lead to incompatibility between serialization formats, i.e. the raw string content will not be portable as objects are across different formats. |
This is useful in the case where the content of an XML element is not to be handled by a Lutaml::Model::Serializable object.
This feature is commonly used with custom methods or a custom model object to handle the content.
This includes:
-
nested tags
-
attributes
-
text nodes
The map_all tag is exclusive and cannot be combined with other mappings (map_element, map_content) except for map_attribute for the same element, ensuring it captures the entire inner XML content.
An error is raised if map_all is defined alongside any other mapping in the same XML mapping context. |
Syntax:
xml do
map_all to: :name_of_attribute
endmap_allclass ExampleMapping < Lutaml::Model::Serializable
attribute :description, :string
xml do
map_all to: :description
end
end<ExampleMapping>Content with <b>tags</b> and <i>formatting</i>.</ExampleMapping>> parsed = ExampleMapping.from_xml(xml)
> puts parsed.all_content
# "Content with <b>tags</b> and <i>formatting</i>."Mapping CDATA nodes
CDATA is an XML feature that allows the inclusion of text that may contain characters that are unescaped in XML.
While CDATA is not preferred in XML, it is sometimes necessary to handle CDATA nodes for both input and output.
| The W3C XML Recommendation explicitly encourages escaping characters over usage of CDATA. |
Lutaml::Model supports the handling of CDATA nodes in XML in the following behavior:
-
When an attribute contains a CDATA node with no text:
-
On reading: The node (CDATA or text) is read as its value.
-
On writing: The value is written as its native type.
-
-
When an XML mapping sets
cdata: trueonmap_elementormap_content:-
On reading: The node (CDATA or text) is read as its value.
-
On writing: The value is written as a CDATA node.
-
-
When an XML mapping sets
cdata: falseonmap_elementormap_content:-
On reading: The node (CDATA or text) is read as its value.
-
On writing: The value is written as a text node (string).
-
Syntax:
xml do
map_content to: :name_of_attribute, cdata: (true | false)
map_element :name, to: :name, cdata: (true | false)
endcdata to map CDATA contentThe following class will parse the XML snippet below:
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><![CDATA[Lutaml]]></title><note>Careful</note></example>> Example.from_xml(xml)
> #<Example:0x0000000104ac7240 @name="John" @description="here is the description" @title="Lutaml" @note="Careful">
> Example.new(name: "John", description: "here is the description", title: "Lutaml", note: "Careful").to_xml
> #<example><name><![CDATA[John]]></name><![CDATA[here is the description]]><title>Lutaml</title><note>Careful</note></example>Example for mapping
The following class will parse the XML snippet below:
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:0x0000000104ac7240 @name="Porcelain Vase", @description=" with celadon glaze.", @temperature=1200>
> Ceramic.new(name: "Porcelain Vase", description: " with celadon glaze.", temperature: 1200).to_xml
> #<ceramic temperature="1200"><name>Porcelain Vase</name> with celadon glaze.</ceramic>XML types (for Lutaml::Model::Value)
Value namespaces
Type-level namespaces are particularly useful for:
-
Reusable types that belong to specific namespaces (e.g., Dublin Core properties, custom XSD types)
-
Multi-namespace document structures (e.g., Office Open XML, Dublin Core metadata)
-
XSD schema generation with proper namespace imports
-
W3C-compliant round-trip serialization and deserialization
Value xml block (unified API)
Custom value types declare their XML configuration using the unified xml do … end block.
Syntax:
class CustomType < Lutaml::Model::Type::Value
xml do (1)
namespace CustomNamespace (2)
xsd_type 'CustomType' (3)
end
def self.cast(value)
# Type conversion logic
end
end| 1 | The xml block provides unified XML configuration (same API as Model classes) |
| 2 | The namespace directive associates an XmlNamespace class |
| 3 | The xsd_type directive sets the XSD type name for schema generation |
Where,
namespace-
Directive inside
xmlblock that accepts anXmlNamespaceclass. This namespace will be applied to any element or attribute using this type, unless overridden by explicit mapping namespace. Works for both serialization and deserialization. xsd_type-
Directive inside
xmlblock that sets the XSD type name. If not specified, defaults todefault_xsd_typefrom the parent class (e.g.,xs:stringforType::String).
The old class-level xml_namespace and xsd_type directives are deprecated. Use the unified xml do … end block for new code. |
Type-level namespaces are resolved during both serialization and deserialization:
During serialization (to_xml):
-
When an element or attribute uses a custom type with namespace
-
The type’s namespace is consulted if no explicit mapping namespace exists
-
Namespace declarations are added to the XML document root
-
Elements/attributes are prefixed according to namespace resolution priority
During deserialization (from_xml):
-
Namespace-qualified elements/attributes are matched against type namespaces
-
Both prefixed (
dc:title) and default namespace elements are handled -
Type namespaces work with
namespace: :inheritand explicit mappings
class EmailNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/types/email'
prefix_default 'email'
end
class EmailType < Lutaml::Model::Type::String
xml do
namespace EmailNamespace
xsd_type 'EmailAddress'
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
root 'contact'
map_element 'email', to: :email # Uses EmailNamespace automatically
end
end
# Serialization output:
contact = Contact.new(email: "user@example.com")
puts contact.to_xml
# => <contact xmlns:email="https://example.com/types/email">
# <email:email>user@example.com</email:email>
# </contact>
# Deserialization (round-trip):
parsed = Contact.from_xml(contact.to_xml)
parsed.email # => "user@example.com"
parsed === contact # => trueInstance serialization
General
XML serialization is controlled via the to_xml method on Lutaml::Model::Serializable and Lutaml::Model::Value objects. instances.
Syntax:
instance.to_xml(options)Where,
options-
Hash of serialization options (see below for details)
Namespace prefix behavior
General
The prefix: option in to_xml controls whether the root element’s namespace is rendered as a default namespace (no prefix) or with a prefix.
This is a serialization-time decision that allows the same model to output clean W3C-compliant XML (default namespace) or prefixed XML for legacy system compatibility.
| Only the root element’s own namespace can be set as default. Other namespaces in scope MUST use their defined prefixes. |
Default behavior: clean XML with default namespace
By default, to_xml renders the root element’s namespace as a default namespace (xmlns="…") with no prefix. This produces clean, W3C-compliant XML.
| A namespace is only rendered if and only if the element itself is assigned an XML namespace. |
Syntax:
instance.to_xml (1)| 1 | No prefix: option uses default namespace (no prefix) |
class AppNamespace < Lutaml::Model::XmlNamespace
uri 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties'
prefix_default 'app'
end
class Properties < Lutaml::Model::Serializable
attribute :template, :string
xml do
root "Properties"
namespace AppNamespace
map_element "Template", to: :template
end
end
props = Properties.new(template: "Normal.dotm")
puts props.to_xmlOutput:
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">
<Template>Normal.dotm</Template>
</Properties> Clean XML with no prefixes. The namespace is declared as default (xmlns="…"). |
Using defined default prefix
To use the prefix defined in XmlNamespace.prefix_default of the element, pass prefix: true:
Syntax:
instance.to_xml(prefix: true) (1)| 1 | Uses prefix_default from XmlNamespace class |
props = Properties.new(template: "Normal.dotm")
puts props.to_xml(prefix: true)Output:
<app:Properties xmlns:app="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">
<app:Template>Normal.dotm</app:Template>
</app:Properties>| All elements in the same namespace use the "app" prefix. |
Using custom prefix
To use a specific custom prefix (overriding prefix_default), pass a string:
Syntax:
instance.to_xml(prefix: "custom") (1)| 1 | Uses provided custom prefix string |
props = Properties.new(template: "Normal.dotm")
puts props.to_xml(prefix: "extended")Output:
<extended:Properties xmlns:extended="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">
<extended:Template>Normal.dotm</extended:Template>
</extended:Properties> Custom prefix of "extended" used instead of the original "app". |
to_xml method prefix: option values
The prefix: option accepts the following values:
| Value | Behavior |
|---|---|
(not specified) or | Use default namespace (xmlns="…") with no prefix. This is the default behavior. |
| Use |
| Use the provided custom prefix string |
Element and attribute qualification
General
XML namespace qualification determines whether elements and attributes in instance documents must include namespace prefixes. Following W3C XML Schema specifications, qualification is controlled at three levels:
-
Namespace-level defaults via
element_form_defaultandattribute_form_default -
Element/attribute-level overrides via
form:option -
Global elements/attributes (always qualified when in a namespace)
Qualification rules
W3C qualification semantics
Per W3C XML Schema specification:
-
Global elements (declared at schema root): Always qualified when in a namespace
-
Local elements (declared within a type): Follow
elementFormDefault -
Global attributes (declared at schema root): Always qualified when in a namespace
-
Local attributes (declared within a type): Follow
attributeFormDefault
Default behavior
The default behavior follows W3C conventions:
-
element_form_default::unqualified(local elements not prefixed) -
attribute_form_default::unqualified(local attributes not prefixed)
This means child elements and attributes are not namespace-qualified by default, even when the parent element is.
class CeramicNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/ceramic'
prefix_default 'cer'
# element_form_default defaults to :unqualified
# attribute_form_default defaults to :unqualified
end
class Ceramic < Lutaml::Model::Serializable
attribute :type, :string
attribute :glaze, :string
xml do
element 'ceramic'
namespace CeramicNamespace
map_element 'type', to: :type
map_attribute 'glaze', to: :glaze
end
end
puts Ceramic.new(type: "Porcelain", glaze: "Clear").to_xml
# => <cer:ceramic xmlns:cer="https://example.com/ceramic" glaze="Clear">
# <type>Porcelain</type>
# </cer:ceramic>
# NOTE: <cer:ceramic> is qualified (global element)
# <type> is unqualified (local element, elementFormDefault=unqualified)
# glaze="" is unqualified (local attribute, attributeFormDefault=unqualified)Namespace-level qualification control
Set qualification defaults for all local elements and attributes in the namespace.
class CeramicNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/ceramic'
prefix_default 'cer'
element_form_default :qualified # All local elements must be 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
puts Ceramic.new(type: "Porcelain", color: "White").to_xml
# => <cer:ceramic xmlns:cer="https://example.com/ceramic">
# <cer:type>Porcelain</cer:type>
# <cer:color>White</cer:color>
# </cer:ceramic>
# Now <cer:type> and <cer:color> are qualifiedElement/attribute-level qualification override
Override namespace defaults using the form: option on individual mappings.
Syntax:
xml do
map_element 'name', to: :name, form: :qualified # or :unqualified
map_attribute 'id', to: :id, form: :qualified # or :unqualified
endclass CeramicNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/ceramic'
prefix_default 'cer'
element_form_default :unqualified # Default: no prefix on local elements
end
class Ceramic < Lutaml::Model::Serializable
attribute :type, :string
attribute :glaze, :string
attribute :id, :string
xml do
element 'ceramic'
namespace CeramicNamespace
# Override specific element to be qualified
map_element 'type', to: :type, form: :qualified
# This element follows namespace default (unqualified)
map_element 'glaze', to: :glaze
# Force attribute to be qualified (unusual but supported)
map_attribute 'id', to: :id, form: :qualified
end
end
puts Ceramic.new(type: "Porcelain", glaze: "Clear", id: "C001").to_xml
# => <cer:ceramic xmlns:cer="https://example.com/ceramic" cer:id="C001">
# <cer:type>Porcelain</cer:type>
# <glaze>Clear</glaze>
# </cer:ceramic>
# NOTE: <cer:type> qualified via form: :qualified override
# <glaze> unqualified via namespace default
# cer:id qualified via form: :qualified overrideUse cases for qualification
Qualified elements (:qualified):
-
Ensures elements are unambiguous across namespace boundaries
-
Required when mixing elements from multiple namespaces
-
Common in complex, multi-namespace schemas
Unqualified elements (:unqualified):
-
Simpler, more readable XML for single-namespace documents
-
Reduces verbosity when namespace context is clear
-
W3C default for local elements
Qualified attributes (unusual):
-
Rarely needed in practice
-
Only when attributes are from different namespace than parent element
-
Most schemas use
:unqualifiedfor attributes