lutaml-model 0.5.3 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/dependent-tests.yml +2 -0
- data/.rubocop_todo.yml +86 -23
- data/Gemfile +2 -0
- data/README.adoc +1441 -220
- data/lib/lutaml/model/attribute.rb +33 -10
- data/lib/lutaml/model/choice.rb +56 -0
- data/lib/lutaml/model/config.rb +1 -0
- data/lib/lutaml/model/constants.rb +7 -0
- data/lib/lutaml/model/error/choice_lower_bound_error.rb +9 -0
- data/lib/lutaml/model/error/choice_upper_bound_error.rb +9 -0
- data/lib/lutaml/model/error/import_model_with_root_error.rb +9 -0
- data/lib/lutaml/model/error/incorrect_sequence_error.rb +9 -0
- data/lib/lutaml/model/error/invalid_choice_range_error.rb +20 -0
- data/lib/lutaml/model/error/no_root_mapping_error.rb +9 -0
- data/lib/lutaml/model/error/no_root_namespace_error.rb +9 -0
- data/lib/lutaml/model/error/type/invalid_value_error.rb +19 -0
- data/lib/lutaml/model/error/unknown_sequence_mapping_error.rb +9 -0
- data/lib/lutaml/model/error.rb +9 -0
- data/lib/lutaml/model/json_adapter/standard_json_adapter.rb +6 -1
- data/lib/lutaml/model/key_value_mapping.rb +34 -3
- data/lib/lutaml/model/key_value_mapping_rule.rb +4 -2
- data/lib/lutaml/model/liquefiable.rb +59 -0
- data/lib/lutaml/model/mapping_hash.rb +9 -1
- data/lib/lutaml/model/mapping_rule.rb +19 -2
- data/lib/lutaml/model/schema/templates/simple_type.rb +247 -0
- data/lib/lutaml/model/schema/xml_compiler.rb +762 -0
- data/lib/lutaml/model/schema.rb +5 -0
- data/lib/lutaml/model/schema_location.rb +7 -0
- data/lib/lutaml/model/sequence.rb +71 -0
- data/lib/lutaml/model/serialize.rb +139 -33
- data/lib/lutaml/model/toml_adapter/toml_rb_adapter.rb +1 -2
- data/lib/lutaml/model/type/decimal.rb +0 -4
- data/lib/lutaml/model/type/hash.rb +11 -11
- data/lib/lutaml/model/type/time.rb +3 -3
- data/lib/lutaml/model/utils.rb +19 -15
- data/lib/lutaml/model/validation.rb +12 -1
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/builder/oga.rb +10 -7
- data/lib/lutaml/model/xml_adapter/builder/ox.rb +20 -13
- data/lib/lutaml/model/xml_adapter/element.rb +32 -0
- data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +13 -9
- data/lib/lutaml/model/xml_adapter/oga/element.rb +14 -13
- data/lib/lutaml/model/xml_adapter/oga_adapter.rb +86 -19
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +19 -15
- data/lib/lutaml/model/xml_adapter/xml_document.rb +82 -25
- data/lib/lutaml/model/xml_adapter/xml_element.rb +57 -3
- data/lib/lutaml/model/xml_mapping.rb +53 -9
- data/lib/lutaml/model/xml_mapping_rule.rb +8 -6
- data/lib/lutaml/model.rb +2 -0
- data/lutaml-model.gemspec +5 -0
- data/spec/benchmarks/xml_parsing_benchmark_spec.rb +75 -0
- data/spec/ceramic_spec.rb +39 -0
- data/spec/fixtures/ceramic.rb +23 -0
- data/spec/fixtures/xml/address_example_260.xsd +9 -0
- data/spec/fixtures/xml/invalid_math_document.xml +4 -0
- data/spec/fixtures/xml/math_document_schema.xsd +56 -0
- data/spec/fixtures/xml/test_schema.xsd +53 -0
- data/spec/fixtures/xml/user.xsd +10 -0
- data/spec/fixtures/xml/valid_math_document.xml +4 -0
- data/spec/lutaml/model/cdata_spec.rb +4 -5
- data/spec/lutaml/model/choice_spec.rb +168 -0
- data/spec/lutaml/model/collection_spec.rb +1 -1
- data/spec/lutaml/model/custom_model_spec.rb +7 -21
- data/spec/lutaml/model/custom_serialization_spec.rb +74 -2
- data/spec/lutaml/model/defaults_spec.rb +3 -1
- data/spec/lutaml/model/delegation_spec.rb +7 -5
- data/spec/lutaml/model/enum_spec.rb +35 -0
- data/spec/lutaml/model/group_spec.rb +160 -0
- data/spec/lutaml/model/inheritance_spec.rb +25 -0
- data/spec/lutaml/model/key_value_mapping_spec.rb +27 -0
- data/spec/lutaml/model/liquefiable_spec.rb +121 -0
- data/spec/lutaml/model/map_all_spec.rb +188 -0
- data/spec/lutaml/model/mixed_content_spec.rb +95 -56
- data/spec/lutaml/model/multiple_mapping_spec.rb +22 -10
- data/spec/lutaml/model/schema/xml_compiler_spec.rb +1624 -0
- data/spec/lutaml/model/sequence_spec.rb +216 -0
- data/spec/lutaml/model/transformation_spec.rb +230 -0
- data/spec/lutaml/model/type_spec.rb +138 -31
- data/spec/lutaml/model/utils_spec.rb +32 -0
- data/spec/lutaml/model/with_child_mapping_spec.rb +2 -2
- data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +11 -7
- data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +52 -0
- data/spec/lutaml/model/xml_mapping_rule_spec.rb +51 -0
- data/spec/lutaml/model/xml_mapping_spec.rb +250 -112
- metadata +77 -2
data/README.adoc
CHANGED
@@ -8,19 +8,27 @@ image:https://img.shields.io/gem/v/lutaml-model.svg[RubyGems Version]
|
|
8
8
|
|
9
9
|
== Purpose
|
10
10
|
|
11
|
-
Lutaml::Model is
|
12
|
-
|
13
|
-
an adapter pattern to support multiple libraries for each format, providing
|
14
|
-
flexibility and extensibility for your data modeling needs.
|
11
|
+
Lutaml::Model is the Ruby implementation of the LutaML modeling methodology,
|
12
|
+
for:
|
15
13
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
Shale.
|
14
|
+
* creating information models in the LutaML language (or its Ruby DSL)
|
15
|
+
* serializing and deserializing LutaML information models
|
16
|
+
* accessing data instances of LutaML information models
|
17
|
+
* documenting LutaML information models
|
21
18
|
|
22
|
-
|
23
|
-
|
19
|
+
It provides simple, flexible and comprehensive mechanisms for defining
|
20
|
+
information models with attributes and types, and the serialization of them
|
21
|
+
to/from serialization formats including JSON, XML, YAML, and TOML.
|
22
|
+
|
23
|
+
For serialization formats, it uses an adapter pattern to support multiple
|
24
|
+
libraries for each format, providing flexibility and extensibility for your data
|
25
|
+
modeling needs.
|
26
|
+
|
27
|
+
NOTE: The Lutaml::Model modeling Ruby DSL was originally designed to be mostly
|
28
|
+
compatible with the data modeling DSL of https://www.shalerb.org[Shale], a data
|
29
|
+
modeller for Ruby. Lutaml::Model is meant to address advanced needs not
|
30
|
+
currently addressed by Shale. Instructions on how to migrate from Shale to
|
31
|
+
Lutaml::Model are provided in <<migrate-from-shale>>.
|
24
32
|
|
25
33
|
|
26
34
|
== Features
|
@@ -49,12 +57,12 @@ The Lutaml::Model data modelling approach is as follows:
|
|
49
57
|
.Modeling relationships of a LutaML Model
|
50
58
|
[source]
|
51
59
|
----
|
52
|
-
|
60
|
+
LutaML Model
|
53
61
|
│
|
54
62
|
Has many attributes
|
55
63
|
│
|
56
64
|
▼
|
57
|
-
|
65
|
+
Attribute
|
58
66
|
│
|
59
67
|
Has type of
|
60
68
|
│
|
@@ -96,57 +104,144 @@ Studio (Model)
|
|
96
104
|
.Modeling relationships of a LutaML Model to serialization models
|
97
105
|
[source]
|
98
106
|
----
|
99
|
-
╔═══════════════════════╗
|
100
|
-
║
|
101
|
-
╚═══════════════════════╝
|
102
|
-
|
103
|
-
╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
|
104
|
-
┆ Model ┆
|
105
|
-
┆ │ ┆ ┌────────────────┐
|
106
|
-
┆ ┌────────┴──┐ ┆ │ │
|
107
|
-
┆ │ │ ┆ │ Model │
|
108
|
-
┆ Models Value Types ┆──►│ Transformation │
|
109
|
-
┆ │ │ ┆ │ & │
|
110
|
-
┆ │ │ ┆ │ Mapping Rules │
|
111
|
-
┆ │ ┌──────┴──┐ ┆ │ │
|
112
|
-
┆ │ │ │ ┆
|
113
|
-
┆ │ String Integer ┆
|
114
|
-
┆ │ Date Float ┆ │
|
115
|
-
┆ │ Time Boolean ┆
|
116
|
-
┆ │ ┆ │
|
117
|
-
┆ └──────┐ ┆ │
|
107
|
+
╔═══════════════════════╗ ╔════════════════════════════╗
|
108
|
+
║ LutaML Core Model ║ ║ Serialization Models ║
|
109
|
+
╚═══════════════════════╝ ╚════════════════════════════╝
|
110
|
+
|
111
|
+
╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
|
112
|
+
┆ Model ┆ ┆ XML Model ┆
|
113
|
+
┆ │ ┆ ┌────────────────┐ ┆ │ ┆
|
114
|
+
┆ ┌────────┴──┐ ┆ │ │ ┆ ┌──────┴──────┐ ┆
|
115
|
+
┆ │ │ ┆ │ Model │ ┆ │ │ ┆
|
116
|
+
┆ Models Value Types ┆──►│ Transformation │ ┆ Models Value Types ┆
|
117
|
+
┆ │ │ ┆ │ & │ ┆ │ │ ┆
|
118
|
+
┆ │ │ ┆ │ Mapping Rules │ ┆ │ │ ┆
|
119
|
+
┆ │ ┌──────┴──┐ ┆ │ │ ┆ ┌────┴────┐ ┌─┴─┐ ┆
|
120
|
+
┆ │ │ │ ┆ └────────────────┘ ┆ │ │ │ │ ┆
|
121
|
+
┆ │ String Integer ┆ │ ┆ Element Value xs:string ┆
|
122
|
+
┆ │ Date Float ┆ │ ┆ Attribute Type xs:date ┆
|
123
|
+
┆ │ Time Boolean ┆ ├──────────►┆ xs:boolean ┆
|
124
|
+
┆ │ ┆ │ ┆ xs:anyURI ┆
|
125
|
+
┆ └──────┐ ┆ │ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯
|
118
126
|
┆ │ ┆ │
|
119
|
-
┆ Contains ┆ │
|
120
|
-
┆ more Models ┆ │
|
121
|
-
┆ (recursive) ┆ │
|
122
|
-
┆ ┆ │
|
123
|
-
╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
127
|
+
┆ Contains ┆ │ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
|
128
|
+
┆ more Models ┆ │ ┆ JSON Model ┆
|
129
|
+
┆ (recursive) ┆ │ ┆ │ ┆
|
130
|
+
┆ ┆ │ ┆ ┌──────┴──────┐ ┆
|
131
|
+
╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯ └──────────►┆ │ │ ┆
|
132
|
+
┆ Models Value Types ┆
|
133
|
+
┆ │ │ ┆
|
134
|
+
┆ │ │ ┆
|
135
|
+
┆ ┌────┴───┐ ┌───┴──┐ ┆
|
136
|
+
┆ │ │ │ │ ┆
|
137
|
+
┆ object array number string ┆
|
138
|
+
┆ value boolean null ┆
|
131
139
|
╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯
|
132
140
|
----
|
133
141
|
|
142
|
+
.Model transformation of a LutaML Model to another LutaML Model
|
143
|
+
[source]
|
144
|
+
----
|
145
|
+
╔═══════════════════════╗ ╔══════════════════╗ ╔═══════════════════════╗
|
146
|
+
║LutaML Model Class FOO ║ ║LutaML Transformer║ ║LutaML Model Class BAR ║
|
147
|
+
╚═══════════════════════╝ ╚══════════════════╝ ╚═══════════════════════╝
|
148
|
+
|
149
|
+
╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
|
150
|
+
┆ Model ┆ ┆ Model ┆
|
151
|
+
┆ │ ┆ ┌────────────────┐ ┆ │ ┆
|
152
|
+
┆ ┌────────┴──┐ ┆ │ │ ┆ ┌────────┴──┐ ┆
|
153
|
+
┆ │ │ ┆ │ Model │ ┆ │ │ ┆
|
154
|
+
┆ Models Value Types ┆───►│ Transformation │───►┆ Models Value Types ┆
|
155
|
+
┆ │ │ ┆◄───│ & │◄───┆ │ │ ┆
|
156
|
+
┆ │ │ ┆ │ Mapping Rules │ ┆ │ │ ┆
|
157
|
+
┆ │ ┌──────┴──┐ ┆ │ │ ┆ │ ┌──────┴──┐ ┆
|
158
|
+
┆ │ │ │ ┆ └────────────────┘ ┆ │ │ │ ┆
|
159
|
+
┆ │ String Integer ┆ ┆ │ String Integer ┆
|
160
|
+
┆ │ Date Float ┆ ┆ │ Date Float ┆
|
161
|
+
┆ │ Time Boolean ┆ ┆ │ Time Boolean ┆
|
162
|
+
┆ │ ┆ ┆ │ ┆
|
163
|
+
┆ └──────┐ ┆ ┆ └──────┐ ┆
|
164
|
+
┆ │ ┆ ┆ │ ┆
|
165
|
+
┆ Contains ┆ ┆ Contains ┆
|
166
|
+
┆ more Models ┆ ┆ more Models ┆
|
167
|
+
┆ (recursive) ┆ ┆ (recursive) ┆
|
168
|
+
┆ ┆ ┆ ┆
|
169
|
+
╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯
|
170
|
+
----
|
171
|
+
|
172
|
+
.The `Value` class, transformation, and serialization formats
|
173
|
+
[source]
|
174
|
+
----
|
175
|
+
╔═══════════════════════╗ ╔═══════════════════════╗
|
176
|
+
║LutaML Value Class FOO ║ ║ Serialization Value ║
|
177
|
+
╚═══════════════════════╝ ╚═══════════════════════╝
|
178
|
+
╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
|
179
|
+
┆ ┌───────────────┐ ┆ ┆ ┌───────────────┐ ┆
|
180
|
+
┆ │ Value │ ┆ ┌──────────────────┐ ┆ │ XML Value │ ┆
|
181
|
+
┆ └───────────────┘ ┆──►│ Value Serializer │──►┆ └───────────────┘ ┆
|
182
|
+
┆ ┌───────────────┐ ┆ └──────────────────┘ ┆ ┌───────────────┐ ┆
|
183
|
+
┆ │Primitive Types│ ┆ ┆ │XML Value Types│ ┆
|
184
|
+
┆ └───────────────┘ ┆ ┆ └───────────────┘ ┆
|
185
|
+
┆ ┌───┘ ┆ ┆ ┌───┘ ┆
|
186
|
+
┆ ├─ string ┆ ┆ ├─ xs:string ┆
|
187
|
+
┆ ├─ integer ┆ ┆ ├─ xs:integer ┆
|
188
|
+
┆ ├─ float ┆ ┆ ├─ xs:decimal ┆
|
189
|
+
┆ ├─ boolean ┆ ┆ ├─ xs:boolean ┆
|
190
|
+
┆ ├─ date ┆ ┆ ├─ xs:date ┆
|
191
|
+
┆ ├─ time_without_date ┆ ┆ ├─ xs:time ┆
|
192
|
+
┆ ├─ date_time ┆ ┆ ├─ xs:dateTime ┆
|
193
|
+
┆ ├─ time ┆ ┆ ├─ xs:decimal ┆
|
194
|
+
┆ ├─ decimal ┆ ┆ ├─ xs:anyType ┆
|
195
|
+
┆ └─ hash ┆ ┆ └─ (complex element) ┆
|
196
|
+
╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯
|
197
|
+
│
|
198
|
+
▼
|
199
|
+
┌───────────────────┐
|
200
|
+
│ Value Transformer │
|
201
|
+
└───────────────────┘
|
202
|
+
│
|
203
|
+
▼
|
204
|
+
╔═══════════════════════╗
|
205
|
+
║LutaML Value Class BAR ║
|
206
|
+
╚═══════════════════════╝
|
207
|
+
╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
|
208
|
+
┆ ┌───────────────┐ ┆
|
209
|
+
┆ │ Value │ ┆
|
210
|
+
┆ └───────────────┘ ┆
|
211
|
+
┆ ┌───────────────┐ ┆
|
212
|
+
┆ │Primitive Types│ ┆
|
213
|
+
┆ └───────────────┘ ┆
|
214
|
+
┆ ┌───┘ ┆
|
215
|
+
┆ ├─ string ┆
|
216
|
+
┆ ├─ integer ┆
|
217
|
+
┆ ├─ float ┆
|
218
|
+
┆ ├─ boolean ┆
|
219
|
+
┆ ├─ date ┆
|
220
|
+
┆ ├─ time_without_date ┆
|
221
|
+
┆ ├─ date_time ┆
|
222
|
+
┆ ├─ time ┆
|
223
|
+
┆ ├─ decimal ┆
|
224
|
+
┆ └─ hash ┆
|
225
|
+
╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯
|
226
|
+
----
|
227
|
+
|
134
228
|
.Example of LutaML Model instance transformed into a serialization model and serialized to JSON
|
135
229
|
====
|
136
230
|
[source]
|
137
231
|
----
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
232
|
+
╔═════════════════════╗ ╔═════════════════════╗ ╔═════════════════════╗
|
233
|
+
║ Studio (Core Model) ║ ║ JSON Model ║ ║ Serialized JSON ║
|
234
|
+
╚═════════════════════╝ ╚═════════════════════╝ ╚═════════════════════╝
|
235
|
+
|
236
|
+
name: "Studio 1" ┌─► { ┌─► {
|
237
|
+
address: │ "name": "...", │ "name": "Studio 1",
|
238
|
+
├── street: "..." │ "address": { │ "address": {
|
239
|
+
└── city: "..." │ "street": "...", │ "street": "...",
|
240
|
+
kilns: ──┤ "city": "..." ──┤ "city": "..."
|
241
|
+
├── count: 3 │ }, │ },
|
242
|
+
└── temp: 1200 │ "kilnsCount": ..., │ "kilnsCount": 3,
|
243
|
+
│ "kilnsTemp": ... │ "kilnsTemp": 1200
|
244
|
+
└─► } └─► }
|
150
245
|
----
|
151
246
|
====
|
152
247
|
|
@@ -548,6 +643,149 @@ end
|
|
548
643
|
====
|
549
644
|
|
550
645
|
|
646
|
+
=== Sequence within XmlMapping
|
647
|
+
|
648
|
+
The sequence option enforces that the defined components must appear in a specified order.
|
649
|
+
|
650
|
+
NOTE: `sequence` only works within XML and only supports `map_element` mappings.
|
651
|
+
|
652
|
+
.Using the `sequence` keyword to define a set of elements in desired order.
|
653
|
+
[example]
|
654
|
+
====
|
655
|
+
[source,ruby]
|
656
|
+
----
|
657
|
+
class Kiln < Lutaml::Model::Serializable
|
658
|
+
attribute :id, :string
|
659
|
+
attribute :name, :string
|
660
|
+
attribute :type, :string
|
661
|
+
attribute :color, :string
|
662
|
+
|
663
|
+
xml do
|
664
|
+
sequence do
|
665
|
+
map_element :id, to: :id
|
666
|
+
map_element :name, to: :name
|
667
|
+
map_element :type, to: :type
|
668
|
+
map_element :color, to: :color
|
669
|
+
end
|
670
|
+
end
|
671
|
+
end
|
672
|
+
|
673
|
+
class KilnCollection < Lutaml::Model::Serializable
|
674
|
+
attribute :kiln, Kiln, collection: 1..2
|
675
|
+
|
676
|
+
xml do
|
677
|
+
root "collection"
|
678
|
+
map_element "kiln", to: :kiln
|
679
|
+
end
|
680
|
+
end
|
681
|
+
----
|
682
|
+
|
683
|
+
[source,ruby]
|
684
|
+
----
|
685
|
+
> parsed = Kiln.from_xml("<Kiln> <id>1</id> <name>Nick</name> <type>Hard</type> <color>Black</color> </Kiln>")
|
686
|
+
> # parsed.not_to raise_error
|
687
|
+
----
|
688
|
+
====
|
689
|
+
|
690
|
+
|
691
|
+
=== Reusable Classes with Import
|
692
|
+
|
693
|
+
Lutaml lets you create reusable element and attribute collections using `no_root`. These can be imported into other models using:
|
694
|
+
|
695
|
+
- `import_model`: imports both attributes and mappings
|
696
|
+
- `import_model_attributes`: imports only attributes
|
697
|
+
- `import_model_mappings`: imports only mappings
|
698
|
+
|
699
|
+
NOTE: This feature works with XML. Import order determines how elements and attributes are overwritten.
|
700
|
+
|
701
|
+
[example]
|
702
|
+
====
|
703
|
+
[source,ruby]
|
704
|
+
----
|
705
|
+
class GroupOfItems < Lutaml::Model::Serializable
|
706
|
+
attribute :name, :string
|
707
|
+
attribute :type, :string
|
708
|
+
attribute :code, :string
|
709
|
+
|
710
|
+
xml do
|
711
|
+
no_root
|
712
|
+
sequence do
|
713
|
+
map_element "name", to: :name
|
714
|
+
map_element "type", to: :type, namespace: "http://www.example.com", prefix: "ex1"
|
715
|
+
end
|
716
|
+
map_attribute "code", to: :code
|
717
|
+
end
|
718
|
+
end
|
719
|
+
|
720
|
+
class ComplexType < Lutaml::Model::Serializable
|
721
|
+
attribute :tag, AttributeValueType
|
722
|
+
attribute :content, :string
|
723
|
+
attribute :group, :string
|
724
|
+
import_model_attributes GroupOfItems
|
725
|
+
|
726
|
+
xml do
|
727
|
+
root "GroupOfItems"
|
728
|
+
map_attribute "tag", to: :tag
|
729
|
+
map_content to: :content
|
730
|
+
map_element :group, to: :group
|
731
|
+
import_model_mappings GroupOfItems
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
735
|
+
class SimpleType < Lutaml::Model::Serializable
|
736
|
+
import_model GroupOfItems
|
737
|
+
end
|
738
|
+
|
739
|
+
class GenericType < Lutaml::Model::Serializable
|
740
|
+
import_model_mappings GroupOfItems
|
741
|
+
end
|
742
|
+
----
|
743
|
+
|
744
|
+
[source,xml]
|
745
|
+
----
|
746
|
+
<GroupOfItems xmlns:ex1="http://www.example.com">
|
747
|
+
<name>Name</name>
|
748
|
+
<ex1:type>Type</ex1:type>
|
749
|
+
</GroupOfItems>
|
750
|
+
----
|
751
|
+
|
752
|
+
[source,ruby]
|
753
|
+
----
|
754
|
+
> parsed = GroupOfItems.from_xml(xml)
|
755
|
+
> # Lutaml::Model::NoRootMappingError: "GroupOfItems has `no_root`, it allowed only for reusable models"
|
756
|
+
----
|
757
|
+
|
758
|
+
NOTE: Models with `no_root` can only be parsed through **Parent Models**. Direct calling `from_xml` will raise `NoRootMappingError`.
|
759
|
+
And if `namespace` is defined with `no_root`, `NoRootNamespaceError` will raise.
|
760
|
+
|
761
|
+
====
|
762
|
+
|
763
|
+
|
764
|
+
=== Choice
|
765
|
+
|
766
|
+
The `choice` option ensures that elements from the specified range are included.
|
767
|
+
|
768
|
+
NOTE: Attribute-level definitions are supported. This can be used with both key_value and xml mappings.
|
769
|
+
|
770
|
+
.Using the `choice` option to define a set of attributes with a range.
|
771
|
+
[example]
|
772
|
+
====
|
773
|
+
[source,ruby]
|
774
|
+
----
|
775
|
+
class Studio < Lutaml::Model::Serializable
|
776
|
+
choice(min: 1, max: 3) do
|
777
|
+
choice(min: 1, max: 2) do
|
778
|
+
attribute :prefix, :string
|
779
|
+
attribute :forename, :string
|
780
|
+
end
|
781
|
+
|
782
|
+
attribute :completeName, :string
|
783
|
+
end
|
784
|
+
end
|
785
|
+
----
|
786
|
+
====
|
787
|
+
|
788
|
+
|
551
789
|
=== Attribute value validation
|
552
790
|
|
553
791
|
==== General
|
@@ -697,7 +935,7 @@ end
|
|
697
935
|
|
698
936
|
|
699
937
|
|
700
|
-
=== Attribute value
|
938
|
+
=== Attribute value defaults
|
701
939
|
|
702
940
|
Specify default values for attributes using the `default` option.
|
703
941
|
The `default` option can be set to a value or a lambda that returns a value.
|
@@ -733,76 +971,6 @@ end
|
|
733
971
|
The "default behavior" (pun intended) is to not render a default value if
|
734
972
|
the current value is the same as the default value.
|
735
973
|
|
736
|
-
In certain cases, it is necessary to render the default value even if the
|
737
|
-
current value is the same as the default value. This can be achieved by setting
|
738
|
-
the `render_default` option to `true`.
|
739
|
-
|
740
|
-
Syntax:
|
741
|
-
|
742
|
-
[source,ruby]
|
743
|
-
----
|
744
|
-
attribute :name_of_attribute, Type, default: -> { value }, render_default: true
|
745
|
-
----
|
746
|
-
|
747
|
-
.Using the `render_default` option to force encoding the default value
|
748
|
-
[example]
|
749
|
-
====
|
750
|
-
[source,ruby]
|
751
|
-
----
|
752
|
-
class Glaze < Lutaml::Model::Serializable
|
753
|
-
attribute :color, :string, default: -> { 'Clear' }
|
754
|
-
attribute :opacity, :string, default: -> { 'Opaque' }
|
755
|
-
attribute :temperature, :integer, default: -> { 1050 }
|
756
|
-
attribute :firing_time, :integer, default: -> { 60 }
|
757
|
-
|
758
|
-
xml do
|
759
|
-
root "glaze"
|
760
|
-
map_element 'color', to: :color
|
761
|
-
map_element 'opacity', to: :opacity, render_default: true
|
762
|
-
map_attribute 'temperature', to: :temperature
|
763
|
-
map_attribute 'firingTime', to: :firing_time, render_default: true
|
764
|
-
end
|
765
|
-
|
766
|
-
json do
|
767
|
-
map 'color', to: :color
|
768
|
-
map 'opacity', to: :opacity, render_default: true
|
769
|
-
map 'temperature', to: :temperature
|
770
|
-
map 'firingTime', to: :firing_time, render_default: true
|
771
|
-
end
|
772
|
-
end
|
773
|
-
----
|
774
|
-
====
|
775
|
-
|
776
|
-
.Attributes with `render_default: true` are rendered when the value is identical to the default
|
777
|
-
[example]
|
778
|
-
====
|
779
|
-
[source,ruby]
|
780
|
-
----
|
781
|
-
> glaze_new = Glaze.new
|
782
|
-
> puts glaze_new.to_xml
|
783
|
-
# <glaze firingTime="60">
|
784
|
-
# <opacity>Opaque</opacity>
|
785
|
-
# </glaze>
|
786
|
-
> puts glaze_new.to_json
|
787
|
-
# {"firingTime":60,"opacity":"Opaque"}
|
788
|
-
----
|
789
|
-
====
|
790
|
-
|
791
|
-
.Attributes with `render_default: true` with non-default values are rendered
|
792
|
-
[example]
|
793
|
-
====
|
794
|
-
[source,ruby]
|
795
|
-
----
|
796
|
-
> glaze = Glaze.new(color: 'Celadon', opacity: 'Semitransparent', temperature: 1300, firing_time: 90)
|
797
|
-
> puts glaze.to_xml
|
798
|
-
# <glaze color="Celadon" temperature="1300" firingTime="90">
|
799
|
-
# <opacity>Semitransparent</opacity>
|
800
|
-
# </glaze>
|
801
|
-
> puts glaze.to_json
|
802
|
-
# {"color":"Celadon","temperature":1300,"firingTime":90,"opacity":"Semitransparent"}
|
803
|
-
----
|
804
|
-
====
|
805
|
-
|
806
974
|
|
807
975
|
|
808
976
|
=== Attribute as raw string
|
@@ -863,7 +1031,7 @@ defining serialization and deserialization mappings.
|
|
863
1031
|
Serialization model mappings are defined under the `xml`, `json`, `yaml`, and
|
864
1032
|
`toml` blocks.
|
865
1033
|
|
866
|
-
.Using the `xml`, `json`, `yaml`, and `
|
1034
|
+
.Using the `xml`, `json`, `yaml`, `toml` and `key_value` blocks to define serialization mappings
|
867
1035
|
[source,ruby]
|
868
1036
|
----
|
869
1037
|
class Example < Lutaml::Model::Serializable
|
@@ -882,6 +1050,10 @@ class Example < Lutaml::Model::Serializable
|
|
882
1050
|
toml do
|
883
1051
|
# ...
|
884
1052
|
end
|
1053
|
+
|
1054
|
+
key_value do
|
1055
|
+
# ...
|
1056
|
+
end
|
885
1057
|
end
|
886
1058
|
----
|
887
1059
|
|
@@ -926,10 +1098,8 @@ end
|
|
926
1098
|
----
|
927
1099
|
====
|
928
1100
|
|
929
|
-
|
930
|
-
==== Mapping all
|
931
|
-
|
932
|
-
WARNING: This feature is only applicable to XML (for now).
|
1101
|
+
[[xml-map-all]]
|
1102
|
+
==== Mapping all XML content
|
933
1103
|
|
934
1104
|
The `map_all` tag in XML mapping captures and maps all content within an XML
|
935
1105
|
element into a single attribute in the target Ruby object.
|
@@ -937,6 +1107,12 @@ element into a single attribute in the target Ruby object.
|
|
937
1107
|
The use case for `map_all` is to tell Lutaml::Model to not parse the content of
|
938
1108
|
the XML element at all, and instead handle it as an XML string.
|
939
1109
|
|
1110
|
+
NOTE: The corresponding method for key-value formats is at <<key-value-map-all>>.
|
1111
|
+
|
1112
|
+
WARNING: Notice that usage of mapping all will lead to incompatibility between
|
1113
|
+
serialization formats, i.e. the raw string content will not be portable as
|
1114
|
+
objects are across different formats.
|
1115
|
+
|
940
1116
|
This is useful in the case where the content of an XML element is not to be
|
941
1117
|
handled by a Lutaml::Model::Serializable object.
|
942
1118
|
|
@@ -949,8 +1125,9 @@ This includes:
|
|
949
1125
|
* attributes
|
950
1126
|
* text nodes
|
951
1127
|
|
952
|
-
The `map_all` tag is **exclusive** and cannot be combined with other mappings
|
953
|
-
|
1128
|
+
The `map_all` tag is **exclusive** and cannot be combined with other mappings
|
1129
|
+
(`map_element`, `map_content`) except for `map_attribute` for the same element,
|
1130
|
+
ensuring it captures the entire inner XML content.
|
954
1131
|
|
955
1132
|
NOTE: An error is raised if `map_all` is defined alongside any other mapping in
|
956
1133
|
the same XML mapping context.
|
@@ -1284,72 +1461,15 @@ The following class will parse the XML snippet below:
|
|
1284
1461
|
|
1285
1462
|
[source,ruby]
|
1286
1463
|
----
|
1287
|
-
class
|
1288
|
-
attribute :name, :string
|
1289
|
-
attribute :description, :string
|
1290
|
-
attribute :value, :integer
|
1291
|
-
|
1292
|
-
xml do
|
1293
|
-
root 'example'
|
1294
|
-
map_element 'name', to: :name
|
1295
|
-
map_attribute 'value', to: :value
|
1296
|
-
map_content to: :description
|
1297
|
-
end
|
1298
|
-
end
|
1299
|
-
----
|
1300
|
-
|
1301
|
-
[source,xml]
|
1302
|
-
----
|
1303
|
-
<example value="12"><name>John Doe</name> is my moniker.</example>
|
1304
|
-
----
|
1305
|
-
|
1306
|
-
[source,ruby]
|
1307
|
-
----
|
1308
|
-
> Example.from_xml(xml)
|
1309
|
-
> #<Example:0x0000000104ac7240 @name="John Doe", @description=" is my moniker.", @value=12>
|
1310
|
-
> Example.new(name: "John Doe", description: " is my moniker.", value: 12).to_xml
|
1311
|
-
> #<example value="12"><name>John Doe</name> is my moniker.</example>
|
1312
|
-
----
|
1313
|
-
====
|
1314
|
-
|
1315
|
-
|
1316
|
-
==== Encoding Options in XmlAdapter
|
1317
|
-
|
1318
|
-
XmlAdapter supports the encoding in the following ways:
|
1319
|
-
|
1320
|
-
. When encoding is not passed in to_xml:
|
1321
|
-
** Default encoding is UTF-8.
|
1322
|
-
|
1323
|
-
. When encoding is explicitly passed nil:
|
1324
|
-
** Encoding will be nil, show the HexCode(Nokogiri) or ASCII-8bit(Ox).
|
1325
|
-
|
1326
|
-
. When encoding is passed with some option:
|
1327
|
-
** Encoding option will be selected as passed.
|
1328
|
-
|
1329
|
-
|
1330
|
-
Syntax:
|
1331
|
-
|
1332
|
-
[source,ruby]
|
1333
|
-
----
|
1334
|
-
Example.new(description: " ∑ is my ∏ moniker µ.").to_xml
|
1335
|
-
Example.new(description: " ∑ is my ∏ moniker µ.").to_xml(encoding: nil)
|
1336
|
-
Example.new(description: " ∑ is my ∏ moniker µ.").to_xml(encoding: "ASCII")
|
1337
|
-
----
|
1338
|
-
|
1339
|
-
[example]
|
1340
|
-
====
|
1341
|
-
The following class will parse the XML snippet below:
|
1342
|
-
|
1343
|
-
[source,ruby]
|
1344
|
-
----
|
1345
|
-
class Example < Lutaml::Model::Serializable
|
1464
|
+
class Ceramic < Lutaml::Model::Serializable
|
1346
1465
|
attribute :name, :string
|
1347
1466
|
attribute :description, :string
|
1348
|
-
attribute :
|
1467
|
+
attribute :temperature, :integer
|
1349
1468
|
|
1350
1469
|
xml do
|
1351
|
-
root '
|
1470
|
+
root 'ceramic'
|
1352
1471
|
map_element 'name', to: :name
|
1472
|
+
map_attribute 'temperature', to: :temperature
|
1353
1473
|
map_content to: :description
|
1354
1474
|
end
|
1355
1475
|
end
|
@@ -1357,21 +1477,15 @@ end
|
|
1357
1477
|
|
1358
1478
|
[source,xml]
|
1359
1479
|
----
|
1360
|
-
<
|
1480
|
+
<ceramic temperature="1200"><name>Porcelain Vase</name> with celadon glaze.</ceramic>
|
1361
1481
|
----
|
1362
1482
|
|
1363
1483
|
[source,ruby]
|
1364
1484
|
----
|
1365
|
-
>
|
1366
|
-
> #<
|
1367
|
-
>
|
1368
|
-
> #<
|
1369
|
-
|
1370
|
-
> Example.new(name: "John & Doe", description: " ∑ is my ∏ moniker µ.").to_xml(encoding: nil)
|
1371
|
-
> #<example><name>John & Doe</name> ∑ is my ∏ moniker µ.</example>
|
1372
|
-
|
1373
|
-
> Example.new(name: "John & Doe", description: " ∑ is my ∏ moniker µ.").to_xml(encoding: "ASCII")
|
1374
|
-
> #<example><name>John & Doe</name> ∑ is my ∏ moniker µ.</example>
|
1485
|
+
> Ceramic.from_xml(xml)
|
1486
|
+
> #<Ceramic:0x0000000104ac7240 @name="Porcelain Vase", @description=" with celadon glaze.", @temperature=1200>
|
1487
|
+
> Ceramic.new(name: "Porcelain Vase", description: " with celadon glaze.", temperature: 1200).to_xml
|
1488
|
+
> #<ceramic temperature="1200"><name>Porcelain Vase</name> with celadon glaze.</ceramic>
|
1375
1489
|
----
|
1376
1490
|
====
|
1377
1491
|
|
@@ -1672,6 +1786,100 @@ end
|
|
1672
1786
|
|
1673
1787
|
// TODO: How to create mixed content from `#new`?
|
1674
1788
|
|
1789
|
+
|
1790
|
+
[[ordered-content]]
|
1791
|
+
==== Ordered content
|
1792
|
+
|
1793
|
+
`ordered: true` maintains the order of **XML Elements**, while `mixed: true` preserves the order of **XML Elements and Content**.
|
1794
|
+
|
1795
|
+
NOTE: When both options are used, `mixed: true` takes precedence.
|
1796
|
+
|
1797
|
+
To specify ordered content, the `ordered: true` option needs to be set at the
|
1798
|
+
`xml` block's `root` method.
|
1799
|
+
|
1800
|
+
Syntax:
|
1801
|
+
|
1802
|
+
[source,ruby]
|
1803
|
+
----
|
1804
|
+
xml do
|
1805
|
+
root 'xml_element_name', ordered: true
|
1806
|
+
end
|
1807
|
+
----
|
1808
|
+
|
1809
|
+
.Applying `ordered` to treat root as ordered content
|
1810
|
+
[example]
|
1811
|
+
====
|
1812
|
+
|
1813
|
+
[source,ruby]
|
1814
|
+
----
|
1815
|
+
class RootOrderedContent < Lutaml::Model::Serializable
|
1816
|
+
attribute :bold, :string
|
1817
|
+
attribute :italic, :string
|
1818
|
+
attribute :underline, :string
|
1819
|
+
|
1820
|
+
xml do
|
1821
|
+
root "RootOrderedContent", ordered: true
|
1822
|
+
map_element :bold, to: :bold
|
1823
|
+
map_element :italic, to: :italic
|
1824
|
+
map_element :underline, to: :underline
|
1825
|
+
end
|
1826
|
+
end
|
1827
|
+
----
|
1828
|
+
|
1829
|
+
[source,xml]
|
1830
|
+
----
|
1831
|
+
<RootOrderedContent>
|
1832
|
+
<underline>Moon</underline>
|
1833
|
+
<italic>384,400 km</italic>
|
1834
|
+
<bold>bell</bold>
|
1835
|
+
</RootOrderedContent>
|
1836
|
+
----
|
1837
|
+
|
1838
|
+
[source,ruby]
|
1839
|
+
----
|
1840
|
+
> instance = RootOrderedContent.from_xml(xml)
|
1841
|
+
> #<RootOrderedContent:0x0000000104ac7240 @bold="bell", @italic="384,400 km", @underline="Moon">
|
1842
|
+
> instance.to_xml
|
1843
|
+
> #<RootOrderedContent><underline>Moon</underline><italic>384,400 km</italic><bold>bell</bold></RootOrderedContent>
|
1844
|
+
----
|
1845
|
+
|
1846
|
+
**Without Ordered True:**
|
1847
|
+
|
1848
|
+
[source,ruby]
|
1849
|
+
----
|
1850
|
+
class RootOrderedContent < Lutaml::Model::Serializable
|
1851
|
+
attribute :bold, :string
|
1852
|
+
attribute :italic, :string
|
1853
|
+
attribute :underline, :string
|
1854
|
+
|
1855
|
+
xml do
|
1856
|
+
root "RootOrderedContent"
|
1857
|
+
map_element :bold, to: :bold
|
1858
|
+
map_element :italic, to: :italic
|
1859
|
+
map_element :underline, to: :underline
|
1860
|
+
end
|
1861
|
+
end
|
1862
|
+
----
|
1863
|
+
|
1864
|
+
[source,xml]
|
1865
|
+
----
|
1866
|
+
<RootOrderedContent>
|
1867
|
+
<underline>Moon</underline>
|
1868
|
+
<italic>384,400 km</italic>
|
1869
|
+
<bold>bell</bold>
|
1870
|
+
</RootOrderedContent>
|
1871
|
+
----
|
1872
|
+
|
1873
|
+
[source,ruby]
|
1874
|
+
----
|
1875
|
+
> instance = RootOrderedContent.from_xml(xml)
|
1876
|
+
> #<RootOrderedContent:0x0000000104ac7240 @bold="bell", @italic="384,400 km", @underline="Moon">
|
1877
|
+
> instance.to_xml
|
1878
|
+
> #<RootOrderedContent>\n <bold>bell</bold>\n <italic>384,400 km</italic>\n <underline>Moon</underline>\n</RootOrderedContent>
|
1879
|
+
----
|
1880
|
+
====
|
1881
|
+
|
1882
|
+
|
1675
1883
|
[[xml-schema-location]]
|
1676
1884
|
==== Automatic support of `xsi:schemaLocation`
|
1677
1885
|
|
@@ -1793,6 +2001,345 @@ https://www.w3.org/TR/xmlschema-1/#xsi_schemaLocation[W3C XML standard].
|
|
1793
2001
|
|
1794
2002
|
|
1795
2003
|
|
2004
|
+
==== Character encoding
|
2005
|
+
|
2006
|
+
===== General
|
2007
|
+
|
2008
|
+
Lutaml::Model XML adapters use a default encoding of `UTF-8` for both input and
|
2009
|
+
output.
|
2010
|
+
|
2011
|
+
Serialization data to be parsed (deserialization) and serialization data to be
|
2012
|
+
exported (serialization) may be in a different character encoding than the
|
2013
|
+
default encoding used by the Lutaml::Model XML adapter. This mismatch may lead
|
2014
|
+
to incorrect data reading or incompatibilities when exporting data.
|
2015
|
+
|
2016
|
+
The possible values for setting character encoding to are:
|
2017
|
+
|
2018
|
+
* A valid encoding value, e.g. `UTF-8`, `Shift_JIS`, `ASCII`;
|
2019
|
+
|
2020
|
+
* `nil` to use the default encoding of the adapter. The behavior differs based
|
2021
|
+
on the adapter used.
|
2022
|
+
|
2023
|
+
** Nokogiri: `UTF-8`. The encoding is set to the default encoding of the Nokogiri library,
|
2024
|
+
which is `UTF-8`.
|
2025
|
+
|
2026
|
+
** Oga: `UTF-8`. The encoding is set to the default encoding of the Oga library, which
|
2027
|
+
uses `UTF-8`.
|
2028
|
+
|
2029
|
+
** Ox: `ASCII-8bit`. The encoding is set to the default encoding of the Ox library, which uses
|
2030
|
+
`ASCII-8bit`.
|
2031
|
+
|
2032
|
+
When the `encoding` option is not set, the default encoding of `UTF-8` is
|
2033
|
+
used.
|
2034
|
+
|
2035
|
+
|
2036
|
+
===== Serialization character encoding (exporting)
|
2037
|
+
|
2038
|
+
====== General
|
2039
|
+
|
2040
|
+
There are two ways to set the character encoding of the XML document during
|
2041
|
+
serialization:
|
2042
|
+
|
2043
|
+
Instance setting::
|
2044
|
+
Setting the instance-level `encoding` option by setting
|
2045
|
+
`ModelClassInstance.encoding('...')`. This setting only affects serialization.
|
2046
|
+
|
2047
|
+
Per-export setting::
|
2048
|
+
Setting the `encoding` option when calling for serialization action using the
|
2049
|
+
`ModelClassInstance.to_xml(..., encoding: ...)` method.
|
2050
|
+
|
2051
|
+
[[encoding-instance-setting]]
|
2052
|
+
====== Instance setting
|
2053
|
+
|
2054
|
+
The `encoding` value of an instance sets the character encoding of the XML
|
2055
|
+
document during serialization.
|
2056
|
+
|
2057
|
+
Syntax:
|
2058
|
+
|
2059
|
+
[source,ruby]
|
2060
|
+
----
|
2061
|
+
ModelClassInstance.encoding = {encoding_value}
|
2062
|
+
----
|
2063
|
+
|
2064
|
+
Where,
|
2065
|
+
|
2066
|
+
`ModelClassInstance`:: An instance of the class that inherits from
|
2067
|
+
Lutaml::Model::Serializable.
|
2068
|
+
`{encoding_value}`:: The encoding of the output data.
|
2069
|
+
|
2070
|
+
.Character encoding set to instance is reflected in its serialization output
|
2071
|
+
[example]
|
2072
|
+
====
|
2073
|
+
[source,ruby]
|
2074
|
+
----
|
2075
|
+
class JapaneseCeramic < Lutaml::Model::Serializable
|
2076
|
+
attribute :glaze_type, :string
|
2077
|
+
attribute :description, :string
|
2078
|
+
|
2079
|
+
xml do
|
2080
|
+
root 'JapaneseCeramic'
|
2081
|
+
map_attribute 'glazeType', to: :glaze_type
|
2082
|
+
map_element 'description' to: :description
|
2083
|
+
end
|
2084
|
+
end
|
2085
|
+
----
|
2086
|
+
|
2087
|
+
[source,ruby]
|
2088
|
+
----
|
2089
|
+
# Create a new instance with UTF-8 data
|
2090
|
+
> instance = JapaneseCeramic.new(glaze_type: "志野釉", description: "東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)")
|
2091
|
+
#=> #<JapaneseCeramic:0x0000000104ac7240 @glaze_type="志野釉", @description="東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)">
|
2092
|
+
|
2093
|
+
# Set character encoding to Shift_JIS
|
2094
|
+
> instance.encoding = "Shift_JIS"
|
2095
|
+
#=> "Shift_JIS"
|
2096
|
+
|
2097
|
+
# Serialize the instance
|
2098
|
+
> serialization_output = instance.to_xml
|
2099
|
+
#=> #<JapaneseCeramic><glazeType>\x{5FD8}\x{91CE}\x{91C9}</glazeType><description>\x{6771}\x{4EAC}\x{56FD}\x{7ACB}\x{535A}\x{7269}\x{9928}\x{30B3}\x{30EC}\x{30AF}\x{30B7}\x{30E7}\x{30F3}\x{306E}\x{7BC0}\x{8336}\x{7897}\x{300C}\x{6A4B}\x{672C}\x{300D}\x{FF08}\x{6853}\x{5C71}\x{6642}\x{4EE3}\x{FF09}</description></JapaneseCeramic>
|
2100
|
+
|
2101
|
+
# Check character encoding of output
|
2102
|
+
> serialization_output.encoding
|
2103
|
+
#=> "Shift_JIS"
|
2104
|
+
----
|
2105
|
+
====
|
2106
|
+
|
2107
|
+
|
2108
|
+
====== Per-export setting
|
2109
|
+
|
2110
|
+
The `encoding` option is used in the `ModelClass#to_xml(..., encoding: ...)`
|
2111
|
+
call to set the character encoding of the XML document during serialization.
|
2112
|
+
|
2113
|
+
The per-export encoding setting supersedes the instance-level encoding setting.
|
2114
|
+
|
2115
|
+
Syntax:
|
2116
|
+
|
2117
|
+
[source,ruby]
|
2118
|
+
----
|
2119
|
+
ModelClassInstance.to_xml(encoding: {encoding_value})
|
2120
|
+
----
|
2121
|
+
|
2122
|
+
Where,
|
2123
|
+
|
2124
|
+
`ModelClassInstance`:: An instance of the class that inherits from
|
2125
|
+
Lutaml::Model::Serializable.
|
2126
|
+
`{encoding_value}`:: The encoding of the output data.
|
2127
|
+
|
2128
|
+
|
2129
|
+
[example]
|
2130
|
+
====
|
2131
|
+
The following class will parse the XML snippet below:
|
2132
|
+
|
2133
|
+
[source,ruby]
|
2134
|
+
----
|
2135
|
+
class Ceramic < Lutaml::Model::Serializable
|
2136
|
+
attribute :potter, :string
|
2137
|
+
attribute :description, :string
|
2138
|
+
attribute :temperature, :integer
|
2139
|
+
|
2140
|
+
xml do
|
2141
|
+
root 'ceramic'
|
2142
|
+
map_element 'potter', to: :potter
|
2143
|
+
map_content to: :description
|
2144
|
+
end
|
2145
|
+
end
|
2146
|
+
----
|
2147
|
+
|
2148
|
+
[source,xml]
|
2149
|
+
----
|
2150
|
+
<ceramic><potter>John & Jane</potter> A ∑ series of ∏ porcelain µ vases.</ceramic>
|
2151
|
+
----
|
2152
|
+
|
2153
|
+
[source,ruby]
|
2154
|
+
----
|
2155
|
+
# Object with attributes
|
2156
|
+
> ceramic_instance = Ceramic.new(potter: "John & Jane", description: " A ∑ series of ∏ porcelain µ vases.")
|
2157
|
+
> #<Ceramic:0x0000000104ac7240 @potter="John & Jane", @description=" A ∑ series of ∏ porcelain µ vases.">
|
2158
|
+
|
2159
|
+
# Parsing the XML snippet with the default encoding of UTF-8
|
2160
|
+
> ceramic_parsed = Ceramic.from_xml(xml)
|
2161
|
+
> #<Ceramic:0x0000000104ac7242 @potter="John & Jane", @description=" A ∑ series of ∏ porcelain µ vases.">
|
2162
|
+
|
2163
|
+
# Object with attributes is equal to the parsed object
|
2164
|
+
> ceramic_parsed == ceramic_instance
|
2165
|
+
> # true
|
2166
|
+
|
2167
|
+
# Using the default encoding of UTF-8
|
2168
|
+
> ceramic_instance.to_xml
|
2169
|
+
> #<ceramic><potter>John & Jane</potter> A ∑ series of ∏ porcelain µ vases.</ceramic>
|
2170
|
+
|
2171
|
+
# Using the default encoding of the adapter, which is UTF-8 in this case
|
2172
|
+
> ceramic_instance.to_xml(encoding: nil)
|
2173
|
+
> #<ceramic><potter>John & Jane</potter> A ∑ series of ∏ porcelain µ vases.</ceramic>
|
2174
|
+
|
2175
|
+
# Using ASCII encoding
|
2176
|
+
> ceramic_instance.to_xml(encoding: "ASCII")
|
2177
|
+
> #<ceramic><potter>John & Jane</potter> A ∑ series of ∏ porcelain µ vases.</ceramic>
|
2178
|
+
----
|
2179
|
+
====
|
2180
|
+
|
2181
|
+
|
2182
|
+
.Character encoding set at `to_xml` overrides instance encoding
|
2183
|
+
[example]
|
2184
|
+
====
|
2185
|
+
[source,ruby]
|
2186
|
+
----
|
2187
|
+
class JapaneseCeramic < Lutaml::Model::Serializable
|
2188
|
+
attribute :glaze_type, :string
|
2189
|
+
attribute :description, :string
|
2190
|
+
|
2191
|
+
xml do
|
2192
|
+
root 'JapaneseCeramic'
|
2193
|
+
map_attribute 'glazeType', to: :glaze_type
|
2194
|
+
map_element 'description' to: :description
|
2195
|
+
end
|
2196
|
+
end
|
2197
|
+
----
|
2198
|
+
|
2199
|
+
[source,ruby]
|
2200
|
+
----
|
2201
|
+
# Create a new instance with UTF-8 data
|
2202
|
+
> instance = JapaneseCeramic.new(glaze_type: "志野釉", description: "東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)")
|
2203
|
+
#=> #<JapaneseCeramic:0x0000000104ac7240 @glaze_type="志野釉", @description="東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)">
|
2204
|
+
|
2205
|
+
# Set character encoding to Shift_JIS
|
2206
|
+
> instance.encoding = "Shift_JIS"
|
2207
|
+
#=> "Shift_JIS"
|
2208
|
+
|
2209
|
+
# Serialize the instance
|
2210
|
+
> serialization_output = instance.to_xml(encoding: "UTF-8")
|
2211
|
+
#=> #<JapaneseCeramic><glazeType>志野釉</glazeType><description>東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)</description></JapaneseCeramic>
|
2212
|
+
|
2213
|
+
# Check character encoding of output
|
2214
|
+
> serialization_output.encoding
|
2215
|
+
#=> "UTF-8"
|
2216
|
+
----
|
2217
|
+
====
|
2218
|
+
|
2219
|
+
|
2220
|
+
===== Deserialization character encoding (parsing)
|
2221
|
+
|
2222
|
+
The character encoding of the XML document being parsed is specified using the
|
2223
|
+
`encoding` option when the `ModelClass.from_{format}(...)` is called.
|
2224
|
+
|
2225
|
+
Syntax:
|
2226
|
+
|
2227
|
+
[source,ruby]
|
2228
|
+
----
|
2229
|
+
ModelClass.from_{format}(string_in_format, encoding: {encoding_value})
|
2230
|
+
----
|
2231
|
+
|
2232
|
+
Where,
|
2233
|
+
|
2234
|
+
`ModelClass`:: The class that inherits from Lutaml::Model::Serializable.
|
2235
|
+
`{format}`:: The format of the input data, e.g. `xml`, `json`, `yaml`, `toml`.
|
2236
|
+
`string_in_format`:: The input data in the specified format.
|
2237
|
+
`{encoding_value}`:: The encoding of the input data.
|
2238
|
+
|
2239
|
+
|
2240
|
+
.Setting the `encoding` option during parsing data not encoded in the default encoding (UTF-8)
|
2241
|
+
[example]
|
2242
|
+
====
|
2243
|
+
Using the definition of `JapaneseCeramic` at <<encoding-instance-setting>>.
|
2244
|
+
|
2245
|
+
This XML snippet is in Shift-JIS.
|
2246
|
+
|
2247
|
+
[source,xml]
|
2248
|
+
----
|
2249
|
+
<JapaneseCeramic>
|
2250
|
+
<glazeType>\x{5FD8}\x{91CE}\x{91C9}</glazeType>
|
2251
|
+
<description>\x{6771}\x{4EAC}\x{56FD}\x{7ACB}\x{535A}\x{7269}\x{9928}\x{30B3}\x{30EC}\x{30AF}\x{30B7}\x{30E7}\x{30F3}\x{306E}\x{7BC0}\x{8336}\x{7897}\x{300C}\x{6A4B}\x{672C}\x{300D}\x{FF08}\x{6853}\x{5C71}\x{6642}\x{4EE3}\x{FF09}</description>
|
2252
|
+
</JapaneseCeramic>
|
2253
|
+
----
|
2254
|
+
|
2255
|
+
[source,ruby]
|
2256
|
+
----
|
2257
|
+
# Parse the XML snippet with the encoding of Shift_JIS
|
2258
|
+
> instance = JapaneseCeramic.from_xml(xml, encoding: "Shift_JIS")
|
2259
|
+
#=> #<JapaneseCeramic:0x0000000104ac7240 @glaze_type="志野釉", @description="東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)">
|
2260
|
+
|
2261
|
+
# Check character encoding of the instance
|
2262
|
+
> instance.encoding
|
2263
|
+
#=> "Shift_JIS"
|
2264
|
+
|
2265
|
+
# Serialize the instance using UTF-8
|
2266
|
+
> serialization_output = instance.to_xml(encoding: "UTF-8")
|
2267
|
+
#=> #<JapaneseCeramic><glazeType>志野釉</glazeType><description>東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)</description></JapaneseCeramic>
|
2268
|
+
> serialization_output.encoding
|
2269
|
+
#=> "UTF-8"
|
2270
|
+
----
|
2271
|
+
====
|
2272
|
+
|
2273
|
+
.When the `encoding` option is not set, the default encoding of the adapter is used
|
2274
|
+
[example]
|
2275
|
+
====
|
2276
|
+
Using the definition of `JapaneseCeramic` at <<encoding-instance-setting>>.
|
2277
|
+
|
2278
|
+
This XML snippet is in UTF-8.
|
2279
|
+
|
2280
|
+
[source,xml]
|
2281
|
+
----
|
2282
|
+
<JapaneseCeramic>
|
2283
|
+
<glazeType>志野釉</glazeType>
|
2284
|
+
<description>東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)</description>
|
2285
|
+
</JapaneseCeramic>
|
2286
|
+
----
|
2287
|
+
|
2288
|
+
In adapters that use a default encoding of `UTF-8`, the content is parsed
|
2289
|
+
properly.
|
2290
|
+
|
2291
|
+
[source,ruby]
|
2292
|
+
----
|
2293
|
+
> instance = JapaneseCeramic.from_xml(xml, encoding: nil)
|
2294
|
+
#=> #<JapaneseCeramic:0x0000000104ac7240 @glaze_type="志野釉", @description="東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)">
|
2295
|
+
> instance.encoding
|
2296
|
+
#=> "UTF-8"
|
2297
|
+
> serialization_output = instance.to_xml
|
2298
|
+
#=> #<JapaneseCeramic><glazeType>志野釉</glazeType><description>東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)</description></JapaneseCeramic>
|
2299
|
+
> serialization_output.encoding
|
2300
|
+
#=> "UTF-8"
|
2301
|
+
----
|
2302
|
+
|
2303
|
+
In adapters that use a default encoding of `ASCII-8bit`, the content becomes
|
2304
|
+
malformed.
|
2305
|
+
|
2306
|
+
[source,ruby]
|
2307
|
+
----
|
2308
|
+
> instance = JapaneseCeramic.from_xml(xml, encoding: nil)
|
2309
|
+
#=> #<JapaneseCeramic:0x0000000104ac7240 @glaze_type="�菑�", @description="�東京国立博物館コレクションの篠茶碗�橋本�桃山時代�">
|
2310
|
+
> instance.encoding
|
2311
|
+
#=> "ASCII-8bit"
|
2312
|
+
> serialization_output = instance.to_xml
|
2313
|
+
#=> #<JapaneseCeramic><glazeType>�菑�</glazeType><description>�東京国立博物館コレクションの篠茶碗�橋本�桃山時代�</description></JapaneseCeramic>
|
2314
|
+
> serialization_output.encoding
|
2315
|
+
#=> "ASCII-8bit"
|
2316
|
+
----
|
2317
|
+
====
|
2318
|
+
|
2319
|
+
|
2320
|
+
.Using an invalid encoding to deserialize causes data corruption
|
2321
|
+
[example]
|
2322
|
+
====
|
2323
|
+
Using the definition of `JapaneseCeramic` at <<encoding-instance-setting>>.
|
2324
|
+
|
2325
|
+
This XML snippet is in UTF-8.
|
2326
|
+
|
2327
|
+
[source,xml]
|
2328
|
+
----
|
2329
|
+
<JapaneseCeramic>
|
2330
|
+
<glazeType>志野釉</glazeType>
|
2331
|
+
<description>東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)</description>
|
2332
|
+
</JapaneseCeramic>
|
2333
|
+
----
|
2334
|
+
|
2335
|
+
[source,ruby]
|
2336
|
+
----
|
2337
|
+
> JapaneseCeramic.from_xml(xml, encoding: "Shift_JIS")
|
2338
|
+
#=> #<JapaneseCeramic:0x0000000104ac7240 @glaze_type="�菑���p���P", @description="�東京国立博物館コレクションの篠茶碗�橋本�桃山時代�">
|
2339
|
+
----
|
2340
|
+
====
|
2341
|
+
|
2342
|
+
|
1796
2343
|
=== Key value data models
|
1797
2344
|
|
1798
2345
|
==== General
|
@@ -1917,21 +2464,117 @@ end
|
|
1917
2464
|
[source,json]
|
1918
2465
|
----
|
1919
2466
|
{
|
1920
|
-
"name": "John Doe",
|
1921
|
-
"value": 28
|
2467
|
+
"name": "John Doe",
|
2468
|
+
"value": 28
|
2469
|
+
}
|
2470
|
+
----
|
2471
|
+
|
2472
|
+
[source,ruby]
|
2473
|
+
----
|
2474
|
+
> Example.from_json(json)
|
2475
|
+
> #<Example:0x0000000104ac7240 @name="John Doe", @value=28>
|
2476
|
+
> Example.new(name: "John Doe", value: 28).to_json
|
2477
|
+
> #{"name"=>"John Doe", "value"=>28}
|
2478
|
+
----
|
2479
|
+
====
|
2480
|
+
|
2481
|
+
[[key-value-map-all]]
|
2482
|
+
==== Mapping all key-value content
|
2483
|
+
|
2484
|
+
The `map_all` tag captures and maps all content within a serialization format
|
2485
|
+
into a single attribute in the target Ruby object.
|
2486
|
+
|
2487
|
+
The use case for `map_all` is to tell Lutaml::Model to not parse the content at
|
2488
|
+
all, and instead handle it as a raw string.
|
2489
|
+
|
2490
|
+
NOTE: The corresponding method for XML is at <<xml-map-all>>.
|
2491
|
+
|
2492
|
+
WARNING: Notice that usage of mapping all will lead to incompatibility between
|
2493
|
+
serialization formats, i.e. the raw string content will not be portable as
|
2494
|
+
objects are across different formats.
|
2495
|
+
|
2496
|
+
This is useful when the content needs to be handled as-is without parsing into
|
2497
|
+
individual attributes.
|
2498
|
+
|
2499
|
+
The `map_all` tag is **exclusive** and cannot be combined with other mappings,
|
2500
|
+
ensuring it captures the entire content.
|
2501
|
+
|
2502
|
+
NOTE: An error is raised if `map_all` is defined alongside any other mapping in
|
2503
|
+
the same mapping context.
|
2504
|
+
|
2505
|
+
Syntax:
|
2506
|
+
|
2507
|
+
[source,ruby]
|
2508
|
+
----
|
2509
|
+
json | yaml | toml | key_value do
|
2510
|
+
map_all to: :name_of_attribute
|
2511
|
+
end
|
2512
|
+
----
|
2513
|
+
|
2514
|
+
.Using `map_all` to capture all content across different formats
|
2515
|
+
[example]
|
2516
|
+
====
|
2517
|
+
[source,ruby]
|
2518
|
+
----
|
2519
|
+
class Document < Lutaml::Model::Serializable
|
2520
|
+
attribute :content, :string
|
2521
|
+
|
2522
|
+
json do
|
2523
|
+
map_all to: :content
|
2524
|
+
end
|
2525
|
+
|
2526
|
+
yaml do
|
2527
|
+
map_all to: :content
|
2528
|
+
end
|
2529
|
+
|
2530
|
+
toml do
|
2531
|
+
map_all to: :content
|
2532
|
+
end
|
2533
|
+
end
|
2534
|
+
----
|
2535
|
+
|
2536
|
+
For JSON:
|
2537
|
+
[source,json]
|
2538
|
+
----
|
2539
|
+
{
|
2540
|
+
"sections": [
|
2541
|
+
{ "title": "Introduction", "text": "Chapter 1" },
|
2542
|
+
{ "title": "Conclusion", "text": "Final chapter" }
|
2543
|
+
],
|
2544
|
+
"metadata": {
|
2545
|
+
"author": "John Doe",
|
2546
|
+
"date": "2024-01-15"
|
2547
|
+
}
|
1922
2548
|
}
|
1923
2549
|
----
|
1924
2550
|
|
2551
|
+
For YAML:
|
2552
|
+
[source,yaml]
|
2553
|
+
----
|
2554
|
+
sections:
|
2555
|
+
- title: Introduction
|
2556
|
+
text: Chapter 1
|
2557
|
+
- title: Conclusion
|
2558
|
+
text: Final chapter
|
2559
|
+
metadata:
|
2560
|
+
author: John Doe
|
2561
|
+
date: 2024-01-15
|
2562
|
+
----
|
2563
|
+
|
2564
|
+
The content is preserved exactly as provided:
|
2565
|
+
|
1925
2566
|
[source,ruby]
|
1926
2567
|
----
|
1927
|
-
>
|
1928
|
-
>
|
1929
|
-
>
|
1930
|
-
|
2568
|
+
> doc = Document.from_json(json_content)
|
2569
|
+
> puts doc.content
|
2570
|
+
> # "{\"sections\":[{\"title\":\"Introduction\",\"text\":\"Chapter 1\"},{\"title\":\"Conclusion\",\"text\":\"Final chapter\"}],\"metadata\":{\"author\":\"John Doe\",\"date\":\"2024-01-15\"}}"
|
2571
|
+
|
2572
|
+
> doc = Document.from_yaml(yaml_content)
|
2573
|
+
> puts doc.content
|
2574
|
+
> # "sections:\n - title: Introduction\n text: Chapter 1\n - title: Conclusion\n text: Final chapter\nmetadata:\n author: John Doe\n date: 2024-01-15\n"
|
1931
2575
|
----
|
1932
2576
|
====
|
1933
2577
|
|
1934
|
-
|
1935
2578
|
==== Nested attribute mappings
|
1936
2579
|
|
1937
2580
|
The `map` method can also be used to map nested key-value data models
|
@@ -2970,6 +3613,91 @@ end
|
|
2970
3613
|
====
|
2971
3614
|
|
2972
3615
|
|
3616
|
+
=== Rendering default values (forced rendering of default values)
|
3617
|
+
|
3618
|
+
By default, attributes with default values are not rendered if the current value
|
3619
|
+
is the same as the default value.
|
3620
|
+
|
3621
|
+
In certain cases, it is necessary to render the default value even if the
|
3622
|
+
current value is the same as the default value. This is achieved by setting the
|
3623
|
+
`render_default` option to `true`.
|
3624
|
+
|
3625
|
+
Syntax:
|
3626
|
+
|
3627
|
+
[source,ruby]
|
3628
|
+
----
|
3629
|
+
attribute :name_of_attribute, Type, default: -> { value }
|
3630
|
+
|
3631
|
+
xml do
|
3632
|
+
map_element 'name_of_attribute', to: :name_of_attribute, render_default: true
|
3633
|
+
map_attribute 'name_of_attribute', to: :name_of_attribute, render_default: true
|
3634
|
+
end
|
3635
|
+
|
3636
|
+
json | yaml | toml | key_value do
|
3637
|
+
map 'name_of_attribute', to: :name_of_attribute, render_default: true
|
3638
|
+
end
|
3639
|
+
----
|
3640
|
+
|
3641
|
+
.Using the `render_default` option to force encoding the default value
|
3642
|
+
[example]
|
3643
|
+
====
|
3644
|
+
[source,ruby]
|
3645
|
+
----
|
3646
|
+
class Glaze < Lutaml::Model::Serializable
|
3647
|
+
attribute :color, :string, default: -> { 'Clear' }
|
3648
|
+
attribute :opacity, :string, default: -> { 'Opaque' }
|
3649
|
+
attribute :temperature, :integer, default: -> { 1050 }
|
3650
|
+
attribute :firing_time, :integer, default: -> { 60 }
|
3651
|
+
|
3652
|
+
xml do
|
3653
|
+
root "glaze"
|
3654
|
+
map_element 'color', to: :color
|
3655
|
+
map_element 'opacity', to: :opacity, render_default: true
|
3656
|
+
map_attribute 'temperature', to: :temperature
|
3657
|
+
map_attribute 'firingTime', to: :firing_time, render_default: true
|
3658
|
+
end
|
3659
|
+
|
3660
|
+
json do
|
3661
|
+
map 'color', to: :color
|
3662
|
+
map 'opacity', to: :opacity, render_default: true
|
3663
|
+
map 'temperature', to: :temperature
|
3664
|
+
map 'firingTime', to: :firing_time, render_default: true
|
3665
|
+
end
|
3666
|
+
end
|
3667
|
+
----
|
3668
|
+
====
|
3669
|
+
|
3670
|
+
.Attributes with `render_default: true` are rendered when the value is identical to the default
|
3671
|
+
[example]
|
3672
|
+
====
|
3673
|
+
[source,ruby]
|
3674
|
+
----
|
3675
|
+
> glaze_new = Glaze.new
|
3676
|
+
> puts glaze_new.to_xml
|
3677
|
+
# <glaze firingTime="60">
|
3678
|
+
# <opacity>Opaque</opacity>
|
3679
|
+
# </glaze>
|
3680
|
+
> puts glaze_new.to_json
|
3681
|
+
# {"firingTime":60,"opacity":"Opaque"}
|
3682
|
+
----
|
3683
|
+
====
|
3684
|
+
|
3685
|
+
.Attributes with `render_default: true` with non-default values are rendered
|
3686
|
+
[example]
|
3687
|
+
====
|
3688
|
+
[source,ruby]
|
3689
|
+
----
|
3690
|
+
> glaze = Glaze.new(color: 'Celadon', opacity: 'Semitransparent', temperature: 1300, firing_time: 90)
|
3691
|
+
> puts glaze.to_xml
|
3692
|
+
# <glaze color="Celadon" temperature="1300" firingTime="90">
|
3693
|
+
# <opacity>Semitransparent</opacity>
|
3694
|
+
# </glaze>
|
3695
|
+
> puts glaze.to_json
|
3696
|
+
# {"color":"Celadon","temperature":1300,"firingTime":90,"opacity":"Semitransparent"}
|
3697
|
+
----
|
3698
|
+
====
|
3699
|
+
|
3700
|
+
|
2973
3701
|
|
2974
3702
|
=== Advanced attribute mapping
|
2975
3703
|
|
@@ -3149,6 +3877,63 @@ end
|
|
3149
3877
|
NOTE: The corresponding keyword used by Shale is `receiver:` instead of
|
3150
3878
|
`delegate:`.
|
3151
3879
|
|
3880
|
+
=== Value Transformations
|
3881
|
+
|
3882
|
+
The `transform` option allows defining import/export transformations at both attribute and mapping levels:
|
3883
|
+
|
3884
|
+
[source,ruby]
|
3885
|
+
----
|
3886
|
+
class Person < Lutaml::Model::Serializable
|
3887
|
+
# Attribute-level transformation
|
3888
|
+
attribute :name, :string, transform: {
|
3889
|
+
export: ->(value) { value.upcase },
|
3890
|
+
import: ->(value) { value.downcase }
|
3891
|
+
}
|
3892
|
+
|
3893
|
+
# Mapping-level transformation in JSON format
|
3894
|
+
json do
|
3895
|
+
map "fullName", to: :name, transform: {
|
3896
|
+
export: ->(value) { "Dr. #{value}" },
|
3897
|
+
import: ->(value) { value.gsub("Dr. ", "") }
|
3898
|
+
}
|
3899
|
+
end
|
3900
|
+
|
3901
|
+
# Mapping-level transformation in XML format
|
3902
|
+
xml do
|
3903
|
+
map "full-name", to: :name, transform: {
|
3904
|
+
export: ->(value) { "Dr. #{value}" },
|
3905
|
+
import: ->(value) { value.gsub("Dr. ", "") }
|
3906
|
+
}
|
3907
|
+
end
|
3908
|
+
end
|
3909
|
+
----
|
3910
|
+
|
3911
|
+
The transformation precedence is:
|
3912
|
+
|
3913
|
+
1. Mapping-level transformation if defined
|
3914
|
+
2. Attribute-level transformation if no mapping transformation exists
|
3915
|
+
|
3916
|
+
This allows flexible value transformations without needing format-specific custom methods:
|
3917
|
+
|
3918
|
+
[source,ruby]
|
3919
|
+
----
|
3920
|
+
person = Person.new(name: "john")
|
3921
|
+
|
3922
|
+
# Uses mapping transformation
|
3923
|
+
person.to_json # => {"fullName": "Dr. john"}
|
3924
|
+
|
3925
|
+
Person.from_json({ "fullName" => "Dr. john"}.to_json).name # => john
|
3926
|
+
|
3927
|
+
# Uses attribute transformation when no mapping exists
|
3928
|
+
person.to_yaml # => name: "JOHN"
|
3929
|
+
----
|
3930
|
+
|
3931
|
+
The `transform` option supports:
|
3932
|
+
|
3933
|
+
* `export`: Transform value during serialization
|
3934
|
+
* `import`: Transform value during deserialization
|
3935
|
+
* Collections with array transformations
|
3936
|
+
* Chaining of attribute and mapping transformations
|
3152
3937
|
|
3153
3938
|
==== Attribute serialization with custom methods
|
3154
3939
|
|
@@ -3188,15 +3973,22 @@ The following class will parse the XML snippet below:
|
|
3188
3973
|
|
3189
3974
|
[source,ruby]
|
3190
3975
|
----
|
3976
|
+
class Metadata < Lutaml::Model::Serializable
|
3977
|
+
attribute :category, :string
|
3978
|
+
attribute :identifier, :string
|
3979
|
+
end
|
3980
|
+
|
3191
3981
|
class CustomCeramic < Lutaml::Model::Serializable
|
3192
3982
|
attribute :name, :string
|
3193
3983
|
attribute :size, :integer
|
3194
3984
|
attribute :description, :string
|
3985
|
+
attribute :metadata, Metadata
|
3195
3986
|
|
3196
3987
|
xml do
|
3197
3988
|
map_element "Name", to: :name, with: { to: :name_to_xml, from: :name_from_xml }
|
3198
3989
|
map_attribute "Size", to: :size, with: { to: :size_to_xml, from: :size_from_xml }
|
3199
3990
|
map_content with: { to: :description_to_xml, from: :description_from_xml }
|
3991
|
+
map_element :metadata, to: :metadata, with: { to: :metadata_to_xml, from: :metadata_from_xml }
|
3200
3992
|
end
|
3201
3993
|
|
3202
3994
|
def name_to_xml(model, parent, doc)
|
@@ -3224,6 +4016,26 @@ class CustomCeramic < Lutaml::Model::Serializable
|
|
3224
4016
|
def description_from_xml(model, value)
|
3225
4017
|
model.description = value.join.strip.sub(/^XML Description: /, "")
|
3226
4018
|
end
|
4019
|
+
|
4020
|
+
def metadata_to_xml(model, parent, doc)
|
4021
|
+
metadata_el = doc.create_element("metadata")
|
4022
|
+
category_el = doc.create_element("category")
|
4023
|
+
identifier_el = doc.create_element("identifier")
|
4024
|
+
|
4025
|
+
doc.add_text(category_el, model.metadata.category)
|
4026
|
+
doc.add_text(identifier_el, model.metadata.identifier)
|
4027
|
+
|
4028
|
+
doc.add_element(metadata_el, category_el)
|
4029
|
+
doc.add_element(metadata_el, identifier_el)
|
4030
|
+
doc.add_element(parent, metadata_el)
|
4031
|
+
end
|
4032
|
+
|
4033
|
+
def metadata_from_xml(model, value)
|
4034
|
+
model.metadata ||= Metadata.new
|
4035
|
+
|
4036
|
+
model.metadata.category = value["elements"]["category"].text
|
4037
|
+
model.metadata.identifier = value["elements"]["identifier"].text
|
4038
|
+
end
|
3227
4039
|
end
|
3228
4040
|
----
|
3229
4041
|
|
@@ -3232,6 +4044,10 @@ end
|
|
3232
4044
|
<CustomCeramic Size="15">
|
3233
4045
|
<Name>XML Masterpiece: Vase</Name>
|
3234
4046
|
XML Description: A beautiful ceramic vase
|
4047
|
+
<metadata>
|
4048
|
+
<category>Metadata</category>
|
4049
|
+
<identifier>123</identifier>
|
4050
|
+
</metadata>
|
3235
4051
|
</CustomCeramic>
|
3236
4052
|
----
|
3237
4053
|
|
@@ -3243,13 +4059,180 @@ end
|
|
3243
4059
|
@name="Masterpiece: Vase",
|
3244
4060
|
@ordered=nil,
|
3245
4061
|
@size=12,
|
3246
|
-
@description="A beautiful ceramic vase"
|
3247
|
-
|
4062
|
+
@description="A beautiful ceramic vase",
|
4063
|
+
@metadata=#<Metadata:0x0000000105ad52e0 @category="Metadata", @identifier="123">>
|
4064
|
+
> puts CustomCeramic.new(name: "Vase", size: 12, description: "A beautiful vase", metadata: Metadata.new(category: "Glaze", identifier: 15)).to_xml
|
3248
4065
|
# <CustomCeramic Size="15">
|
3249
4066
|
# <Name>XML Masterpiece: Vase</Name>
|
4067
|
+
# <metadata>
|
4068
|
+
# <category>Glaze</category>
|
4069
|
+
# <identifier>15</identifier>
|
4070
|
+
# </metadata>
|
3250
4071
|
# XML Description: A beautiful vase
|
3251
4072
|
# </CustomCeramic>
|
3252
4073
|
----
|
4074
|
+
|
4075
|
+
[source,ruby]
|
4076
|
+
----
|
4077
|
+
def custom_method_from_xml(model, value)
|
4078
|
+
instance = value.node # Lutaml::Model::XmlAdapter::AdapterElement
|
4079
|
+
# OR
|
4080
|
+
instance = value.node.adapter_node # Adapter::Element
|
4081
|
+
|
4082
|
+
xml = instance.to_xml
|
4083
|
+
end
|
4084
|
+
----
|
4085
|
+
|
4086
|
+
When building a model from XML in **custom methods**, if the `value` parameter is a `mapping_hash`, then it allows access to the parsed XML structure through `value.node` which can be converted to an XML string using `to_xml`.
|
4087
|
+
|
4088
|
+
NOTE: For `NokogiriAdapter`, we can also call `to_xml` on `value.node.adapter_node`.
|
4089
|
+
|
4090
|
+
[source,ruby]
|
4091
|
+
----
|
4092
|
+
> value
|
4093
|
+
> # {"text"=>["\n ", "\n ", "\n "], "elements"=>{"category"=>{"text"=>"Metadata"}}}
|
4094
|
+
> value.to_xml
|
4095
|
+
> # undefined_method `to_xml`
|
4096
|
+
|
4097
|
+
> value.node
|
4098
|
+
|
4099
|
+
# Nokogiri Adapter Node
|
4100
|
+
|
4101
|
+
#<Lutaml::Model::XmlAdapter::NokogiriElement:0x0000000107656ed8
|
4102
|
+
# @attributes={},
|
4103
|
+
# @children=
|
4104
|
+
# [#<Lutaml::Model::XmlAdapter::NokogiriElement:0x0000000107656cd0 @attributes={}, @children=[], @default_namespace=nil, @name="text", @namespace_prefix=nil, @text="\n ">,
|
4105
|
+
# #<Lutaml::Model::XmlAdapter::NokogiriElement:0x00000001076569b0
|
4106
|
+
# @attributes={},
|
4107
|
+
# @children=
|
4108
|
+
# [#<Lutaml::Model::XmlAdapter::NokogiriElement:0x00000001076567f8 @attributes={}, @children=[], @default_namespace=nil, @name="text", @namespace_prefix=nil, @text="Metadata">],
|
4109
|
+
# @default_namespace=nil,
|
4110
|
+
# @name="category",
|
4111
|
+
# @namespace_prefix=nil,
|
4112
|
+
# @text="Metadata">,
|
4113
|
+
# #<Lutaml::Model::XmlAdapter::NokogiriElement:0x0000000107656028 @attributes={}, @children=[], @default_namespace=nil, @name="text", @namespace_prefix=nil, @text="\n ">],
|
4114
|
+
# @default_namespace=nil,
|
4115
|
+
# @name="metadata",
|
4116
|
+
# @namespace_prefix=nil,
|
4117
|
+
# @text="\n Metadata\n ">
|
4118
|
+
|
4119
|
+
# Ox Adapter Node
|
4120
|
+
|
4121
|
+
#<Lutaml::Model::XmlAdapter::OxElement:0x0000000107584f78
|
4122
|
+
# @attributes={},
|
4123
|
+
# @children=
|
4124
|
+
# [#<Lutaml::Model::XmlAdapter::OxElement:0x0000000107584e60
|
4125
|
+
# @attributes={},
|
4126
|
+
# @children=[#<Lutaml::Model::XmlAdapter::OxElement:0x0000000107584d48 @attributes={}, @children=[], @default_namespace=nil, @name="text", @namespace_prefix=nil, @text="Metadata">],
|
4127
|
+
# @default_namespace=nil,
|
4128
|
+
# @name="category",
|
4129
|
+
# @namespace_prefix=nil,
|
4130
|
+
# @text="Metadata">],
|
4131
|
+
# @default_namespace=nil,
|
4132
|
+
# @name="metadata",
|
4133
|
+
# @namespace_prefix=nil,
|
4134
|
+
# @text=nil>
|
4135
|
+
|
4136
|
+
# Oga Adapter Node
|
4137
|
+
|
4138
|
+
# <Lutaml::Model::XmlAdapter::Oga::Element:0x0000000107314158
|
4139
|
+
# @attributes={},
|
4140
|
+
# @children=
|
4141
|
+
# [#<Lutaml::Model::XmlAdapter::Oga::Element:0x0000000107314090 @attributes={}, @children=[], @default_namespace=nil, @name="text", @namespace_prefix=nil, @text="\n ">,
|
4142
|
+
# #<Lutaml::Model::XmlAdapter::Oga::Element:0x000000010730fe78
|
4143
|
+
# @attributes={},
|
4144
|
+
# @children=[#<Lutaml::Model::XmlAdapter::Oga::Element:0x000000010730fd88 @attributes={}, @children=[], @default_namespace=nil, @name="text", @namespace_prefix=nil, @text="Metadata">],
|
4145
|
+
# @default_namespace=nil,
|
4146
|
+
# @name="category",
|
4147
|
+
# @namespace_prefix=nil,
|
4148
|
+
# @text="Metadata">,
|
4149
|
+
# #<Lutaml::Model::XmlAdapter::Oga::Element:0x000000010730f8d8 @attributes={}, @children=[], @default_namespace=nil, @name="text", @namespace_prefix=nil, @text="\n ">],
|
4150
|
+
# @default_namespace=nil,
|
4151
|
+
# @name="metadata",
|
4152
|
+
# @namespace_prefix=nil,
|
4153
|
+
# @text="\n Metadata\n ">
|
4154
|
+
|
4155
|
+
> value.node.to_xml
|
4156
|
+
> #<metadata><category>Metadata</category></metadata>
|
4157
|
+
----
|
4158
|
+
====
|
4159
|
+
|
4160
|
+
==== Separate Serialization Model With Custom Methods
|
4161
|
+
|
4162
|
+
[example]
|
4163
|
+
====
|
4164
|
+
The following class will parse the XML snippet below:
|
4165
|
+
|
4166
|
+
[source,ruby]
|
4167
|
+
----
|
4168
|
+
class CustomModelChild
|
4169
|
+
attr_accessor :street, :city
|
4170
|
+
end
|
4171
|
+
|
4172
|
+
class CustomModelChildMapper < Lutaml::Model::Serializable
|
4173
|
+
model CustomModelChild
|
4174
|
+
|
4175
|
+
attribute :street, Lutaml::Model::Type::String
|
4176
|
+
attribute :city, Lutaml::Model::Type::String
|
4177
|
+
|
4178
|
+
xml do
|
4179
|
+
map_element :street, to: :street
|
4180
|
+
map_element :city, to: :city
|
4181
|
+
end
|
4182
|
+
end
|
4183
|
+
|
4184
|
+
class CustomModelParentMapper < Lutaml::Model::Serializable
|
4185
|
+
attribute :first_name, Lutaml::Model::Type::String
|
4186
|
+
attribute :child_mapper, CustomModelChildMapper
|
4187
|
+
|
4188
|
+
xml do
|
4189
|
+
map_element :first_name, to: :first_name
|
4190
|
+
map_element :CustomModelChild,
|
4191
|
+
with: { to: :child_to_xml, from: :child_from_xml }
|
4192
|
+
end
|
4193
|
+
|
4194
|
+
def child_to_xml(model, parent, doc)
|
4195
|
+
child_el = doc.create_element("CustomModelChild")
|
4196
|
+
street_el = doc.create_element("street")
|
4197
|
+
city_el = doc.create_element("city")
|
4198
|
+
|
4199
|
+
doc.add_text(street_el, model.child_mapper.street)
|
4200
|
+
doc.add_text(city_el, model.child_mapper.city)
|
4201
|
+
|
4202
|
+
doc.add_element(child_el, street_el)
|
4203
|
+
doc.add_element(child_el, city_el)
|
4204
|
+
doc.add_element(parent, child_el)
|
4205
|
+
end
|
4206
|
+
|
4207
|
+
def child_from_xml(model, value)
|
4208
|
+
model.child_mapper ||= CustomModelChild.new
|
4209
|
+
|
4210
|
+
model.child_mapper.street = value["elements"]["street"].text
|
4211
|
+
model.child_mapper.city = value["elements"]["city"].text
|
4212
|
+
end
|
4213
|
+
end
|
4214
|
+
----
|
4215
|
+
|
4216
|
+
[source,xml]
|
4217
|
+
----
|
4218
|
+
<CustomModelParent>
|
4219
|
+
<first_name>John</first_name>
|
4220
|
+
<CustomModelChild>
|
4221
|
+
<street>Oxford Street</street>
|
4222
|
+
<city>London</city>
|
4223
|
+
</CustomModelChild>
|
4224
|
+
</CustomModelParent>
|
4225
|
+
----
|
4226
|
+
|
4227
|
+
[source,ruby]
|
4228
|
+
----
|
4229
|
+
> instance = CustomModelParentMapper.from_xml(xml)
|
4230
|
+
> #<CustomModelParent:0x0000000107c9ca68 @child_mapper=#<CustomModelChild:0x0000000107c95218 @city="London", @street="Oxford Street">, @first_name="John">
|
4231
|
+
> CustomModelParentMapper.to_xml(instance)
|
4232
|
+
> #<CustomModelParent><first_name>John</first_name><CustomModelChild><street>Oxford Street</street><city>London</city></CustomModelChild></CustomModelParent>
|
4233
|
+
----
|
4234
|
+
|
4235
|
+
NOTE: For **custom models**, `to_xml` is called on the **mapper class**, not on **model instance**.
|
3253
4236
|
====
|
3254
4237
|
|
3255
4238
|
|
@@ -3310,6 +4293,195 @@ end
|
|
3310
4293
|
====
|
3311
4294
|
|
3312
4295
|
|
4296
|
+
== Importing data models
|
4297
|
+
|
4298
|
+
=== General
|
4299
|
+
|
4300
|
+
Lutaml::Model provides a way to import data models defined from various formats
|
4301
|
+
into the LutaML data modeling system.
|
4302
|
+
|
4303
|
+
Data model languages supported are:
|
4304
|
+
|
4305
|
+
* XSD (https://w3.org/TR/xmlschema-1/[XML Schema Definition Language])
|
4306
|
+
// * RNC (https://relaxng.org/compact-tutorial-20030326.html[RELAX NG Compact Syntax])
|
4307
|
+
// * RNG (https://relaxng.org/relaxng-compact.html[RELAX NG XML Syntax])
|
4308
|
+
// * JSON Schema (https://json-schema.org/understanding-json-schema/[JSON Schema])
|
4309
|
+
// * YAML Schema (https://yaml.org/spec/1.2/spec.html[YAML])
|
4310
|
+
// * LutaML
|
4311
|
+
|
4312
|
+
|
4313
|
+
The following figure illustrates the process of importing an XML Schema model to
|
4314
|
+
create LutaML core models. Once the LutaML core models are created, they can be
|
4315
|
+
used to parse and generate XML documents according to the imported XML Schema
|
4316
|
+
model.
|
4317
|
+
|
4318
|
+
Today, the LutaML core models are written into Ruby files, which can be used to
|
4319
|
+
parse and generate XML documents according to the imported XML Schema.
|
4320
|
+
This is to be changed so that the LutaML core models are directly loaded and
|
4321
|
+
interpreted.
|
4322
|
+
|
4323
|
+
.Importing an XML Schema model to create LutaML core models
|
4324
|
+
[source]
|
4325
|
+
----
|
4326
|
+
╔════════════════════════════╗ ╔═══════════════════════╗
|
4327
|
+
║ Serialization Models ║ ║ Core Model ║
|
4328
|
+
╚════════════════════════════╝ ╚═══════════════════════╝
|
4329
|
+
|
4330
|
+
╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
|
4331
|
+
┆ XML Schema (XSD/RNG/RNC) ┆ ┆ Model ┆
|
4332
|
+
┆ │ ┆ ┌────────────────┐ ┆ │ ┆
|
4333
|
+
┆ ┌──────┴──────┐ ┆ │ │ ┆ ┌────────┴──┐ ┆
|
4334
|
+
┆ │ │ ┆ │ Model │ ┆ │ │ ┆
|
4335
|
+
┆ Models Value Types ┆──►│ Importing │──►┆ Models Value Types ┆
|
4336
|
+
┆ │ │ ┆ │ │ ┆ │ │ ┆
|
4337
|
+
┆ │ │ ┆ └────────────────┘ ┆ │ │ ┆
|
4338
|
+
┆ ┌────┴────┐ ┌─┴─┐ ┆ │ ┆ │ ┌──────┴──┐ ┆
|
4339
|
+
┆ │ │ │ │ ┆ │ ┆ │ │ │ ┆
|
4340
|
+
┆ Element Value xs:string ┆ │ ┆ │ String Integer ┆
|
4341
|
+
┆ Attribute Type xs:date ┆ │ ┆ │ Date Float ┆
|
4342
|
+
┆ Union Complex xs:boolean ┆ │ ┆ │ Time Boolean ┆
|
4343
|
+
┆ Sequence Choice xs:anyURI ┆ │ ┆ │ ┆
|
4344
|
+
╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯ │ ┆ └──────┐ ┆
|
4345
|
+
│ ┆ │ ┆
|
4346
|
+
│ ┆ Contains ┆
|
4347
|
+
│ ┆ more Models ┆
|
4348
|
+
│ ┆ (recursive) ┆
|
4349
|
+
│ ┆ ┆
|
4350
|
+
│ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯
|
4351
|
+
│ ┌────────────────┐
|
4352
|
+
│ │ │
|
4353
|
+
│ │ Model │
|
4354
|
+
└──────────► │ Transformation │
|
4355
|
+
│ & │
|
4356
|
+
│ Mapping Rules │
|
4357
|
+
│ │
|
4358
|
+
└────────────────┘
|
4359
|
+
----
|
4360
|
+
|
4361
|
+
|
4362
|
+
[[xml-schema-to-model-files]]
|
4363
|
+
=== XML Schema (XSD)
|
4364
|
+
|
4365
|
+
W3C XSD is a schema language designed to define the structure of XML documents,
|
4366
|
+
alongside other XML schema languages like DTD, RELAX NG, and Schematron.
|
4367
|
+
|
4368
|
+
Lutaml::Model supports the import of XSD schema files to define information
|
4369
|
+
models that can be used to parse and generate XML documents.
|
4370
|
+
|
4371
|
+
Specifically, the `Lutaml::Model::Schema#from_xml` method loads XML Schema files
|
4372
|
+
(XSD, `*.xsd`) and generates Ruby files (`*.rb`) that inherit from
|
4373
|
+
`Lutaml::Model::Serializable` that are saved to disk.
|
4374
|
+
|
4375
|
+
Syntax:
|
4376
|
+
|
4377
|
+
[source,ruby]
|
4378
|
+
----
|
4379
|
+
Lutaml::Model::Schema.from_xml(
|
4380
|
+
xsd_schema, <1>
|
4381
|
+
options: options <2>
|
4382
|
+
)
|
4383
|
+
----
|
4384
|
+
<1> The `xsd_schema` is the XML Schema string to be converted to model files.
|
4385
|
+
<2> The `options` hash is an optional argument.
|
4386
|
+
|
4387
|
+
`options`:: Optional hash containing potentially the following key-values.
|
4388
|
+
|
4389
|
+
`output_dir`::: The directory where the model files will be saved. If not
|
4390
|
+
provided, a default directory named `lutaml_models_<timestamp>` is created.
|
4391
|
+
+
|
4392
|
+
[example]
|
4393
|
+
`"path/to/directory"`
|
4394
|
+
|
4395
|
+
`create_files`::: A `boolean` argument (`false` by default) to create files directly in the specified directory as defined by the `output_dir` option.
|
4396
|
+
+
|
4397
|
+
[example]
|
4398
|
+
`create_files: (true | false)`
|
4399
|
+
|
4400
|
+
`load_classes`::: A `boolean` argument (`false` by default) to load generated classes before returning them.
|
4401
|
+
+
|
4402
|
+
[example]
|
4403
|
+
`load_classes: (true | false)`
|
4404
|
+
|
4405
|
+
`namespace`::: The namespace of the schema. This will be added in the
|
4406
|
+
`Lutaml::Model::Serializable` file's `xml do` block.
|
4407
|
+
+
|
4408
|
+
[example]
|
4409
|
+
`http://example.com/namespace`
|
4410
|
+
|
4411
|
+
`prefix`::: The prefix of the namespace provided in the `namespace` option.
|
4412
|
+
+
|
4413
|
+
[example]
|
4414
|
+
`example-prefix`
|
4415
|
+
|
4416
|
+
`location`::: The URL or path of the directory containing all the files of the
|
4417
|
+
schema. For more information, refer to the
|
4418
|
+
link:https://www.w3.org/TR/xmlschema-1/#include[XML Schema specification].
|
4419
|
+
+
|
4420
|
+
[example]
|
4421
|
+
`"http://example.com/example.xsd"`
|
4422
|
+
+
|
4423
|
+
[example]
|
4424
|
+
`"path/to/schema/directory"`
|
4425
|
+
|
4426
|
+
NOTE: If both `create_files` and `load_classes` are provided, the `create_files` argument will take priority and generate files without loading them!
|
4427
|
+
|
4428
|
+
The generated LutaML models consists of two different kind of Ruby classes
|
4429
|
+
depending on the XSD schema:
|
4430
|
+
|
4431
|
+
XSD "SimpleTypes":: converted into classes that inherit from
|
4432
|
+
`Lutaml::Model::Type::Value`, which define the data types with restrictions and
|
4433
|
+
other validations of these values.
|
4434
|
+
|
4435
|
+
XSD "ComplexTypes":: converted into classes that inherit from
|
4436
|
+
`Lutaml::Model::Serializable` that model according to the defined structure.
|
4437
|
+
|
4438
|
+
Lutaml::Model uses the https://github.com/lutaml/lutaml-xsd[`lutaml-xsd` gem] to
|
4439
|
+
automatically resolve the `include` and `import` elements, enabling
|
4440
|
+
*Lutaml-Model* to generate the corresponding model files.
|
4441
|
+
|
4442
|
+
This auto-resolving feature allows seamless integration of these files into your
|
4443
|
+
models without the need for manual resolution of includes and imports.
|
4444
|
+
|
4445
|
+
[example]
|
4446
|
+
.Using `Lutaml::Model::Schema#from_xml` to convert an XML Schema to model files
|
4447
|
+
====
|
4448
|
+
[source,ruby]
|
4449
|
+
----
|
4450
|
+
xsd_schema = <<~XSD
|
4451
|
+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
4452
|
+
/* your schema here */
|
4453
|
+
</xs:schema>
|
4454
|
+
XSD
|
4455
|
+
options = {
|
4456
|
+
# These are all optional:
|
4457
|
+
output_dir: 'path/to/directory',
|
4458
|
+
namespace: 'http://example.com/namespace',
|
4459
|
+
prefix: "example-prefix",
|
4460
|
+
location: "http://example.com/example.xsd",
|
4461
|
+
# or
|
4462
|
+
# location: "path/to/schema/directory"
|
4463
|
+
create_files: true, # Default: false
|
4464
|
+
# OR
|
4465
|
+
load_classes: true, # Default: false
|
4466
|
+
}
|
4467
|
+
|
4468
|
+
# generates the files in the output_dir | default_dir
|
4469
|
+
Lutaml::Model::Schema.from_xml(xsd_schema, options: options)
|
4470
|
+
----
|
4471
|
+
====
|
4472
|
+
|
4473
|
+
You could also directly load the generated Ruby files into your application by
|
4474
|
+
requiring them.
|
4475
|
+
|
4476
|
+
[example]
|
4477
|
+
.Using the generated Ruby files in your application
|
4478
|
+
====
|
4479
|
+
[source,ruby]
|
4480
|
+
----
|
4481
|
+
Lutaml::Model::Schema.from_xml(xsd_schema, options: {output_dir: 'path/to/directory'})
|
4482
|
+
require_relative 'path/to/directory/*.rb'
|
4483
|
+
----
|
4484
|
+
====
|
3313
4485
|
|
3314
4486
|
|
3315
4487
|
== Validation
|
@@ -3331,6 +4503,7 @@ Lutaml::Model supports the following validation methods:
|
|
3331
4503
|
|
3332
4504
|
* `collection`:: Validates collection size range.
|
3333
4505
|
* `values`:: Validates the value of an attribute from a set of fixed values.
|
4506
|
+
* `choice` :: Validates that attribute specified within defined range
|
3334
4507
|
|
3335
4508
|
[example]
|
3336
4509
|
====
|
@@ -3344,6 +4517,17 @@ class Klin < Lutaml::Model::Serializable
|
|
3344
4517
|
attribute :name, :string
|
3345
4518
|
attribute :degree_settings, :integer, collection: (1..)
|
3346
4519
|
attribute :description, :string, values: %w[one two three]
|
4520
|
+
attribute :id, :integer
|
4521
|
+
attribute :age, :integer
|
4522
|
+
|
4523
|
+
choice(min: 1, max: 1) do
|
4524
|
+
choice(min: 1, max: 2) do
|
4525
|
+
attribute :prefix, :string
|
4526
|
+
attribute :forename, :string
|
4527
|
+
end
|
4528
|
+
|
4529
|
+
attribute :nick_name, :string
|
4530
|
+
end
|
3347
4531
|
|
3348
4532
|
xml do
|
3349
4533
|
map_element 'name', to: :name
|
@@ -3351,26 +4535,30 @@ class Klin < Lutaml::Model::Serializable
|
|
3351
4535
|
end
|
3352
4536
|
end
|
3353
4537
|
|
3354
|
-
klin = Klin.new(name: "Klin", degree_settings: [100, 200, 300], description: "one")
|
4538
|
+
klin = Klin.new(name: "Klin", degree_settings: [100, 200, 300], description: "one", prefix: "Ben")
|
3355
4539
|
klin.validate
|
3356
4540
|
# => []
|
3357
4541
|
|
3358
|
-
klin = Klin.new(name: "Klin", degree_settings: [], description: "four")
|
4542
|
+
klin = Klin.new(name: "Klin", degree_settings: [], description: "four", prefix: "Ben", nick_name: "Smith")
|
3359
4543
|
klin.validate
|
3360
4544
|
# => [
|
3361
4545
|
# #<Lutaml::Model::CollectionSizeError: degree_settings must have at least 1 element>,
|
3362
|
-
# #<Lutaml::Model::ValueError: description must be one of [one, two, three]
|
4546
|
+
# #<Lutaml::Model::ValueError: description must be one of [one, two, three]>,
|
4547
|
+
# #<Lutaml::Model::ChoiceUpperBoundError: Attribute count exceeds the upper bound>
|
3363
4548
|
# ]
|
3364
4549
|
|
3365
4550
|
e = klin.validate!
|
3366
4551
|
# => Lutaml::Model::ValidationError: [
|
3367
4552
|
# degree_settings must have at least 1 element,
|
3368
|
-
# description must be one of [one, two, three]
|
4553
|
+
# description must be one of [one, two, three],
|
4554
|
+
# Attribute count exceeds the upper bound
|
3369
4555
|
# ]
|
3370
4556
|
e.errors
|
3371
4557
|
# => [
|
3372
4558
|
# #<Lutaml::Model::CollectionSizeError: degree_settings must have at least 1 element>,
|
3373
|
-
# #<Lutaml::Model::ValueError: description must be one of [one, two, three]
|
4559
|
+
# #<Lutaml::Model::ValueError: description must be one of [one, two, three]>,
|
4560
|
+
# #<Lutaml::Model::ChoiceUpperBoundError: Attribute count exceeds the upper bound>
|
4561
|
+
# #<Lutaml::Model::ChoiceLowerBoundError: Attribute count is less than lower bound>
|
3374
4562
|
# ]
|
3375
4563
|
----
|
3376
4564
|
====
|
@@ -3407,6 +4595,30 @@ klin.validate
|
|
3407
4595
|
====
|
3408
4596
|
|
3409
4597
|
|
4598
|
+
== Liquid Compatability
|
4599
|
+
|
4600
|
+
`to_liquid` can be used to convert a class that inherit from *Lutaml::Model::Serializable* to `LiquidDrop` to be safely used in liquid templates. The returned drop provides all the attributes defined in the class as methods.
|
4601
|
+
|
4602
|
+
[example]
|
4603
|
+
====
|
4604
|
+
[source,ruby]
|
4605
|
+
----
|
4606
|
+
class Person < Lutaml::Model::Serializable
|
4607
|
+
attribute :name, :string
|
4608
|
+
attribute :age, integer
|
4609
|
+
end
|
4610
|
+
|
4611
|
+
person = Person.new({ name: "John", age: 22 })
|
4612
|
+
person_drop = person.to_liquid
|
4613
|
+
# Person::PersonDrop
|
4614
|
+
|
4615
|
+
puts person_drop.name
|
4616
|
+
# "John"
|
4617
|
+
puts person_drop.age
|
4618
|
+
# 22
|
4619
|
+
----
|
4620
|
+
====
|
4621
|
+
|
3410
4622
|
== Adapters
|
3411
4623
|
|
3412
4624
|
=== General
|
@@ -3701,6 +4913,15 @@ attribute for every element.
|
|
3701
4913
|
| Requires manual specification on every XML element that uses it.
|
3702
4914
|
|
|
3703
4915
|
|
4916
|
+
|
4917
|
+
| Compiling XML Schema to *Lutaml::Model::Serializable* classes
|
4918
|
+
| Yes. Using <<xml-schema-to-model-files, `Lutaml::Model::Schema#from_xml`>>
|
4919
|
+
|
4920
|
+
1. *ComplexTypes* are compiled to *Lutaml::Model::Serializable* classes containing the attributes.
|
4921
|
+
2. *SimpleTypes* are compiled to *Lutaml::Model::Type::Value* classes to support XML Schema level validations.
|
4922
|
+
| Yes, Provides only an array of the classes and doesn't support `simple types` with restrictions and/or other validations.
|
4923
|
+
|
|
4924
|
+
|
3704
4925
|
4+h| Attribute features
|
3705
4926
|
|
3706
4927
|
| Attribute delegation
|