<?xml version="1.0" encoding="UTF-8"?>
<metanorma xmlns="https://www.metanorma.org/ns/standoc" type="semantic" version="2.6.5" schema-version="v2.0.4" flavor="ribose">
<bibdata type="standard">
<title language="en" format="text/plain">LutaML — Transforms</title>
<docidentifier primary="true" type="Ribose">RS 3003</docidentifier><docnumber>RS 3003</docnumber><date type="published"><on>2025-02-20</on></date><contributor><role type="author"/><organization>
<name>Ribose Inc.</name>
<address><formattedAddress>167-169 Great Portland Street<br/>5th Floor<br/>London<br/>W1W 5PF<br/>United Kingdom</formattedAddress></address></organization></contributor><contributor><role type="author"/><person>
<name><completename>Ronald Tse</completename></name>
<affiliation><organization>
<name>Ribose</name>
</organization></affiliation></person></contributor><contributor><role type="publisher"/><organization>
<name>Ribose Inc.</name>
<address><formattedAddress>167-169 Great Portland Street<br/>5th Floor<br/>London<br/>W1W 5PF<br/>United Kingdom</formattedAddress></address></organization></contributor><edition>1.0</edition><language>en</language><script>Latn</script><status><stage>published</stage></status><copyright><from>2025</from><owner><organization>
<name>Ribose Inc.</name>
<address><formattedAddress>167-169 Great Portland Street<br/>5th Floor<br/>London<br/>W1W 5PF<br/>United Kingdom</formattedAddress></address></organization></owner></copyright><ext><doctype>standard</doctype><flavor>ribose</flavor><editorialgroup><committee type="technical">LutaML</committee></editorialgroup><security>unrestricted</security></ext></bibdata><metanorma-extension><presentation-metadata><name>TOC Heading Levels</name><value>2</value></presentation-metadata><presentation-metadata><name>HTML TOC Heading Levels</name><value>2</value></presentation-metadata><presentation-metadata><name>DOC TOC Heading Levels</name><value>2</value></presentation-metadata><presentation-metadata><name>PDF TOC Heading Levels</name><value>2</value></presentation-metadata></metanorma-extension>
<boilerplate><copyright-statement>

<clause id="_e6d5240d-324b-88ca-a190-956ea435c32e" obligation="normative">
<p id="_96cc8bbb-9d9a-ff6c-98e9-9a7a9b0a5def">© Ribose Inc. 2025</p>
</clause>
</copyright-statement>

<legal-statement>

<clause id="_7c9b34cc-5853-55f8-f0b6-1d5149135a25" obligation="normative">
<p id="_50bbfa02-61c3-8332-bad8-61e7389d1808">All rights reserved. Unless otherwise specified, no part of this publication may be reproduced or utilized otherwise in any form or by any means, electronic or mechanical, including photocopying, or posting on the internet or an intranet, without prior written permission. Permission can be requested from the address below.</p>
</clause>
</legal-statement>

<feedback-statement>

<clause id="_836ec555-8fe8-ed0b-96d7-ba7230d4cfaa" obligation="normative">
<p id="boilerplate-name" align="left">Ribose Inc.</p>

<p id="boilerplate-address" align="left">167-169 Great Portland Street<br/>5th Floor<br/>London<br/>W1W 5PF<br/>United Kingdom<br/><br/><link target="mailto:copyright@ribose.com"/><br/><link target="https://www.ribose.com">www.ribose.com</link></p>
</clause>
</feedback-statement>
</boilerplate><sections><clause id="_scope" type="scope" obligation="normative">
<title>Scope</title>
<p id="_b9d2853d-7007-0e99-46d1-5e62ae74c14f">This document specifies the transformation capabilities in LutaML Model, which enable mapping between different model representations while maintaining data integrity and structure.</p>

<p id="_692b4da8-2d06-dda6-6f72-e49fffb2332f">It defines:</p>

<ul id="_0ccc4519-526c-7379-0a6c-16f1caab64dc"><li><p id="_5f329bda-90b2-b670-ada4-fba3389d383f">Value transformation interfaces</p>
</li>
<li><p id="_3dcf3ba9-b436-af58-9733-99903d0aa510">Model transformation patterns</p>
</li>
<li><p id="_bbb0c9ef-ede7-2a1d-9763-ec1c8cdf6771">Nested transformation rules</p>
</li>
<li><p id="_5c891805-e607-3405-7fc7-16f68b916690">Bidirectional transformation capabilities</p>
</li>
</ul>
</clause>



<terms id="_terms_and_definitions" obligation="normative">
<title>Terms and definitions</title><p id="_1fbc7bf1-a8de-c3eb-ee88-3b4cf950e61b">For the purposes of this document, the following terms and definitions apply.</p>

<term id="term-transform"><preferred><expression>
<name>transform</name>
</expression>
</preferred>
<definition><verbal-definition><p id="_d06a2dd2-da7f-e58d-42de-bc4da0fac054">operation that defines mapping rules between different model representations</p></verbal-definition></definition>
 </term>

<term id="term-value-transform"><preferred><expression>
<name>value transform</name>
</expression>
</preferred>
<definition><verbal-definition><p id="_39ee5a2e-17c5-b177-e6ba-354ab703b3ab"><concept><refterm>transform</refterm><renderterm>transform</renderterm><xref target="term-transform"/></concept> that operates on individual attribute values</p></verbal-definition></definition>
 </term>

<term id="term-model-transform"><preferred><expression>
<name>model transform</name>
</expression>
</preferred>
<definition><verbal-definition><p id="_8b96d2ea-eb65-9d1a-9bf5-ab306eb1c68c"><concept><refterm>transform</refterm><renderterm>transform</renderterm><xref target="term-transform"/></concept> that operates on entire model structures</p></verbal-definition></definition>
 </term>

<term id="term-source-model"><preferred><expression>
<name>source model</name>
</expression>
</preferred>
<definition><verbal-definition><p id="_5bb2c428-b9dd-1184-5cac-f7afe2a914df">model instance containing the original data to be transformed</p></verbal-definition></definition>
 </term>

<term id="term-target-model"><preferred><expression>
<name>target model</name>
</expression>
</preferred>
<definition><verbal-definition><p id="_7e6f64cf-f1f9-691f-0c92-4a22886652c2">model instance that will receive the transformed data</p></verbal-definition></definition>
 </term>
</terms>

<clause id="_principles_in_transformations" obligation="normative">
<title>Principles in transformations</title>
<clause id="_general" obligation="normative">
<title>General</title>
<p id="_f30adc36-c304-0bfe-5b3a-df639a965012">A LutaML model defines the internal information organization structure of an information model.</p>

<p id="_2a995cb1-b0d4-4735-e983-90f9e4cda3b2">In order to allow external users to interact with the model, it is necessary to provide a way to transform the defined model into other models:</p>

<ul id="_e6e3d652-81fd-0e44-ee03-a0cd51c3f3e5"><li><p id="_e4716466-b005-954b-5150-db0b8d46c3ef">Serialization models that represent the model in a specific serialization format. e.g. JSON, XML, YAML.</p>
</li>
<li><p id="_e9749de5-f81b-9c1c-a4a2-415a6f046157">Other LutaML models that represent information differently.</p>
</li>
<li><p id="_0ab74a26-9489-5f81-6819-bc278cb9dc96">Information models in another modelling language that represent information differently.</p>
</li>
<note id="_ebc49675-aa72-1fb5-859f-a66d1309ce9b"><p id="_eceb764c-e143-c04e-48ec-56d556feba3b">The “transform” referred here is a mapping between a source LutaML model and a target LutaML model.</p>
</note></ul>


</clause>

<clause id="_architecture" obligation="normative">
<title>Architecture</title>
<p id="_6a5ab779-d1c1-bbfa-8d5b-b3d09230184f">In LutaML, a transform is a first-order object that defines:</p>

<ul id="_49496759-0f77-b1d9-2550-586e0a06ed87"><li><p id="_04474c14-82f9-cb1b-af16-330b1f1c0a9b">source model</p>
</li>
<li><p id="_96c60d09-1c58-0bfb-ef03-aae0f35e398b">target model</p>
</li>
<li><p id="_0a566e5e-32cb-e2cd-ebce-d5c25801c756">mapping between the two models</p>
</li>
<li><p id="_17c845b3-a122-245d-f9d0-40c62860dcb9">any value transforms or processing logic</p>
</li>
</ul>

<p id="_ff66a2ce-55c3-f2fa-2c20-74f2fedb8f05">A transform can be unidirectional or bidirectional.</p>

<sourcecode id="_8aeceb8e-f038-de8e-7fe3-f7ad0bfb7b73"><name>Model transformation of a LutaML Model to another LutaML Model</name><body>╔═══════════════════════╗   ╔══════════════════╗   ╔═══════════════════════╗
║LutaML Model Class FOO ║   ║LutaML Transformer║   ║LutaML Model Class BAR ║
╚═══════════════════════╝   ╚══════════════════╝   ╚═══════════════════════╝

╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮                          ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
┆          Model        ┆                          ┆          Model        ┆
┆            │          ┆    ┌────────────────┐    ┆            │          ┆
┆   ┌────────┴──┐       ┆    │                │    ┆   ┌────────┴──┐       ┆
┆   │           │       ┆    │     Model      │    ┆   │           │       ┆
┆ Models   Value Types  ┆───►│ Transformation │───►┆ Models   Value Types  ┆
┆   │           │       ┆◄───│       &amp;        │◄───┆   │           │       ┆
┆   │           │       ┆    │ Mapping Rules  │    ┆   │           │       ┆
┆   │    ┌──────┴──┐    ┆    │                │    ┆   │    ┌──────┴──┐    ┆
┆   │    │         │    ┆    └────────────────┘    ┆   │    │         │    ┆
┆   │   String  Integer ┆                          ┆   │   String  Integer ┆
┆   │   Date    Float   ┆                          ┆   │   Date    Float   ┆
┆   │   Time    Boolean ┆                          ┆   │   Time    Boolean ┆
┆   │                   ┆                          ┆   │                   ┆
┆   └──────┐            ┆                          ┆   └──────┐            ┆
┆          │            ┆                          ┆          │            ┆
┆     Contains          ┆                          ┆     Contains          ┆
┆     more Models       ┆                          ┆     more Models       ┆
┆     (recursive)       ┆                          ┆     (recursive)       ┆
┆                       ┆                          ┆                       ┆
╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯                          ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯</body></sourcecode>

</clause>

<clause id="_model_and_value_transforms" obligation="normative">
<title>Model and value transforms</title>
<p id="_11ab0eaf-e1ae-9077-8872-f5a97496bf1f">A value transform is a transformation that operates on individual attribute values, converting from one type or format to another.</p>

<p id="_64dd4453-70d2-94b1-4205-079050e29b73">Common cases include:</p>

<ul id="_91d3d951-3daf-97ba-c7fe-29b0b322bc72"><li><p id="_273253fb-f894-8d07-de9e-c9e9c99996d2">Converting between different data types (e.g., string to date)</p>
</li>
<li><p id="_1d20df26-740f-d63c-e88f-5f2da94c8568">Splitting or combining values</p>
</li>
</ul>

<example id="_07bc021e-402b-09c3-831d-10163f75c3ef"><p id="_068ca81e-49c1-8c45-b631-716996cff0d0">Transforming:</p>

<ul id="_88af30f3-6827-ae52-56ee-c78b8cbf1258"><li><p id="_331426dc-98bd-99ed-31e1-41439cb87862">source: a string date “2025-03-15”</p>
</li>
<li><p id="_a9b18867-ddd9-589e-74c2-f8f59337ee30">target: a LutaML Value type “Date”.</p>
</li>
</ul>
</example>

<p id="_4efd6460-0342-0a1c-97fb-b959828f61a8">A model transform operates on entire model structures, allowing for comprehensive mapping of various attributes and relations within the model.</p>

<p id="_a9ececdf-5561-b0cd-4333-e2e0bf44b51c">Common cases include:</p>

<ul id="_d5c36682-6255-4494-3693-829ccd2062e8"><li><p id="_0f2cceb8-5912-773c-f3ad-6da070a95c9a">Converting between different model representations</p>
</li>
<li><p id="_1c35282a-6278-0b82-a154-e76bfed77f89">Handling nested object transformations</p>
</li>
<li><p id="_10b0d10e-58f6-78fa-cce8-53697cb94c7e">Processing collections</p>
</li>
</ul>

<example id="_a9547219-e458-0576-b5db-6bd3f7987ea5"><p id="_eeb4fd4c-dc03-6d0d-c562-fe8fb9708615">Transforming:</p>

<ul id="_239c6a8f-e167-59e9-2a36-98079bb6a71b"><li><p id="_7e850f41-299d-52a6-0a27-4bde66a41436">source: a model with attributes <tt>title</tt>, <tt>author</tt>, <tt>publication_date</tt></p>
</li>
<li><p id="_a93a0f3b-7e48-9886-cdff-303cab950823">target: a model with attributes <tt>name</tt>, <tt>creator</tt>, <tt>date</tt>, which correspond to the source model attributes.</p>
</li>
</ul>
</example>

<p id="_0218e05b-1d55-753e-8fa5-9d857a036768">A source or target model here could also be a value type. For instance, extracting structured information (a model) from a string (a value type).</p>

<example id="_66be1948-a56b-13b7-e7b5-e065b52691b7"><p id="_e7a7c7cf-e157-888d-9a35-17e4b59b8f02">Transforming:</p>

<ul id="_05def670-f3c9-4923-0a1a-936b62880d0c"><li><p id="_d6f5b510-5621-b9f5-724a-a9c23de9a3bb">source: a string set of names <tt>"Gottlieb;von;Peter;Arnold"</tt></p>
</li>
<li><p id="_ebc56646-df0f-6248-b242-5923b92c6e79">target: a structured name model that contains given names (<tt>["Peter", "Arnold"]</tt>), conjunction (<tt>"von"</tt>) and last name (<tt>"Gottlieb"</tt>).</p>
</li>
</ul>
</example>
</clause>

<clause id="_directionality" obligation="normative">
<title>Directionality</title>
<p id="_0af7098a-7d9a-9d45-ddb3-1786f68b8fd3">A transform can operate in one or both directions.</p>

<p id="_24fc3577-7e2c-bdf5-9846-d57bb6beb620">A unidirectional transform is a transformation that operates in one direction only, and cannot be reversed.</p>

<p id="_ec6751a9-5c98-20a9-04b3-9e355d4d6ad9">A bidirectional transform is a transformation that operates in both directions, allowing for reversible transformations.</p>
</clause>

<clause id="_common_use_cases" obligation="normative">
<title>Common use cases</title>
<clause id="_between_model_representations" obligation="normative">
<title>Between model representations</title>
<p id="_36b933d2-3bb1-39e7-8650-7ba579c9bc0b">This use case is demonstrated by the Plurimath gem’s handling of MathML conversion (<link target="https://github.com/plurimath/plurimath#304">plurimath/plurimath#304</link>).</p>

<p id="_c9280823-5df2-e7ec-11e8-bc7d835cbb29">In Plurimath:</p>

<ul id="_b40bc8dd-6e0d-018d-7cf9-540826978ebf"><li><p id="_41e80aa4-ce34-ea51-144d-c9e9cab927eb">Plurimath maintains an internal math model (the <tt>Formula</tt> class) for mathematical semantics</p>
</li>
<li><p id="_690c6576-9e30-4373-bd1a-09dc441b9e40">The mml gem models the MathML language specification, and provides MathML XML serialization</p>
</li>
</ul>

<p id="_4bec61dc-71e6-ea13-cc8b-3b83f25676ee">When a MathML XML document is loaded, it is transformed into a <tt>Plurimath::Formula</tt> model instance in these steps:</p>

<ol id="_43cb83e6-539e-a3aa-cf7c-0787026de834" type="arabic"><li><p id="_4be4d139-64ba-5784-adc6-3521a2727551">MathML XML ⇒ The <tt>Mml</tt> LutaML model within the <tt>mml</tt> gem</p>
</li>
<li><p id="_e94660bd-f79c-6555-6fee-00987331494f">The <tt>Mml</tt> LutaML model ⇒ The <tt>Plurimath::Formula</tt> LutaML model</p>
</li>
</ol>

<p id="_f8542767-0c91-4c9e-6bce-4cfa4163814a">This architecture means Plurimath::Math does not directly handle serialization, but can transform into the <tt>Mml</tt> model when serialization is needed.</p>

<p id="_a4debbf1-a1e5-f81f-08cc-af2c8f130efd">When a MathML XML document is saved, the process is reversed:</p>

<ol id="_b3c8d941-0322-2441-e42b-534f92b83453" type="arabic"><li><p id="_dec110b7-d399-9035-7c8c-964959a1e139">The <tt>Plurimath::Formula</tt> LutaML model ⇒ The <tt>Mml</tt> LutaML model</p>
</li>
<li><p id="_7207a19a-ac18-506c-5336-30a263753490">The <tt>Mml</tt> LutaML model within the <tt>mml</tt> gem ⇒ MathML XML</p>
</li>
</ol>
</clause>

<clause id="_between_serialization_models" obligation="normative">
<title>Between serialization models</title>
<p id="_03b633f4-e85e-538a-b94d-125220ec23ea">A common requirement is the need to handle multiple serialization formats for the same data model.</p>

<p id="_80f7cbbe-eca8-e6ca-ce9a-23fb60a6f0ea">The <link target="https://github.com/metanorma/modspec-ruby"><tt>modspec</tt> gem</link> provides a LutaML model for the OGC Modular Specification (ModSpec) requirements model, and supports XML and YAML serialization outputs (the “Native ModSpec XML/YAML format”).</p>

<p id="_0175974b-a7cf-a378-023e-6c66da4a7082">The <link target="https://github.com/metanorma/mn-requirements"><tt>mn-requirements</tt> gem</link> needs to provide a Metanorma Requirements XML serialization format for the identical ModSpec model (the “Metanorma Requirements XML format”).</p>

<p id="_d1cd4eaa-211c-4e87-6705-fc43dac81e4b">In encoding Metanorma Requirements in ModSpec, the user supplies Native ModSpec YAML which is meant to be transformed into Metanorma Requirements XML.</p>

<p id="_a7894c40-ab0f-b659-d287-dc7e1e8bd1df">The transformation process is:</p>

<ol id="_d1ecc170-41d4-59f0-0ea6-2acdd2142ccc" type="arabic"><li><p id="_47ad2950-7747-b5cb-d59b-3081b60f7822">ModSpec YAML ⇒ ModSpec LutaML model</p>
</li>
<li><p id="_f708c808-6d56-3c88-428f-b255627dc939">ModSpec LutaML model ⇒ Metanorma Requirements LutaML model</p>
</li>
<li><p id="_d17836b5-80d5-39c1-f4d5-870921731b2f">Metanorma Requirements LutaML model ⇒ Metanorma Requirements XML (the “Native Metanorma Requirements XML format”)</p>
</li>
</ol>

<p id="_44098ae4-57c3-ffce-25d0-d21c2b95eaf6">In reverse, when the user wants to extract ModSpec YAML from Metanorma Requirements XML:</p>

<ol id="_64284409-82d6-856d-83a3-54a18aa2b268" type="arabic"><li><p id="_e394098e-a49e-06cd-2cae-999aca940508">Metanorma Requirements XML ⇒ Metanorma Requirements LutaML model</p>
</li>
<li><p id="_fb4c4fff-a785-c753-37a8-949929bdc194">Metanorma Requirements LutaML model ⇒ ModSpec LutaML model</p>
</li>
<li><p id="_e8b2960a-9502-8088-26c8-516e4d63061e">ModSpec LutaML model ⇒ ModSpec YAML</p>
</li>
</ol>
</clause>

<clause id="_between_versioned_models" obligation="normative">
<title>Between versioned models</title>
<p id="_86e37947-3a35-2173-4c46-f1b30aec7e76">A common use case involves transforming between different versions of the same model as it evolves over time.</p>

<p id="_a466f95b-5250-95ed-946b-3fe378bcf75f">The <link target="https://github.com/relaton/relaton">Relaton</link> LutaML model demonstrates this pattern:</p>

<ul id="_7423c32c-2b35-97b7-2c45-70f15752eddf"><li><p id="_9573d0fe-6a52-d5ed-9f71-1d05c8f007ac">Model version information is stored in the <tt>schema-version</tt> attribute of serialized formats of Relaton.</p>
</li>
<li><p id="_5d0a0a77-6d7e-2655-58b3-92896203da7c">When an older version of the Relaton serialization is parsed, it is first interpreted by the appropriate version of the Relaton serialization LutaML model, and then transformed into the latest version of the Relaton data model.</p>
</li>
<li><p id="_bbaf71ed-ed83-07e2-9d14-c16e802a2548">A version-to-version transform handles model changes</p>
</li>
</ul>

<example id="_69a88d2d-0144-6790-bf44-7ada630b287f"><p id="_562d5fb9-ba19-5573-1d04-7436189573ad">Relaton XML/YAML version attributes:</p>

<sourcecode id="_ea3adb00-ce77-c41a-7d7d-8885f1eb4ed1" lang="xml"><body>&lt;bibdata type="standard" schema-version="1.2.9"&gt;
  ...
  &lt;ext schema-version="1.0.3"&gt;
    ...
  &lt;/ext&gt;
&lt;/bibdata&gt;</body></sourcecode>


<sourcecode id="_f9030f32-d509-2d3d-256a-e111a6a32fc3" lang="yaml"><body>id: ISO1231994
type: standard
schema_version: 1.2.9
...
ext:
  schema_version: 1.0.3
  ...</body></sourcecode>

</example>

<p id="_8d501058-d489-4688-cad5-f360eaa83aff">For transformations across multiple versions, transformations must be applied sequentially in historical order (e.g., “1.0.1” → “1.0.2” → “1.0.3”).</p>
</clause>
</clause>

<clause id="_model_transformation_patterns" obligation="normative">
<title>Model transformation patterns</title>
<clause id="_general_2" obligation="normative">
<title>General</title>
<p id="_425a55de-bdff-53e9-9e52-2e6776afa7c3">There are several common model transformation patterns:</p>

<ul id="_46431f89-6d90-f251-e244-83fa8154139c"><li><p id="_64e2db74-bc96-c5d0-c0dd-b6bfa51b6409">Generic-to-specific transformation</p>
</li>
<li><p id="_0acc0918-8dab-9069-4ef1-a2c3e84452fb">Specific-to-generic transformation</p>
</li>
<li><p id="_8cee9ce7-359a-79fd-a3ae-131550f85de4">Many-to-many transformation</p>
</li>
</ul>
</clause>

<clause id="_re_mapping_attributes" obligation="normative">
<title>Re-mapping attributes</title>
<p id="_0f8db3f1-3722-b610-e8b4-d08d31a9f8a3">When transforming between models, it is common to re-map attributes between different models without changing value types.</p>

<example id="_8e664e5e-7afd-32ad-f5a2-3cd40fa2eb1c"><p id="_75172d5d-d4c8-08aa-40b9-f96c7b89ff91">Converting a “title” attribute in a “Publication” model to a “name” attribute in a “CatalogEntry” model.</p>
</example>
</clause>

<clause id="_generic_to_specific_transformation" obligation="normative">
<title>Generic-to-specific transformation</title>
<p id="_52ae88cf-eae3-ab66-94c6-3bc83d3aa679">Transforms a general model into a more specific one.</p>

<example id="_9c353def-c30e-b3bb-66a9-b4dfcf69ad14"><p id="_ca136687-94b4-1f47-de61-6a05628096f6">Converting a general “car” model into a specialized “taxi” model.</p>
</example>
</clause>

<clause id="_specific_to_generic_transformation" obligation="normative">
<title>Specific-to-generic transformation</title>
<p id="_41a57bbd-5d20-b6ac-e4b4-7659873ddf5c">Transforms a specific model into a more general one.</p>

<example id="_24c2a37a-e660-2d78-ad69-f9a5f86a9f1f"><p id="_ebfa5999-5f4f-211a-2ac1-c54334925cab">Converting a specialized “taxi” model into a general “car” model.</p>
</example>
</clause>

<clause id="_many_to_many_transformation" obligation="normative">
<title>Many-to-many transformation</title>
<p id="_a74ea709-638b-d8f4-edb7-649b32ae4bb0">Transforms a model that can be represented in multiple ways.</p>

<example id="_0ca6a0fd-ac99-f43f-5cab-a60c3d6e2f59"><p id="_7bcb9968-4b66-2360-75d8-1519abcf0323">An amphibious vehicle model that can transform into both “car” and “boat” models.</p>
</example>
</clause>
</clause>

<clause id="_directionality_2" obligation="normative">
<title>Directionality</title>
<clause id="_general_3" obligation="normative">
<title>General</title>
<p id="_f144206a-1024-6c27-8db7-26d87222d7ad">Transforms can be configured to operate in one or both directions.</p>

<p id="_3c91f52f-c324-aa8d-f234-d5ebb7bec71d">The reversibility of a transform depends on two things:</p>

<ul id="_7fe75600-6aaf-4bbe-766d-a40470255d6f"><li><p id="_84841607-6b04-e1e0-780d-ffa5f25809fd">whether any mapping rules are one-way transforms</p>
</li>
<li><p id="_27b1d177-621b-9252-0068-1fc963a75dc3">whether the <tt>reverse_transform do</tt> block is defined</p>
</li>
</ul>
</clause>

<clause id="_simple_transforms_bidirectional" obligation="normative">
<title>Simple transforms (bidirectional)</title>
<p id="_5e86a2a8-f004-daf6-262b-aac0a9fe264b">When a transform is defined with only a <tt>transform do</tt> block that contains bidirectional mapping rules, the transform is bidirectional.</p>

<sourcecode id="_0e564acf-f417-b471-0677-dfb3028f27d4" lang="ruby"><body>class SimpleBidirectionalTransform &lt; Lutaml::Model::Transform
  source_model :source_model
  target_model :target_model

  transform do
    # mapping without value transform logic
  end
end</body></sourcecode>

</clause>

<clause id="_single_direction_transform" obligation="normative">
<title>Single direction transform</title>
<p id="_2a2f3f90-b269-8081-b01e-2264bbd406d8">When a transform is defined with only a <tt>transform do</tt> block that contains unidirectional mapping rules, the transform is unidirectional.</p>

<sourcecode id="_85238f41-b037-61f5-aec0-c0d79818a104" lang="ruby"><body>class UnidirectionalTransform &lt; Lutaml::Model::Transform
  source_model :source_model
  target_model :target_model

  transform do
    # mapping with value transform logic
  end
end</body></sourcecode>

</clause>

<clause id="_explicit_bidirectional_transform" obligation="normative">
<title>Explicit bidirectional transform</title>
<p id="_abddf9d2-f0a4-a0bc-7ef5-7e89065fbac6">When a transform is defined with a <tt>transform do</tt> block that contains unidirectional mapping rules, but also a <tt>reverse_transform do</tt> block that contains reverse unidirectional mapping rules, the transform is bidirectional.</p>

<sourcecode id="_62d154c7-30c5-a596-fd27-24641ede14b9" lang="ruby"><body>class ExplicitBidirectionalTransform &lt; Lutaml::Model::Transform
  source_model :source_model
  target_model :target_model

  transform do
    # mapping with value transform logic
  end

  reverse_transform do
    # mapping with value transform logic
  end
end</body></sourcecode>

</clause>
</clause>
</clause>

<clause id="_value_transforms" obligation="normative">
<title>Value transforms</title>
<clause id="_general_4" obligation="normative">
<title>General</title>
<p id="_204c8f06-ed40-de32-1b95-75d7d8d91662">Value transforms operate on individual attribute values, converting from one type or format to another.</p>
</clause>

<clause id="_structure" obligation="normative">
<title>Structure</title>
<p id="_5ca21e89-850a-d64f-fbdf-c47b2eca82f9">A value transform:</p>

<ul id="_9ee316ab-08ce-405b-627f-eccd3c4003e3"><li><p id="_b4aec460-1639-33cd-9c93-1a94ca5971cb">Inherits from <tt>Lutaml::Value::Transform</tt></p>
</li>
<li><p id="_87b4822c-0c90-5104-87e1-25a1eed31e3b">Defines source and target value types</p>
</li>
<li><p id="_d1489a77-6896-9d5b-8381-19b7c6b6e7f1">Implements the <tt>transform</tt> method</p>
</li>
<li><p id="_d4fad203-c576-0bc5-f192-bbc06b1a9227">Implement <tt>reverse_transform</tt> method if bidirectional</p>
</li>
</ul>

<p id="_77d9e0c7-9a26-9053-b470-498478c5084c">Syntax:</p>

<sourcecode id="_cdf0391a-2c60-4739-decd-4405f4921e83" lang="ruby"><body>class ValueTransformClass &lt; Lutaml::Value::Transform
  source_value :source_type <callout>1</callout>
  target_value :target_type <callout>2</callout>

  transform do |source_value|
    # transformation logic
  end

  reverse_transform do |target_value|
    # reverse transformation logic
  end
end</body></sourcecode>


<dl id="_f7e732de-f44c-16d2-46d2-d96df1c9a2c7"><dt><tt>source_value</tt></dt>
<dd><p id="_1f28813b-28eb-60be-bc6f-b5b705c792fd">Specifies the source value type. This can be a primitive type or a class that inherits from <tt>Lutaml::Value</tt>.</p>
</dd>
<dt><tt>target_value</tt></dt>
<dd><p id="_96e1c4af-f5e0-f045-9352-243230963d6d">Specifies the target value type. This can be a primitive type or a class that inherits from <tt>Lutaml::Value</tt>.</p>
</dd>
<dt><tt>transform</tt></dt>
<dd><p id="_61d27257-8bcb-c42f-0c87-e522b3a922a6">Defines the transformation logic.</p>
</dd>
<dt><tt>reverse_transform</tt></dt>
<dd><p id="_8d62ee84-7d74-f286-fc47-2ca95f9978d7">Defines the reverse transformation logic for bidirectional transforms.</p>
</dd>
</dl>

<example id="_4e8691cf-eed4-c0b0-e551-32c497b277de"><sourcecode id="_bee60555-df4f-b74a-a444-0bd27703264f" lang="ruby"><body># Transforms a string into a Date model
class DateFormatTransform &lt; Lutaml::Value::Transform
  source_value :string
  target_value :date_with_time

  transform do |source_value|
    Date.parse(source_value)
  end

  reverse_transform do |target_value|
    target_target_value.strftime('%Y-%m-%d')
  end
end</body></sourcecode>


<p id="_deab58f2-888a-8375-a29f-b05ec6bc0c44">Given:</p>

<sourcecode id="_9416915c-4d18-7274-06f4-f02ef4632e2b" lang="ruby"><body>DateFormatTransform.transform('2021-01-01')
# =&gt; #&lt;Date: 2021-01-01 ((2459216j,0s,0n),+0s,2299161j)&gt;

DateFormatTransform.reverse_transform(Date.new(2021, 1, 1))
# =&gt; "2021-01-01"</body></sourcecode>

</example>
</clause>
</clause>

<clause id="_model_transforms" obligation="normative">
<title>Model transforms</title>
<clause id="_general_5" obligation="normative">
<title>General</title>
<p id="_27b912da-b239-07a7-8c02-cd7d1c2426b5">Model transforms operate on entire model structures, mapping attributes between different model representations.</p>
</clause>

<clause id="_base_requirements" obligation="normative">
<title>Base requirements</title>
<p id="_9989c610-7be3-c3b6-0ea8-d5953c83fa16">A model transform:</p>

<ul id="_556a0a04-fbc7-2d0f-d0c3-5623c3684dbc"><li><p id="_409a389f-5dc3-9276-8739-a422cb73f922">Inherits from <tt>Lutaml::Model::Transform</tt></p>
</li>
<li><p id="_a50422d7-eec7-74ad-7525-38c74a4c2394">Specifies source and target models</p>
</li>
<li><p id="_3308a286-5a75-f708-9f25-edd72db794d0">Defines mapping rules within a transform block</p>
</li>
<li><p id="_934c3573-ada5-3988-ea6f-30f6b5d284de">Declares directionality</p>
</li>
</ul>

<p id="_8f434a35-0df3-cda3-5d2f-da7492b0c1a7">Syntax:</p>

<sourcecode id="_397486de-069d-825d-e889-be1544de3f74" lang="ruby"><body>class TransformClass &lt; Lutaml::Model::Transform
  source_model :source_model <callout>1</callout>
  target_model :target_model <callout>2</callout>

  transform do
    # mapping rules
  end

  reverse_transform do
    # reverse mapping rules
  end
end</body></sourcecode>


<dl id="_c4278a4f-e901-4bd6-fcbc-c4f8b6d19a3c"><dt><tt>source_model</tt></dt>
<dd><p id="_0a8029af-8807-224f-7bb4-9fda8fbae528">Specifies the source model class.</p>
</dd>
<dt><tt>target_model</tt></dt>
<dd><p id="_282517a5-de23-c2a0-2332-f863e0c15a9f">Specifies the target model class.</p>
</dd>
</dl>

<example id="_71007a30-1f7f-053d-d995-65dff5d6cb2c"><sourcecode id="_7dabe0e6-b858-5498-7033-48b3b4800fe3" lang="ruby"><body>class PublicationTransform &lt; Lutaml::Model::Transform
  source_model Publication
  target_model CatalogEntry

  transform do
    # mapping rules
    map from: 'title', to: 'title'
    map from: 'author', to: 'creator'
  end

  reverse_transform do
    # reverse mapping rules
    map from: 'target.title', to: 'source.title'
    map from: 'target.creator', to: 'source.author'
  end
end</body></sourcecode>


<p id="_d89fe38b-0365-2655-0adc-ee79d1e36264">Given:</p>

<sourcecode id="_d41ef43f-a06d-4232-759a-60ef7ebcc36b" lang="ruby"><body>publication = Publication.new(title: 'The Art of War', author: 'Sun Tzu')
transformed = PublicationTransform.transform(publication)
# =&gt; #&lt;CatalogEntry:0x00007f9b1b8b3b10 @title="The Art of War", @creator="Sun Tzu"&gt;

publication_transformed = PublicationTransform.reverse_transform(transformed)
# =&gt; #&lt;Publication
#      @title="The Art of War",
#      @author="Sun Tzu"&gt;</body></sourcecode>

</example>
</clause>

<clause id="_model_mapping_rules" obligation="normative">
<title>Model mapping rules</title>
<clause id="_direct_attribute_mapping" obligation="normative">
<title>Direct attribute mapping</title>
<p id="_f3701182-a659-da66-ad30-19361c3aef6e">Maps source attributes to target attributes with identical names without value modification.</p>

<p id="_2c251c51-cfa9-770a-8142-4247823ca4bd">This type of mapping is bidirectional by default.</p>

<p id="_a692963e-409b-588b-1d07-abe801bb7ac7">Syntax:</p>

<sourcecode id="_2479040c-1c69-5f63-4067-d535c27d26bc" lang="ruby"><body>map from: 'path_from_source', to: 'path_at_target', directional: :bidirectional # default
# or simply
map from: 'path_from_source', to: 'path_at_target'</body></sourcecode>


<p id="_604a767d-f640-689e-bd90-ac5051579a33">The <tt>from:</tt> and <tt>to:</tt> parameters are in the LutaML Path syntax.</p>

<example id="_71ecd4f4-d502-dc91-3055-53b084577ffb"><p id="_8715fb72-5738-1e65-2bcf-01363eeb4ba0">In LutaML Path syntax, given a source model of <tt>{ name("John Doe"), email("<link target="mailto:john@example.com"/>") }</tt>, the path to the <tt>name</tt> attribute is <tt>name</tt>, and the path to the <tt>email</tt> attribute is <tt>email</tt>.</p>
</example>

<example id="_c908273b-3e89-7b03-9313-64ab6fcbb2a7">
<name>Direct attribute mapping example</name>
<sourcecode id="_56ba5b4d-ae4e-959b-7961-4bc48ef40790" lang="ruby"><body>class Publication &lt; Lutaml::Model::Serializable
  attribute :title, :string
  attribute :author, :string
end

class CatalogEntry &lt; Lutaml::Model::Serializable
  attribute :title, :string
  attribute :author, :string
end

class PublicationTransform &lt; Lutaml::Model::Transform
  source_model Publication
  target_model CatalogEntry

  transform do
    map from: 'title', to: 'title'
    map from: 'author', to: 'author'
  end
end</body></sourcecode>

</example>
</clause>

<clause id="_attribute_renaming" obligation="normative">
<title>Attribute renaming</title>
<p id="_b825138e-29be-b57f-c7d2-10dd0e2041c0">Maps source attributes to differently named target attributes.</p>

<p id="_b106b804-228d-ddc8-476e-2054473edaee">This type of mapping is bidirectional by default.</p>

<p id="_411211f5-3059-b426-63ab-6a5325b806f0">Syntax:</p>

<sourcecode id="_64ab1e01-ed39-3d18-f231-aa81e800dde0" lang="ruby"><body>map from: 'old_name', to: 'new_name'</body></sourcecode>


<example id="_332446c4-b65f-ff76-194c-08028366110e"><sourcecode id="_8758fff1-6a8d-4561-9051-09180daad9ce" lang="ruby"><body>class Person &lt; Lutaml::Model::Serializable
  attribute :name, :string
  attribute :year_born, :string
end

class User &lt; Lutaml::Model::Serializable
  attribute :name, :string
  attribute :birth_year, :string
end

class UserTransform &lt; Lutaml::Model::Transform
  source_model Person
  target_model User

  transform do
    map from: 'year_born', to: 'birth_year'
  end
end</body></sourcecode> </example>
</clause>

<clause id="_value_transformation_mapping" obligation="normative">
<title>Value transformation mapping</title>
<p id="_46ced095-59dd-a26e-a1c3-193d1a0603a2">Maps source attributes to target attributes with value transformation.</p>

<p id="_9f6df669-a72b-ed9d-49b2-7f680ded49d8">The directionality of a transformation mapping depends on the directionality of the value transform.</p>

<p id="_567f2e7e-d6c0-08ba-ab59-517dbec601e2">Syntax:</p>

<sourcecode id="_8edf05df-4f9e-c7e9-af99-d4afc9960ab8" lang="ruby"><body>map from: 'attribute', to: 'attribute', transform: TransformClass</body></sourcecode>


<example id="_a9fbe8f4-a361-de2a-2c67-93e1ee6a9641"><sourcecode id="_b59b0f06-0e63-52ac-fece-0c00d98cf982" lang="ruby"><body>class Person &lt; Lutaml::Model::Serializable
  attribute :name, :string
  attribute :birth_date, :string
end

class User &lt; Lutaml::Model::Serializable
  attribute :name, :string
  attribute :birth_date, :date_with_time
end

# This is a uni-directional transform
class DateFormatTransform &lt; Lutaml::Value::Transform
  source_value :string
  target_value :date_with_time

  transform do |source_value|
    Date.parse(source_value)
  end
end

class UserTransform &lt; Lutaml::Model::Transform
  source_model Person
  target_model User

  transform do
    # This is a uni-directional mapping
    map from: 'birth_date', to: 'birth_date', transform: DateFormatTransform
  end
end</body></sourcecode> </example>

<example id="_971410c2-1ec3-f077-cd5b-a8a2f4ed4daa"><sourcecode id="_f9fd6063-904f-1100-43f6-5354af2cfb28" lang="ruby"><body># This is a bidirectional transform
class DateFormatTransform &lt; Lutaml::Value::Transform
  source_value :string
  target_value :date_with_time

  transform do |source_value|
    Date.parse(source_value)
  end

  reverse_transform do |target_value|
    target_target_value.strftime('%Y-%m-%d')
  end
end

class UserTransform &lt; Lutaml::Model::Transform
  transform do
    # This becomes a bidirectional mapping
    map from: 'birth_date', to: 'birth_date', transform: DateFormatTransform
  end
end</body></sourcecode> </example>
</clause>
</clause>
</clause>

<clause id="_nested_transforms" obligation="normative">
<title>Nested transforms</title>
<clause id="_general_6" obligation="normative">
<title>General</title>
<p id="_a028689e-024d-d126-d774-ce826124ed3a">Nested transforms handle complex object hierarchies, allowing transformation of nested attributes and objects.</p>
</clause>

<clause id="_structure_2" obligation="normative">
<title>Structure</title>
<p id="_7edcff2e-ad7c-eb6e-daca-d0987c9ea17e">A nested transform:</p>

<ul id="_872ead66-b8c6-e8e9-ad65-bb2e56e37e3f"><li><p id="_29aec2bd-e6c9-83a5-2a01-4dfc3d7d121f">Defines mappings for nested attributes using dot notation</p>
</li>
<li><p id="_f425e0c2-0d99-62cf-b221-53e79bc5860c">Handles collections appropriately</p>
</li>
<li><p id="_cf7f914a-af29-9784-cc9d-05a98c0eb090">Supports value transforms within nested mappings</p>
</li>
</ul>
</clause>

<clause id="_nested_attribute_mapping" obligation="normative">
<title>Nested attribute mapping</title>
<p id="_d18ae1ed-b7f0-c1cd-f39c-b2426962bc46">Maps attributes within nested objects.</p>

<p id="_d91c5287-1d47-f7dd-f6ba-06fe4126de06">The directionality of a nested mapping depends on the directionality of the transform.</p>

<p id="_504ee2ae-ee9c-4c70-c784-2fb2754cd071">Syntax:</p>

<sourcecode id="_139d51ff-e320-9cf6-51c3-45f88a33eea3" lang="ruby"><body># Value to value transformation
map from: 'source_attribute', to: 'destination_attribute'

# Cross-model-value transformation
## Value to model transformation
map from: 'source_model.attribute', to: 'destination_attribute'
## Model to value transformation
map from: 'source_attribute', to: 'destination_model.attribute'

# Model to model transformation
map from: 'source_model.[...]attribute', to: 'destination_model.[...]attribute'</body></sourcecode>


<p id="_0d56a888-8941-9813-db88-3c80faba4bba">There are three scenarios in transforming nested attributes:</p>

<ol id="_a64acb9e-4e47-f86f-92a5-66f7f497760f" type="arabic"><li><p id="_0235ebbf-2d3b-f791-ab51-70230a97ea0e">Value to value transformation</p>
</li>
<li><p id="_baf99788-673c-8eca-0856-f7064e82eab1">Cross model-value transformation</p>
</li>
<li><p id="_31062681-bfcc-c466-ed3e-862b757ea2d2">Model to model transformation</p>
</li>
</ol>
</clause>

<clause id="_value_to_value_transformation" obligation="normative">
<title>Value to value transformation</title>
<p id="_49fe0a7d-e339-2dc2-0020-c65056ba4dc8">We can apply a value-to-value transformation if both the source and destination attributes are values.</p>

<p id="_04b4c44f-0e1d-34b8-30f3-6787595867ab">Suppose we have the following source and destination models.</p>

<sourcecode id="_0674c22c-fe91-4113-e544-359fc02fca71" lang="ruby"><body>class UnstructuredDateTime &lt; Lutaml::Model::Value
  attribute :value, :string
end

class StructuredDateTime &lt; Lutaml::Model::Value
  attribute :date, :string
  attribute :time, :string
end

class OldDigitalTimepiece &lt; Lutaml::Model::Serializable
  attribute :raw_time, UnstructuredDateTime
end

class NewDigitalTimepiece &lt; Lutaml::Model::Serializable
  attribute :detailed_time, StructuredDateTime
end</body></sourcecode>


<p id="_6606e409-9f24-0786-fb6d-6fed5d7cd7bb">First define a value transform:</p>

<sourcecode id="_399519f2-d578-0976-613f-ab709998f2a9" lang="ruby"><body>class DateTimeTransform &lt; Lutaml::Value::Transform
  source_value UnstructuredDateTime
  target_value StructuredDateTime

  transform do |source_value|
    # In ISO 8601-1:2019, a basic date-time string can be "{YYYY}{MM}{DD}T{hh}:{mm}:{ss}"
    date, time = source_value.value.split('T')
    StructuredDateTime.new(date: date, time: time)
  end

  reverse_transform do |target_value|
    UnstructuredDateTime.new(value: "#{target_value.date}T#{target_value.time}")
  end
end</body></sourcecode>


<p id="_21cdaf80-fd46-950f-4057-f33be19c1691">Then define a model transform:</p>

<sourcecode id="_ceb43891-b1dc-b651-2120-b74f54cb6396" lang="ruby"><body>class TimepieceTransform &lt; Lutaml::Model::Transform
  source_model OldDigitalTimepiece
  target_model NewDigitalTimepiece

  transform do
    map from: 'time', to: 'time', transform: DateTimeTransform
  end
end</body></sourcecode>


<p id="_90971dcf-75f7-bdcd-0ce1-3ec75ab21e00">The resulting transformation is demonstrated with this diagram.</p>

<sourcecode id="_90254e1f-d97e-d25d-1d06-9ce62f22b96a"><body>                            transforms via
                            DateTimeTransform
╔════════════════════════════════╗      ╔═══════════════════════════════════╗
║  UnstructuredDateTime          ║      ║  StructuredDateTime               ║
╠════════════════════════════════╣ ───► ╠═══════════════════════════════════╣
║  value: string                 ║      ║  date: string                     ║
║                                ║      ║  time: string                     ║
╚════════════════════════════════╝      ╚═══════════════════════════════════╝
       ▲                                         ▲
       │                                         │
       │has_one                                  │has_one
       │                                         │
╔════════════════════════════════╗      ╔═══════════════════════════════════╗
║ OldDigitalTimepiece            ║      ║ NewDigitalTimepiece               ║
╠════════════════════════════════╣ ───► ╠═══════════════════════════════════╣
║ raw_time: UnstructuredDateTime ║      ║ detailed_time: StructuredDateTime ║
╚════════════════════════════════╝      ╚═══════════════════════════════════╝
                            transforms via
                            TimepieceTransform
────► : Transformation processing</body></sourcecode>

</clause>

<clause id="nested-cross-model-value-transformation" obligation="normative">
<title>Cross model-value transformation</title>
<p id="_db9c6898-2f65-10cd-da1e-f601f8b15c9b">A cross-model-value transformation occurs when one of the source and destination attributes is a model and the other is a value.</p>

<p id="_bec189ca-65a2-a491-7d0c-38f5df057e52">Suppose we have a set of deeper source and destination models.</p>

<sourcecode id="_36a6f8cf-1644-5310-a040-9aa505764461" lang="ruby"><body>class UnstructuredDateTimeWithOffset &lt; Lutaml::Model::Value
  attribute :value, :string
end

class StructuredDateTimeWithOffset &lt; Lutaml::Model::Model
  attribute :date_time, StructuredDateTime
  attribute :offset, TimeOffset
end

class TimeOffset &lt; Lutaml::Model::Value
  attribute :value, :string
end

class OldDigitalTimepiece &lt; Lutaml::Model::Serializable
  attribute :raw_time, UnstructuredDateTimeWithOffset
end

class NewDigitalTimepiece &lt; Lutaml::Model::Serializable
  attribute :detailed_time, StructuredDateTimeWithOffset
end</body></sourcecode>


<p id="_f8328e7b-2fcf-23d9-bbfd-b9c2c8de37b3">Here, the <tt>UnstructuredDateTimeWithOffset</tt> model is a value, and the<tt>StructuredDateTimeWithOffset</tt> model is a model.</p>

<p id="_5771efa0-f3b8-df5e-edd8-40aed199081b">A cross-model-value transform is defined to transform between<tt>UnstructuredDateTimeWithOffset</tt> (value) and <tt>StructuredDateTimeWithOffset</tt> (model).</p>

<sourcecode id="_9bad01d8-c9d6-3809-5c9b-0f64b712315f" lang="ruby"><body>class DateTimeTransform &lt; Lutaml::Value::Transform
  source_value UnstructuredDateTimeWithOffset
  target_value StructuredDateTimeWithOffset

  # Split the string into date, time, and offset
  transform do |source_value|
    match, date, time, offset = source_value.value.match(/^(.+?)T(.+?)(?:\+(.+))?$/)
    StructuredDateTimeWithOffset.new(
      date_time: StructuredDateTime.new(date: date, time: time),
      offset: TimeOffset.new(value: offset || '00:00')
    )
  end

  reverse_transform do |target_value|
    UnstructuredDateTimeWithOffset.new(
      value:[
        target_value.date_time.date,
        "T",
        target_value.date_time.time,
        "+",
        target_value.zone.offset
      ].join("")
    )
  end
end</body></sourcecode>


<p id="_3df561d8-cd46-88d5-41f1-09a930efd8c6">Then define a model transform.</p>

<sourcecode id="_349ee1c8-0f4f-13a0-9ee2-3b4b525d2bbe" lang="ruby"><body>class TimepieceTransform &lt; Lutaml::Model::Transform
  source_model OldDigitalTimepiece
  target_model NewDigitalTimepiece

  transform do
    map from: 'raw_time', to: 'detailed_time', transform: DateTimeTransform
  end
end</body></sourcecode>


<p id="_f8b3ab68-bfc3-fc7d-9f5e-de45b969bd57">The resulting transformation is demonstrated with this diagram.</p>

<sourcecode id="_2787e22d-def0-2b37-95ef-005b93a5fa57"><body>                      ╔═════════════════════════════╗  ╔════════════════════╗
                      ║  StructuredDateTime (Model) ║  ║ TimeOffset (Value) ║
                      ╠═════════════════════════════╣  ╠════════════════════╣
                      ║  date: string               ║  ║ value: string      ║
                      ║  time: string               ║  ║                    ║
                      ╚═════════════════════════════╝  ╚════════════════════╝
                                                 ▲             ▲
                                                 │             │
                                                 │has_one      │has_one
                                                 │             │
                        transforms via           │             │
                        DateTimeTransform        │             │
╔════════════════════════════════╗      ╔═══════════════════════════════════╗
║ UnstructuredDateTimeWithOffset ║      ║ StructuredDateTimeWithOffset      ║
╠════════════════════════════════╣ ───► ╠═══════════════════════════════════╣
║ value: string                  ║      ║ date_time: StructuredDateTime     ║
║                                ║      ║ offset: TimeOffset                ║
╚════════════════════════════════╝      ╚═══════════════════════════════════╝
                    ▲                                         ▲
                    │                                         │
                    │has_one                                  │has_one
                    │                                         │
╔══════════════════════════════════════════╗      ╔═════════════════════════════════════════════╗
║ OldDigitalTimepiece                      ║      ║ NewDigitalTimepiece                         ║
╠══════════════════════════════════════════╣ ───► ╠═════════════════════════════════════════════╣
║ raw_time: UnstructuredDateTimeWithOffset ║      ║ detailed_time: StructuredDateTimeWithOffset ║
╚══════════════════════════════════════════╝      ╚═════════════════════════════════════════════╝
                                        transforms via
                                        TimepieceTransform
────► : Transformation processing</body></sourcecode>

</clause>

<clause id="nested-model-to-model-transformation" obligation="normative">
<title>Model-to-model transformation</title>
<p id="_61b74b40-fa4c-fb72-c093-f5abfb6833c9">Model-to-model transformations handle mapping between models.</p>

<p id="_779d7706-dc95-ca29-0b78-ea2e8fe1a73f">In a model-to-model transformation, a Value Transform may be used to transform the value of an attribute.</p>

<p id="_7cbe5c4c-e7c8-155a-c246-18940a22b3df">When approaching model to model transformation, the mapping can be placed at the desired level of transformation.</p>

<ol id="_217ab410-9ea0-1e28-7f4e-898ba524604c" type="arabic"><li><p id="_2b62f0d2-7e94-4a00-c6ff-f0aa90614afa">Creating the mapping at the most basic level, where either the mapping source or destination is an attribute accessible at root.</p>
<example id="_1099e59c-4498-015d-c7bd-aa4e31202459"><p id="_14c9895c-c71b-67c8-afc9-660cfe90a440">In the mapping, a <tt>time</tt> path is a root attribute, referring to the attribute of<tt>{source|target}.time</tt>.</p>
</example>
</li>
<li><p id="_69763bdd-cedf-1518-6c3c-ae0e8c717a24">Creating the mapping at the model-to-model level, where both source and destination are models, . Given a deep model hierarchy, the transformation can be applied at a shallow level (e.g. close to the underlying value) or at a deeper level (e.g. close to the top-level model, very nested).</p>
<example id="_136e611f-a1a9-dda8-1ec9-36510ab79b9b"><p id="_d8a9b1c6-c418-d6ef-89c9-e5285fc20e33">A path of <tt>clock.time</tt> refers to an inner model attribute, referring to the attribute of <tt>{source|target}.clock.time</tt>.</p>

<p id="_58ce7f04-5484-4760-72d9-c4883fadaf81">A path of <tt>computer.clock.time</tt> refers to a deeper model attribute, referring to the attribute of <tt>{source|target}.computer.clock.time</tt>.</p>
</example>
</li>
</ol>

<p id="_1fe1a8b9-4bc1-d938-8a50-226f48f70ce6">The consideration on where the mapping is placed depends on the accessibility, appropriateness and authority of the desired transformation.</p>

<p id="_c73c73f4-fe8f-6981-dc38-2d783428db53">A basic level value-to-value or cross-model-value transformation may not be practical when the source and destination models are not managed or owned by the party performing the transformation.</p>

<p id="_e694737a-1d74-609d-878c-ae4af8f46f5e">To illustrate such a scenario, we add one more model layer to the same model as in <xref target="nested-cross-model-value-transformation"/> to define <tt>ComputerClock</tt> and<tt>WallClock</tt>. Then move the transformation logic to the model level.</p>

<sourcecode id="_6353b3a2-d1ab-c3eb-f6ba-a007cd9f2d80" lang="ruby"><body>class UnstructuredDateTimeWithOffset &lt; Lutaml::Model::Value
  attribute :value, :string
end

class StructuredDateTimeWithOffset &lt; Lutaml::Model::Model
  attribute :date_time, StructuredDateTime
  attribute :offset, TimeOffset
end

class TimeOffset &lt; Lutaml::Model::Value
  attribute :value, :string
end

class OldDigitalTimepiece &lt; Lutaml::Model::Serializable
  attribute :raw_time, UnstructuredDateTimeWithOffset
end

class NewDigitalTimepiece &lt; Lutaml::Model::Serializable
  attribute :detailed_time, StructuredDateTimeWithOffset
end

class WallClock &lt; Lutaml::Model::Serializable
  attribute :timepiece, OldDigitalTimepiece
end

class ComputerClock &lt; Lutaml::Model::Serializable
  attribute :timepiece, NewDigitalTimepiece
end

class DateTimeTransform &lt; Lutaml::Value::Transform
  source_value UnstructuredDateTimeWithOffset
  target_value StructuredDateTimeWithOffset

  # Split the string into date, time, and offset
  transform do |source_value|
    match, date, time, offset = source_value.value.match(/^(.+?)T(.+?)(?:\+(.+))?$/)
    StructuredDateTimeWithOffset.new(
      date_time: StructuredDateTime.new(date: date, time: time),
      offset: TimeOffset.new(value: offset || '00:00')
    )
  end

  reverse_transform do |target_value|
    UnstructuredDateTimeWithOffset.new(
      value:[
        target_value.date_time.date,
        "T",
        target_value.date_time.time,
        "+",
        target_value.zone.offset
      ].join("")
    )
  end
end</body></sourcecode>


<p id="_6f72bb9c-9d48-189f-b9fc-66fdbddb294f">Now define a model transform between <tt>ComputerClock</tt> and <tt>WallClock</tt>.</p>

<sourcecode id="_c9ebd7b4-a34f-b592-6d7d-a951dcc9323d" lang="ruby"><body>class ClockTransform &lt; Lutaml::Model::Transform
  source_model WallClock
  target_model ComputerClock

  transform do
    map from: 'timepiece.raw_time', to: 'timepiece.detailed_time', transform: DateTimeTransform
  end
end</body></sourcecode>


<p id="_f6786d7b-6c21-ec9e-9c8b-ec81966abca2">Notice that different from the <xref target="nested-cross-model-value-transformation"/>, the transformation is now applied at the model level, where the mapping is defined at a model-to-model level where both source and destination do not own the attributes involved in transformation.</p>

<p id="_25576e90-1314-03ab-9df6-118fff587a5a">The resulting transformation is demonstrated with this diagram.</p>

<sourcecode id="_b011d280-063a-5d10-a8a8-7a9602ba4e58"><body>                      ╔═════════════════════════════╗  ╔════════════════════╗
                      ║  StructuredDateTime (Model) ║  ║ TimeOffset (Value) ║
                      ╠═════════════════════════════╣  ╠════════════════════╣
                      ║  date: string               ║  ║ value: string      ║
                      ║  time: string               ║  ║                    ║
                      ╚═════════════════════════════╝  ╚════════════════════╝
                                                 ▲             ▲
                                                 │             │
                                                 │has_one      │has_one
                                                 │             │
                        transforms via           │             │
                        DateTimeTransform        │             │
╔════════════════════════════════╗      ╔═══════════════════════════════════╗
║ UnstructuredDateTimeWithOffset ║      ║ StructuredDateTimeWithOffset      ║
╠════════════════════════════════╣ ───► ╠═══════════════════════════════════╣
║ value: string                  ║      ║ date_time: StructuredDateTime     ║
║                                ║      ║ offset: TimeOffset                ║
╚════════════════════════════════╝      ╚═══════════════════════════════════╝
                    ▲                                         ▲
                    │                                         │
                    │has_one                                  │has_one
                    │                                         │
╔══════════════════════════════════════════╗       ╔═════════════════════════════════════════════╗
║ OldDigitalTimepiece                      ║       ║ NewDigitalTimepiece                         ║
╠══════════════════════════════════════════╣ ─ ─ ► ╠═════════════════════════════════════════════╣
║ raw_time: UnstructuredDateTimeWithOffset ║       ║ detailed_time: StructuredDateTimeWithOffset ║
╚══════════════════════════════════════════╝       ╚═════════════════════════════════════════════╝
                    ▲          Mapping specified via          ▲
                    │          ClockTransform.transform       │
                    │has_one                                  │has_one
                    │                                         │
╔════════════════════════════════╗      ╔════════════════════════════════╗
║ WallClock                      ║      ║ ComputerClock                  ║
╠════════════════════════════════╣ ───► ╠════════════════════════════════╣
║ timepiece: OldDigitalTimepiece ║      ║ timepiece: NewDigitalTimepiece ║
╚════════════════════════════════╝      ╚════════════════════════════════╝
                                transforms via
                                ClockTransform

- - ► : Transformation mapping only
────► : Transformation processing</body></sourcecode>

</clause>
</clause>

<clause id="_collection_transforms" obligation="normative">
<title>Collection transforms</title>
<clause id="_general_7" obligation="normative">
<title>General</title>
<p id="_b4ec1405-691f-3dbf-33b7-02cbaaaba15d">Collection transforms handle mapping between collections of objects.</p>
</clause>

<clause id="_models_to_models" obligation="normative">
<title>Models to models</title>
<p id="_a9d2b68d-2d7a-f02c-b586-6a1a029cb542">The <tt>map_each</tt> command is used to specify that the source attribute is a collection.</p>

<p id="_46b0fb25-7db5-c381-41a7-eea3d76e7aa3">Syntax:</p>

<sourcecode id="_5ce08a47-2b54-b9c5-644e-8bf9772dd94d" lang="ruby"><body>map_each from: 'collection_path', to: 'attribute_or_collection_path', transform: CollectionTransform</body></sourcecode>


<example id="_ce72a841-946f-31ea-d06e-9ddb42d7fc59"><sourcecode id="_3b7265ed-25ca-0915-5332-8857aa0528e3" lang="ruby"><body>class Publication &lt; Lutaml::Model::Serializable
  attribute :name, :string
  attribute :authors, Author, collection: true
end

class CatalogEntry &lt; Lutaml::Model::Serializable
  attribute :name, :string
  attribute :contributors, Contributor, collection: true
end

class Author &lt; Lutaml::Model::Serializable
  attribute :name, :string
end

class Contributor &lt; Lutaml::Model::Serializable
  attribute :name, :string
end

# This is a model to model transform
class AuthorTransform &lt; Lutaml::Model::Transform
  source_model Author
  target_model Contributor

  transform do
    map from: 'name', to: 'name'
  end
end

class PublicationTransform &lt; Lutaml::Model::Transform
  source_model Publication
  target_model CatalogEntry

  transform do
    map_each from: 'authors', to: 'contributors', transform: AuthorTransform
  end
end</body></sourcecode> </example>
</clause>

<clause id="_splitting_models_into_a_collection" obligation="normative">
<title>Splitting models into a collection</title>
<p id="_eefb826d-861a-af63-eb0b-d093511e4aa7">Collection transforms can also split a single model into multiple entries in a collection. For instance, consider a transform that takes a publication’s authors and converts them into a collection of contributors.</p>

<p id="_b1731813-23c4-4653-9dc4-0f23f73dea10">The <tt>target_model …​, collection: true</tt> syntax is used to specify that the target attribute is a collection.</p>

<p id="_22cf3b07-371a-5aee-c8bb-c5c10440ca9c">Syntax:</p>

<sourcecode id="_2c2996e8-f5fa-251f-ee61-f9d18fc16281" lang="ruby"><body>map_each from: 'attribute', to: 'collection_path', transform: CollectionTransform</body></sourcecode>


<example id="_37f00358-d3dc-c5c8-d1b4-7a404158c040"><sourcecode id="_16be54a9-046b-5984-1b54-bc90399f2325" lang="ruby"><body>class PublicationV1 &lt; Lutaml::Model::Serializable
  attribute :title, :string
  attribute :contributor_information, :string
end

class PublicationV2 &lt; Lutaml::Model::Serializable
  attribute :title, :string
  attribute :contributors, Contributor, collection: true
end

class Contributor &lt; Luatml::Model::Serializable
  attribute :name, :string
end

class ContributorTransform &lt; Lutaml::Model::Transform
  source_value :string
  target_model Contributor, collection: true

  transform do |source_value|
    source_value.split(',').map do |name|
      Contributor.new(name: name)
    end
  end
end

class PublicationTransform &lt; Lutaml::Model::Transform
  source_model PublicationV1
  target_model PublicationV2

  transform do
    map from: 'contributor_information', to: 'contributors', transform: ContributorTransform
  end
end</body></sourcecode> </example>
</clause>

<clause id="_joining_a_collection_into_an_attribute" obligation="normative">
<title>Joining a collection into an attribute</title>
<p id="_d9d1c0d8-95c5-cf83-0ad1-8d318c0184c2">Collection transforms can also join a collection of objects into a single attribute in the target model.</p>

<p id="_af957bb1-c605-c31e-08af-e7d161b2254c">Syntax:</p>

<sourcecode id="_57598e77-2f27-e216-8aa3-5e7f51e94db7" lang="ruby"><body>map_each from: 'collection_path', to: 'attribute', transform: CollectionTransform</body></sourcecode>


<example id="_5b1b6e87-a66e-95e9-1b84-558bd1f3991e"><sourcecode id="_9a12860e-ea28-9df3-5ad4-af098173f72a" lang="ruby"><body>class StandardsPublication &lt; Lutaml::Model::Serializable
  attribute :title, :string, collection: true
end

class BibliographyEntry &lt; Lutaml::Model::Serializable
  attribute :title, :string
end

class TitleAggregationTransform &lt; Lutaml::Model::Transform
  source_model :string, collection: true
  target_model :string

  transform do |source_values|
    source_values.join(', ')
  end
end

class StandardsPublicationTransform &lt; Lutaml::Model::Transform
  source_model StandardsPublication
  target_model BibliographyEntry

  transform do
    map_each from: 'title', to: 'title', transform: TitleAggregationTransform
  end
end</body></sourcecode> </example>
</clause>
</clause>


</sections><annex id="_tutorial_complex_transformation_scenario" obligation="normative">
<title>Tutorial: Complex transformation scenario</title>
<p id="_ed222be8-3bd3-5004-19d3-e9bcbb90453e">This tutorial demonstrates a complete transformation scenario using a museum’s art collection as an example.</p>

<p id="_ba21f9ee-97ec-f5c7-8edb-266b519a26f2">Consider the following model trees.</p>

<p id="_3006812f-b587-cd18-0863-9f9f174bd454">The “De Lutam’l Art Museum” has a collection of ceramic pieces managed in a register used for generic art information.</p>

<p id="_0619624e-1ffa-3714-1bdb-ff8cabe2c271">The register has the following fields:</p>

<sourcecode id="_949fa871-4694-cf94-029c-e13412da1932" lang="ruby"><body># First model tree
class GenericArtInformation &lt; Lutaml::Model::Serializable
  attribute :title, :string
  attribute :description, :string
  attribute :artist, CreatorInformation
  attribute :creation_date, :string
  attribute :place_of_work, :string
end

class CreatorInformation &lt; Lutaml::Model::Serializable
  attribute :name, :string
  attribute :bio, :string
  attribute :website, :string
  attribute :year_born, :integer
  attribute :year_died, :integer
end</body></sourcecode>


<p id="_d9952725-eaef-355a-85c6-abfdda572b8a">This is an example of a YAML file that represents the first model tree:</p>

<sourcecode id="_c446b47d-ec5f-4274-4260-ec1fe94edc9f" lang="yaml">
<name>In the <tt>GenericArtInformation</tt> model</name>
<body>---
- title: "Translucent Vase"
  description: |
    A tall and beautiful translucent vase created in the celadon color.

    Dimensions: 10x10x10 cm
    Fire temperature: 1000°C
    Clay type: Porcelain
  artist:
    name: "Masaaki Shibata"
    bio: |
      Masaaki Shibata is a Japanese ceramic artist.

      Awards: Japan Ceramic Society Award, 2005.

      Skills: Glazing, painting
    website: "https://www.masaakishibata.com"
    year_born: 1947
    year_died: null
  creation_date: "2010-01-01"
  place_of_work: Tokyo, Japan
- title: "Blue and White Bowl"
  description: |
    A blue and white bowl with a floral pattern.

    Dimensions: 20x20x20 cm
    Fire temperature: 1200°C
    Clay type: Stoneware
    Glaze: Blue and white
  artist:
    name: "Lucie Rie"
    bio: |
      Lucie Rie was an Austrian-born British studio potter.

      Awards: Potter's Gold Medal, 1987.

      Skills: Throwing, glazing
    website: "https://www.lucierie.com"
    year_born: 1902
    year_died: 1995
  creation_date: "1970-01-01"
  place_of_work: London, UK
- title: "Ceramic Sculpture"
  description: |
    A ceramic sculpture in form of a golden fish.

    Dimensions: 30x10x20 cm
    Fire temperature: 800°C
    Clay type: Earthenware
    Glaze: Gold
  artist:
    name: "Peter Voulkos"
    bio: |
      Peter Voulkos was an American artist of Greek descent.

      Awards: National Medal of Arts, 2001.

      Skills: Throwing, hand-building, glazing

    website: "https://www.petervoulkos.com"
    year_born: 1924
    year_died: 2002
  creation_date: "1980-01-01"
  place_of_work: Portopolous, Greece</body></sourcecode>


<p id="_a30c7e12-e0df-2148-46d0-dadff63849a6">The museum wants to transform the generic art information model into a ceramic art information model to better manage the ceramic pieces.</p>

<sourcecode id="_083e9340-8423-ebb5-b964-96564ffee482" lang="ruby"><body># Second model tree
class CeramicArtInformation &lt; Lutaml::Model::Serializable
  attribute :title, :string
  attribute :description, :string
  attribute :artist, CeramicCreatorInformation
  attribute :creation_date, :date_with_time
  attribute :location, :string
  attribute :dimensions, Dimensions
  attribute :fire_temperature, :integer
  attribute :fire_temperature_unit, :string, values: %w[°C °F]
  attribute :clay_type, :string
  attribute :glaze, :string
end

class Dimensions &lt; Lutaml::Model::Serializable
  attribute :height, :integer
  attribute :width, :integer
  attribute :depth, :integer
end

class CeramicCreatorInformation &lt; Lutaml::Model::Serializable
  attribute :name, :string
  attribute :bio, :string
  attribute :website, :string
  attribute :year_of_birth, :integer
  attribute :year_of_death, :integer
  attribute :techniques, :string, collection: true
  attribute :awards, :string, collection: true
end</body></sourcecode>


<p id="_6e2ff4e3-3ea7-2144-13a1-ce1620807959">We need to create a <tt>Lutaml::Model::Transform</tt> class that will transform the first model tree into the second model tree.</p>

<p id="_9e46836f-4477-2e74-03b4-074814357b06">Let’s first map the fields and group them according to the level of processing
needed.<note id="_057dba5f-fc9f-a139-d996-4c7528099102"><p id="_5f86a036-092f-ed86-0a88-727e21e66fda">The “source” refes to the <tt>GenericArtInformation</tt> model, and the “target” refers to the <tt>CeramicArtInformation</tt> model.</p>
</note></p>



<table id="_be6b8f1a-55bf-5660-709e-63278d6f890f">
<name>No processing needed</name>
<thead><tr><th valign="top" align="left">Source attribute(s)</th>
<th valign="top" align="left">Target attribute(s)</th>
<th valign="top" align="left">Value processing needed</th>
</tr></thead>
<tbody><tr><td valign="top" align="left"><tt>title</tt></td>
<td valign="top" align="left"><tt>title</tt></td>
<td valign="top" align="left">None</td>
</tr><tr><td valign="top" align="left"><tt>description</tt></td>
<td valign="top" align="left"><tt>description</tt></td>
<td valign="top" align="left">None</td>
</tr></tbody>
</table>

<table id="_f3554685-9f1b-5057-099e-caa484073573">
<name>Attribute rename</name>
<thead><tr><th valign="top" align="left">Source attribute(s)</th>
<th valign="top" align="left">Target attribute(s)</th>
<th valign="top" align="left">Value processing needed</th>
</tr></thead>
<tbody><tr><td valign="top" align="left"><tt>place_of_work</tt></td>
<td valign="top" align="left"><tt>location</tt></td>
<td valign="top" align="left">None</td>
</tr></tbody>
</table>

<table id="_d41fbf9d-3bda-0f54-7b57-d33d1a0e04ed">
<name>Value type conversion</name>
<tbody><tr><td valign="top" align="left">Source attribute(s)</td>
<td valign="top" align="left">Target attribute(s)</td>
<td valign="top" align="left">Value processing needed</td>
</tr><tr><td valign="top" align="left"><tt>creation_date</tt></td>
<td valign="top" align="left"><tt>creation_date</tt></td>
<td valign="top" align="left">Convert string to date with time</td>
</tr></tbody>
</table>

<table id="_bf672882-bff2-148f-aa02-951eeb19d32c">
<name>Processing needed</name>
<tbody><tr><td valign="top" align="left">Source attribute(s)</td>
<td valign="top" align="left">Target attribute(s)</td>
<td valign="top" align="left">Value processing needed</td>
</tr><tr><td valign="top" align="left"><tt>fire_temperature</tt></td>
<td valign="top" align="left"><tt>fire_temperature</tt></td>
<td valign="top" align="left">Extract from <tt>description</tt></td>
</tr><tr><td valign="top" align="left"><tt>fire_technique</tt></td>
<td valign="top" align="left"><tt>fire_technique</tt></td>
<td valign="top" align="left">Extract from <tt>description</tt></td>
</tr><tr><td valign="top" align="left"><tt>clay_type</tt></td>
<td valign="top" align="left"><tt>clay_type</tt></td>
<td valign="top" align="left">Extract from <tt>description</tt></td>
</tr><tr><td valign="top" align="left"><tt>glaze</tt></td>
<td valign="top" align="left"><tt>glaze</tt></td>
<td valign="top" align="left">Extract from <tt>description</tt></td>
</tr><tr><td valign="top" align="left"><tt>dimensions</tt></td>
<td valign="top" align="left"><tt>dimensions</tt></td>
<td valign="top" align="left">Extract specific values and map to Dimensions attributes</td>
</tr></tbody>
</table>

<table id="_6d2f1015-c7d2-2ca6-bf39-b2d8aa31cb58">
<name>Nested attribute map</name>
<tbody><tr><td valign="top" align="left">Source attribute(s)</td>
<td valign="top" align="left">Target attribute(s)</td>
<td valign="top" align="left">Value processing needed</td>
</tr><tr><td valign="top" align="left"><tt>artist.name</tt></td>
<td valign="top" align="left"><tt>artist.name</tt></td>
<td valign="top" align="left">None</td>
</tr><tr><td valign="top" align="left"><tt>artist.bio</tt></td>
<td valign="top" align="left"><tt>artist.bio</tt></td>
<td valign="top" align="left">None</td>
</tr><tr><td valign="top" align="left"><tt>artist.website</tt></td>
<td valign="top" align="left"><tt>artist.website</tt></td>
<td valign="top" align="left">None</td>
</tr></tbody>
</table>

<table id="_59f212be-6da9-65dc-da15-4e7c480aa432">
<name>Nested attribute rename</name>
<tbody><tr><td valign="top" align="left">Source attribute(s)</td>
<td valign="top" align="left">Target attribute(s)</td>
<td valign="top" align="left">Value processing needed</td>
</tr><tr><td valign="top" align="left"><tt>artist.year_born</tt></td>
<td valign="top" align="left"><tt>artist.year_of_birth</tt></td>
<td valign="top" align="left">None</td>
</tr><tr><td valign="top" align="left"><tt>artist.year_died</tt></td>
<td valign="top" align="left"><tt>artist.year_of_death</tt></td>
<td valign="top" align="left">None</td>
</tr></tbody>
</table>

<table id="_2937fdca-b142-db26-4201-d8a0a25cd23c">
<name>Nested attribute processing</name>
<tbody><tr><td valign="top" align="left">Source attribute(s)</td>
<td valign="top" align="left">Target attribute(s)</td>
<td valign="top" align="left">Value processing needed</td>
</tr><tr><td valign="top" align="left"><tt>artist.bio</tt></td>
<td valign="top" align="left"><tt>artist.techniques</tt></td>
<td valign="top" align="left">Extract from artist bio</td>
</tr><tr><td valign="top" align="left"><tt>artist.bio</tt></td>
<td valign="top" align="left"><tt>artist.awards</tt></td>
<td valign="top" align="left">Extract from artist bio</td>
</tr></tbody>
</table>

<p id="_e9221189-4307-90a7-6ad8-6e85f2766ae9">The following <tt>Lutaml::Model::Transform</tt> class will transform the first model tree into the second model tree. We build this class incrementally by adding the necessary mappings.</p>

<p id="_6228a234-976f-cf5d-d661-72df4f86dc85">Let’s start with mapping the attributes that do not require any processing.</p>

<sourcecode id="_03d77ce7-6da6-b034-9b19-610c49f11ea4" lang="ruby"><body>class CeramicArtInformationTransform &lt; Lutaml::Model::Transform
  source_model GenericArtInformation
  target_model CeramicArtInformation

  transform do
    # Simple mapping
    map from: 'title', to: 'title'
    map from: 'description', to: 'description'
  end
end</body></sourcecode>


<p id="_067b1045-a387-e927-2753-4e90814e3599">Next, we add the mapping for the attributes that require renaming.</p>

<sourcecode id="_77337723-2eff-c521-08d1-3f6c4e255c53" lang="ruby"><body>class CeramicArtInformationTransform &lt; Lutaml::Model::Transform
  source_model GenericArtInformation
  target_model CeramicArtInformation

  transform do
    # Simple mapping
    map from: 'title', to: 'title'
    map from: 'description', to: 'description'

    # Rename attributes
    map from: 'place_of_work', to: 'location'
  end
end</body></sourcecode>


<p id="_321954a0-1f7d-d328-fe6c-526257ffc3d3">Now let’s add the mapping for the nested attributes.</p>

<sourcecode id="_2adc892d-7595-9656-e3ab-b12823d04139" lang="ruby"><body>class CeramicArtInformationTransform &lt; Lutaml::Model::Transform
  source_model GenericArtInformation
  target_model CeramicArtInformation

  transform do
    # Simple mapping
    map from: 'title', to: 'title'
    map from: 'description', to: 'description'

    # Rename attributes
    map from: 'place_of_work', to: 'location'

    # Nested attribute mapping
    map from: 'artist.name', to: 'artist.name'
    map from: 'artist.bio', to: 'artist.bio'
    map from: 'artist.website', to: 'artist.website'

    # Rename nested attributes
    map from: 'artist.year_born', to: 'artist.year_of_birth'
    map from: 'artist.year_died', to: 'artist.year_of_death'
  end
end</body></sourcecode>


<p id="_6080f70e-80b7-d90b-597f-ce78bff84fd8">Next, we add the mapping for the attributes that require value type conversion.</p>

<p id="_2801eace-14da-fb23-1f08-26010e5b7817">There are two ways we can specify a value transform.</p>

<ol id="_2b2a255f-4da3-74cb-62af-80d50e01812e" type="arabic"><li><p id="_4bbec07a-6213-a047-d938-6578dfc59d72">By using a Lutaml::Value::Transform class that implements the <tt>transform</tt> and <tt>reverse_transform</tt> methods.</p>
</li>
<li><p id="_82daeff7-f6f6-fb37-85ab-c8719ba03084">By using a block that takes the source value as an argument and returns the transformed value.</p>
</li>
</ol>

<p id="_c55e161a-4d69-36df-ebc8-7f5cb128779a">In the first manner, we define the <tt>DateFormatTransform</tt> class that converts a string to a date with time.</p>

<sourcecode id="_e6767ac7-5146-7c08-7217-66f456dde719" lang="ruby"><body>class DateFormatTransform &lt; Lutaml::Value::Transform
  source_value :string
  target_value :date_with_time

  transform do |source_value|
    Date.parse(source_value)
  end

  reverse_transform do |target_value|
    target_value.strftime('%Y-%m-%d')
  end
end</body></sourcecode>


<p id="_6acccd71-101d-381e-41eb-8fd499876db6">Then the mapping is added to the <tt>CeramicArtInformationTransform</tt> class like the following.</p>

<sourcecode id="_77036c8d-c466-ba89-564c-835f129498db" lang="ruby"><body># Value type conversion
map from: 'creation_date', to: 'creation_date', transform: DateFormatTransform</body></sourcecode>


<p id="_844f9de0-ae62-bd04-a1dc-a419a20c3a05">In the second manner, we can use a block to specify the transformation.</p>

<sourcecode id="_6575dcaf-89da-be18-f923-1d9ab88d95c3" lang="ruby"><body># Value type conversion
map from: 'creation_date', to: 'creation_date',
  transform: -&gt; { |source_value|
    Date.parse(source_value)
  },
  reverse_transform: -&gt; { |target_value|
    target_value.strftime('%Y-%m-%d')
  }</body></sourcecode>


<p id="_ff988073-fee8-d34b-50ae-31a8063a7dae">The example follows that we follow the first manner.</p>

<p id="_1b97c7bc-4ee8-0de1-8217-09d1845acfbc">Next, we add the mapping for the attributes that require processing.</p>

<sourcecode id="_54be14a7-e811-82f8-2255-73a58834fbc8" lang="ruby"><body>class CeramicArtInformationTransform &lt; Lutaml::Model::Transform
  source_model GenericArtInformation
  target_model CeramicArtInformation

  transform do
    # Simple mapping
    map from: 'title', to: 'title'
    map from: 'description', to: 'description'

    # Rename attributes
    map from: 'place_of_work', to: 'location'

    # Nested attribute mapping
    map from: 'artist.name', to: 'artist.name'
    map from: 'artist.bio', to: 'artist.bio'
    map from: 'artist.website', to: 'artist.website'

    # Rename nested attributes
    map from: 'artist.year_born', to: 'artist.year_of_birth'
    map from: 'artist.year_died', to: 'artist.year_of_death'

    # Value type conversion
    map from: 'creation_date', to: 'creation_date', transform: DateFormatTransform

    # Single direction transform only, because the source information remains
    # unchanged in a reverse migration.
    map from: 'description', to: 'fire_temperature', transform: :extract_fire_temperature
    map from: 'description', to: 'fire_temperature_unit', transform: :extract_fire_temperature_unit

    # Extract the clay type from the description.
    # e.g. "Clay type: Porcelain" =&gt; "Porcelain"
    map from: 'description', to: 'clay_type', transform: -&gt; { |description|
      description.match(/Clay type: ([\w\s]+)/)[1]
    }

    # Extract the glaze from the description.
    # e.g. "Glaze: Blue and white" =&gt; "Blue and white"
    # Notice that the glaze is (optional), so we use a non-greedy match.
    map from: 'description', to: 'glaze', transform: -&gt; { |description|
      description.match(/Glaze: (.+?)/)[1] rescue nil
    }

    # Use a separate method to extract dimensions from the description.
    # Extract the fire temperature from the description.
    map from: 'description', to: 'fire_temperature', transform: :extract_fire_temperature

    # e.g. "Fire temperature: 1000°C" =&gt; 1000
    # NOTE: Fire temperature might not be present.
    def extract_fire_temperature(description)
      description.match(/Fire temperature: (\d+)/)[1]&amp;.to_i
    end

    # Use a separate method to extract dimensions from the description.
    # Extract the temperature unit from the description.
    map from: 'description', to: 'fire_temperature_unit', transform: :extract_fire_temperature_unit

    # e.g. "Fire temperature: 1000°C" =&gt; "°C"
    # NOTE: Fire temperature might not be present.
    def extract_fire_temperature_unit(description)
      description.match(/Fire temperature: \d+(°\w+)/)[1]&amp;.to_s
    end

    # Nested attribute with extracted values
    map from: 'artist.bio', to: 'artist.techniques', transform: :extract_techniques
    map from: 'artist.bio', to: 'artist.awards', transform: :extract_awards

    # Extract techniques from the bio text.
    # e.g. "Technique: Pottery" =&gt; ["Pottery"]
    def extract_techniques(bio)
      bio.scan(/Technique: ([\w\s]+)/).flatten
    end

    # Extract awards from the bio text.
    # e.g. "Award: Best in Show" =&gt; ["Best in Show"]
    def extract_awards(bio)
      bio.scan(/Award: ([\w\s]+)/).flatten
    end
  end
end</body></sourcecode>


<p id="_732185ae-769e-8664-7012-6a81903e33bc">Now we have to create a transformation for the Dimensions attribute.</p>

<sourcecode id="_d4d56984-0027-1633-3a46-da63a3b8be3b" lang="ruby"><body># Transforms a string into a Dimension model
class DimensionsTransform &lt; Lutaml::Model::Transform
  source_value :string
  target_model :dimensions

  transform do |source_value|
    height, width, depth = source_value.match(/Dimensions: (\d+)x(\d+)x(\d+)/).captures
    target_model.new(
      height: height.to_i,
      width: width.to_i,
      depth: depth.to_i
    )
  end

  reverse_transform do |target_model|
    "#{target_model.height}x#{target_model.width}x#{target_model.depth}"
  end
end</body></sourcecode>


<p id="_ba58e61f-eb37-ebd9-bb33-7713b7a3a2cf">Then we add the mapping to the <tt>CeramicArtInformationTransform</tt> class.</p>

<sourcecode id="_7e3bc0a8-9e2d-44d5-c081-3458dc84583c" lang="ruby"><body># Extract dimensions from the description.
map from: 'description', to: 'dimensions', transform: DimensionsTransform</body></sourcecode>


<p id="_15a146fe-7713-06c3-747e-2f2eb16d86a8">Finally, we add the reverse transformation to the <tt>CeramicArtInformationTransform</tt> class.</p>

<sourcecode id="_8db985db-c93a-41d4-75f2-3177d8b4833c" lang="ruby"><body>class CeramicArtInformationTransform &lt; Lutaml::Model::Transform
  source_model GenericArtInformation
  target_model CeramicArtInformation

  transform do
    # Simple mapping
    map from: 'title', to: 'title'
    map from: 'description', to: 'description'

    # Rename attributes
    map from: 'place_of_work', to: 'location'

    # Nested attribute mapping
    map from: 'artist.name', to: 'artist.name'
    map from: 'artist.bio', to: 'artist.bio'
    map from: 'artist.website', to: 'artist.website'

    # Rename nested attributes
    map from: 'artist.year_born', to: 'artist.year_of_birth'
    map from: 'artist.year_died', to: 'artist.year_of_death'

    # Value type conversion
    map from: 'creation_date', to: 'creation_date', transform: DateFormatTransform

    # Single direction transform only, because the source information remains
    # unchanged in a reverse migration.
    map from: 'description', to: 'fire_temperature', transform: :extract_fire_temperature
    map from: 'description', to: 'fire_temperature_unit', transform: :extract_fire_temperature_unit

    # Extract the clay type from the description.
    # e.g. "Clay type: Porcelain" =&gt; "Porcelain"
    map from: 'description', to: 'clay_type', transform: -&gt; { |description|
      description.match(/Clay type: ([\w\s]+)/)[1]
    }

    # Extract the glaze from the description.
    # e.g. "Glaze: Blue and white" =&gt; "Blue and white"
    # Notice that the glaze is (optional), so we use a non-greedy match.
    map from: 'description', to: 'glaze', transform: -&gt; { |description|
      description.match(/Glaze: (.+?)/)[1] rescue nil
    }

    # Use a separate method to extract dimensions from the description.
    # Extract the fire temperature from the description.
    map from: 'description', to: 'fire_temperature', transform: :extract_fire_temperature

    # e.g. "Fire temperature: 1000°C" =&gt; 1000
    # NOTE: Fire temperature might not be present.
    def extract_fire_temperature(description)
      description.match(/Fire temperature: (\d+)/)[1]&amp;.to_i
    end

    # Use a separate method to extract dimensions from the description.
    # Extract the temperature unit from the description.
    map from: 'description', to: 'fire_temperature_unit', transform: :extract_fire_temperature_unit

    # e.g. "Fire temperature: 1000°C" =&gt; "°C"
    # NOTE: Fire temperature might not be present.
    def extract_fire_temperature_unit(description)
      description.match(/Fire temperature: \d+(°\w+)/)[1]&amp;.to_s
    end

    # Nested attribute with extracted values
    map from: 'artist.bio', to: 'artist.techniques', transform: :extract_techniques
    map from: 'artist.bio', to: 'artist.awards', transform: :extract_awards

    # Extract techniques from the bio text.
    # e.g. "Technique: Pottery" =&gt; ["Pottery"]
    def extract_techniques(bio)
      bio.scan(/Technique: ([\w\s]+)/).flatten
    end

    # Extract awards from the bio text.
    # e.g. "Award: Best in Show" =&gt; ["Best in Show"]
    def extract_awards(bio)
      bio.scan(/Award: ([\w\s]+)/).flatten
    end

    # Extract dimensions from the description.
    map from: 'description', to: 'dimensions', transform:
    DimensionsTransform
  end
end

# Transforms a string into a Dimension model
class DimensionsTransform &lt; Lutaml::Model::Transform
  source_value :string
  target_model Dimensions

  transform do |source_value|
    height, width, depth = source_value.match(/Dimensions: (\d+)x(\d+)x(\d+)/).captures
    target_model.new(
      height: height.to_i,
      width: width.to_i,
      depth: depth.to_i
    )
  end

  reverse_transform do |target_model|
    "#{target_model.height}x#{target_model.width}x#{target_model.depth}"
  end
end

# Transforms a string into a Date model
class DateFormatTransform &lt; Lutaml::Value::Transform
  source_value :string
  target_value :date_with_time

  transform do |source_value|
    Date.parse(source_value)
  end

  reverse_transform do |target_value|
    target_value.strftime('%Y-%m-%d')
  end
end</body></sourcecode>


<p id="_d3086a44-8db9-385c-e9b5-84668f14a814">The transformation is now complete.</p>

<p id="_7f3d1bff-db50-8df9-f496-f548060700b9">We can now use the <tt>CeramicArtInformationTransform</tt> class to transform the data from the first model tree to the second model tree.</p>

<sourcecode id="_f1d776e7-d922-fb78-4cba-e7aaeaf62691" lang="ruby"><body># Load the data from the YAML file
data = YAML.load_file('generic_art_information.yaml')

# Load the generic art information
generic_art_info = GenericArtInformation.from_yaml(data)

# Transform the data
transformed_data = CeramicArtInformationTransform.transform(generic_art_info)

transformed_data.first.class
# =&gt; CeramicArtInformation

# Save the transformed data to a YAML file
File.write('ceramic_art_information.yaml', transformed_data.to_yaml)</body></sourcecode>


<p id="_546e9565-3da9-c58e-fba4-d13367c0d62f">The transformed data looks like this.</p>

<sourcecode id="_ba37d837-ea9b-7e98-c456-d8c67854548f" lang="yaml">
<name>Data instances in the <tt>CeramicArtInformation</tt> model</name>
<body>---
- title: "Translucent Vase"
  description: |
    A tall and beautiful translucent vase created in the celadon color.

    Dimensions: 10x10x10 cm
    Fire temperature: 1000°C
    Clay type: Porcelain
  artist:
    name: "Masaaki Shibata"
    bio: |
      Masaaki Shibata is a Japanese ceramic artist.

      Awards: Japan Ceramic Society Award, 2005.

      Skills: Glazing, painting
    website: "https://www.masaakishibata.com"
    year_of_birth: 1947
    year_of_death: null
    techniques:
      - "Glazing"
      - "Painting"
    awards:
      - "Japan Ceramic Society Award, 2005"
  creation_date: "2010-01-01"
  location: Tokyo, Japan
  dimensions:
    height: 10
    width: 10
    depth: 10
  fire_temperature: 1000
  fire_temperature_unit: "°C"
  clay_type: "Porcelain"
  glaze: null
- title: "Blue and White Bowl"
  description: |
    A blue and white bowl with a floral pattern.

    Dimensions: 20x20x20 cm
    Fire temperature: 1200°C
    Clay type: Stoneware
    Glaze: Blue and white
  artist:
    name: "Lucie Rie"
    bio: |
      Lucie Rie was an Austrian-born British studio potter.

      Awards: Potter's Gold Medal, 1987.

      Skills: Throwing, glazing
    website: "https://www.lucierie.com"
    year_of_birth: 1902
    year_of_death: 1995
    techniques:
      - "Throwing"
      - "Glazing"
    awards:
      - "Potter's Gold Medal, 1987"
  creation_date: "1970-01-01"
  location: London, UK
  dimensions:
    height: 20
    width: 20
    depth: 20
  fire_temperature: 1200
  fire_temperature_unit: "°C"
  clay_type: "Stoneware"
  glaze: "Blue and white"
- title: "Ceramic Sculpture"
  description: |
    A ceramic sculpture in form of a golden fish.

    Dimensions: 30x10x20 cm
    Fire temperature: 800°C
    Clay type: Earthenware
    Glaze: Gold
  artist:
    name: "Peter Voulkos"
    bio: |
      Peter Voulkos was an American artist of Greek descent.

      Awards: National Medal of Arts, 2001.

      Skills: Throwing, hand-building, glazing

    website: "https://www.petervoulkos.com"
    year_of_birth: 1924
    year_of_death: 2002
    techniques:
      - "Throwing"
      - "Hand-building"
      - "Glazing"
    awards:
      - "National Medal of Arts, 2001"
  creation_date: "1980-01-01"
  location: Portopolous, Greece
  dimensions:
    height: 30
    width: 10
    depth: 20
  fire_temperature: 800
  fire_temperature_unit: "°C"
  clay_type: "Earthenware"
  glaze: "Gold"</body></sourcecode>

</annex><bibliography><references id="_normative_references" normative="true" obligation="informative">
<title>Normative references</title><p id="_cf4fafa1-7328-7d25-f309-bb194339feef">There are no normative references in this document.</p>

</references></bibliography>
</metanorma>
