lutaml-model 0.5.2 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-tests.yml +2 -0
  3. data/.rubocop_todo.yml +39 -13
  4. data/Gemfile +1 -0
  5. data/README.adoc +430 -52
  6. data/lib/lutaml/model/constants.rb +7 -0
  7. data/lib/lutaml/model/error/type/invalid_value_error.rb +19 -0
  8. data/lib/lutaml/model/error.rb +1 -0
  9. data/lib/lutaml/model/key_value_mapping.rb +31 -2
  10. data/lib/lutaml/model/mapping_hash.rb +8 -0
  11. data/lib/lutaml/model/mapping_rule.rb +8 -0
  12. data/lib/lutaml/model/schema/templates/simple_type.rb +247 -0
  13. data/lib/lutaml/model/schema/xml_compiler.rb +720 -0
  14. data/lib/lutaml/model/schema.rb +5 -0
  15. data/lib/lutaml/model/serialize.rb +33 -13
  16. data/lib/lutaml/model/toml_adapter/toml_rb_adapter.rb +1 -2
  17. data/lib/lutaml/model/type/hash.rb +11 -11
  18. data/lib/lutaml/model/utils.rb +7 -0
  19. data/lib/lutaml/model/version.rb +1 -1
  20. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +5 -1
  21. data/lib/lutaml/model/xml_adapter/xml_document.rb +11 -15
  22. data/lib/lutaml/model/xml_mapping.rb +4 -2
  23. data/lib/lutaml/model/xml_mapping_rule.rb +1 -4
  24. data/lib/lutaml/model.rb +1 -0
  25. data/spec/fixtures/xml/invalid_math_document.xml +4 -0
  26. data/spec/fixtures/xml/math_document_schema.xsd +56 -0
  27. data/spec/fixtures/xml/test_schema.xsd +53 -0
  28. data/spec/fixtures/xml/valid_math_document.xml +4 -0
  29. data/spec/lutaml/model/cdata_spec.rb +2 -2
  30. data/spec/lutaml/model/custom_model_spec.rb +7 -20
  31. data/spec/lutaml/model/key_value_mapping_spec.rb +27 -0
  32. data/spec/lutaml/model/map_all_spec.rb +188 -0
  33. data/spec/lutaml/model/mixed_content_spec.rb +15 -15
  34. data/spec/lutaml/model/render_nil_spec.rb +29 -0
  35. data/spec/lutaml/model/schema/xml_compiler_spec.rb +1431 -0
  36. data/spec/lutaml/model/with_child_mapping_spec.rb +2 -2
  37. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +52 -0
  38. data/spec/lutaml/model/xml_mapping_spec.rb +108 -1
  39. metadata +12 -2
data/README.adoc CHANGED
@@ -13,8 +13,9 @@ objects to and from various formats such as JSON, XML, YAML, and TOML. It uses
13
13
  an adapter pattern to support multiple libraries for each format, providing
14
14
  flexibility and extensibility for your data modeling needs.
15
15
 
16
- NOTE: Lutaml::Model is designed to be mostly compatible with the data modeling
17
- API of https://www.shalerb.org[Shale], an impressive Ruby data modeller.
16
+ NOTE: The Lutaml::Model modeling Ruby API is designed to be mostly compatible
17
+ with the data modeling API of https://www.shalerb.org[Shale], a data modeller
18
+ for Ruby.
18
19
  Lutaml::Model is meant to address advanced needs not currently addressed by
19
20
  Shale.
20
21
 
@@ -48,12 +49,12 @@ The Lutaml::Model data modelling approach is as follows:
48
49
  .Modeling relationships of a LutaML Model
49
50
  [source]
50
51
  ----
51
- Lutaml Model
52
+ LutaML Model
52
53
 
53
54
  Has many attributes
54
55
 
55
56
 
56
- Attribute
57
+ Attribute
57
58
 
58
59
  Has type of
59
60
 
@@ -95,54 +96,144 @@ Studio (Model)
95
96
  .Modeling relationships of a LutaML Model to serialization models
96
97
  [source]
97
98
  ----
98
- Core Model Serialization Model
99
- ========== ===================
100
- │ (mapping)
101
- │ │
102
- ▼ ▼
103
- Model XML Model
104
-
105
- ┌─────┴─────┐ ┌──────┴──────┐
106
- │ │ │ │
107
- Models Value Types ────┬──► Models Value Types
108
- │ │ │ │ │
109
- │ │ │ │ │
110
- │ ┌──────┴──┐ │ ┌────┴────┐ ┌─┴─┐
111
- │ │ │ │ │ │ │
112
- │ String Integer │ Element Value xs:string
113
- │ Date Float │ Attribute Type xs:date
114
- │ Time Boolean xs:boolean
115
- │ xs:anyURI
116
- └──────┐
117
- JSON Model
118
- Contains
119
- more Models │ ┌──────┴──────┐
120
- (recursive) │
121
- └──► Models Value Types
122
- │ │
123
- │ │
124
- ┌────┴────┐ ┌─┴─┐
125
- │ │
126
- object array number string
127
- value boolean null
99
+ ╔═══════════════════════╗ ╔════════════════════════════╗
100
+ ║ LutaML Core Model ║ ║ Serialization Models ║
101
+ ╚═══════════════════════╝ ╚════════════════════════════╝
102
+
103
+ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
104
+ Model ┆ ┆ XML Model
105
+ ┆ ┌────────────────┐ ┆
106
+ ┆ ┌────────┴──┐ ┆ │ │ ┆ ┌──────┴──────┐
107
+ │ │ Model │ ┆ │
108
+ Models Value Types ┆──►│ Transformation │ ┆ Models Value Types
109
+ │ │& ┆ │
110
+ │ │ Mapping Rules │ ┆ │ │
111
+ │ ┌──────┴──┐ │ ┆ ┌────┴────┐ ┌─┴─┐
112
+ │ │ │ ┆ └────────────────┘ ┆ │ │ │ │
113
+ │ String Integer Element Value xs:string
114
+ │ Date Float Attribute Type xs:date
115
+ │ Time Boolean ┆ ├──────► ┆ xs:boolean
116
+ xs:anyURI
117
+ └──────┐ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯
118
+
119
+ Contains ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
120
+ more Models ┆ │ ┆ JSON Model ┆
121
+ (recursive) ┆ │
122
+ ┆ ┆ │ ┆ ┌──────┴──────┐ ┆
123
+ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯ └───────► ┆ │ │
124
+ ┆ Models Value Types ┆
125
+ ┆ │ │
126
+
127
+ ┌────┴───┐ ┌───┴──┐ ┆
128
+ ┆ │ │ │ │ ┆
129
+ ┆ object array number string ┆
130
+ ┆ value boolean null ┆
131
+ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯
132
+ ----
133
+
134
+ .Model transformation of a LutaML Model to another LutaML Model
135
+ [source]
136
+ ----
137
+ ╔═══════════════════════╗ ╔══════════════════╗ ╔═══════════════════════╗
138
+ ║LutaML Model Class FOO ║ ║LutaML Transformer║ ║LutaML Model Class BAR ║
139
+ ╚═══════════════════════╝ ╚══════════════════╝ ╚═══════════════════════╝
140
+
141
+ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
142
+ ┆ Model ┆ ┆ Model ┆
143
+ ┆ │ ┆ ┌────────────────┐ ┆ │ ┆
144
+ ┆ ┌────────┴──┐ ┆ │ │ ┆ ┌────────┴──┐ ┆
145
+ ┆ │ │ ┆ │ Model │ ┆ │ │ ┆
146
+ ┆ Models Value Types ┆───►│ Transformation │───►┆ Models Value Types ┆
147
+ ┆ │ │ ┆◄───│ & │◄───┆ │ │ ┆
148
+ ┆ │ │ ┆ │ Mapping Rules │ ┆ │ │ ┆
149
+ ┆ │ ┌──────┴──┐ ┆ │ │ ┆ │ ┌──────┴──┐ ┆
150
+ ┆ │ │ │ ┆ └────────────────┘ ┆ │ │ │ ┆
151
+ ┆ │ String Integer ┆ ┆ │ String Integer ┆
152
+ ┆ │ Date Float ┆ ┆ │ Date Float ┆
153
+ ┆ │ Time Boolean ┆ ┆ │ Time Boolean ┆
154
+ ┆ │ ┆ ┆ │ ┆
155
+ ┆ └──────┐ ┆ ┆ └──────┐ ┆
156
+ ┆ │ ┆ ┆ │ ┆
157
+ ┆ Contains ┆ ┆ Contains ┆
158
+ ┆ more Models ┆ ┆ more Models ┆
159
+ ┆ (recursive) ┆ ┆ (recursive) ┆
160
+ ┆ ┆ ┆ ┆
161
+ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯
162
+ ----
163
+
164
+ .The `Value` class, transformation, and serialization formats
165
+ [source]
166
+ ----
167
+ ╔═══════════════════════╗ ╔═══════════════════════╗
168
+ ║LutaML Value Class FOO ║ ║ Serialization Value ║
169
+ ╚═══════════════════════╝ ╚═══════════════════════╝
170
+ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
171
+ ┆ ┌───────────────┐ ┆ ┆ ┌───────────────┐ ┆
172
+ ┆ │ Value │ ┆ ┌──────────────────┐ ┆ │ XML Value │ ┆
173
+ ┆ └───────────────┘ ┆──►│ Value Serializer │──►┆ └───────────────┘ ┆
174
+ ┆ ┌───────────────┐ ┆ └──────────────────┘ ┆ ┌───────────────┐ ┆
175
+ ┆ │Primitive Types│ ┆ ┆ │XML Value Types│ ┆
176
+ ┆ └───────────────┘ ┆ ┆ └───────────────┘ ┆
177
+ ┆ ┌───┘ ┆ ┆ ┌───┘ ┆
178
+ ┆ ├─ string ┆ ┆ ├─ xs:string ┆
179
+ ┆ ├─ integer ┆ ┆ ├─ xs:integer ┆
180
+ ┆ ├─ float ┆ ┆ ├─ xs:decimal ┆
181
+ ┆ ├─ boolean ┆ ┆ ├─ xs:boolean ┆
182
+ ┆ ├─ date ┆ ┆ ├─ xs:date ┆
183
+ ┆ ├─ time_without_date ┆ ┆ ├─ xs:time ┆
184
+ ┆ ├─ date_time ┆ ┆ ├─ xs:dateTime ┆
185
+ ┆ ├─ time ┆ ┆ ├─ xs:decimal ┆
186
+ ┆ ├─ decimal ┆ ┆ ├─ xs:anyType ┆
187
+ ┆ └─ hash ┆ ┆ └─ (complex element) ┆
188
+ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯
189
+
190
+
191
+ ┌───────────────────┐
192
+ │ Value Transformer │
193
+ └───────────────────┘
194
+
195
+
196
+ ╔═══════════════════════╗
197
+ ║LutaML Value Class BAR ║
198
+ ╚═══════════════════════╝
199
+ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
200
+ ┆ ┌───────────────┐ ┆
201
+ ┆ │ Value │ ┆
202
+ ┆ └───────────────┘ ┆
203
+ ┆ ┌───────────────┐ ┆
204
+ ┆ │Primitive Types│ ┆
205
+ ┆ └───────────────┘ ┆
206
+ ┆ ┌───┘ ┆
207
+ ┆ ├─ string ┆
208
+ ┆ ├─ integer ┆
209
+ ┆ ├─ float ┆
210
+ ┆ ├─ boolean ┆
211
+ ┆ ├─ date ┆
212
+ ┆ ├─ time_without_date ┆
213
+ ┆ ├─ date_time ┆
214
+ ┆ ├─ time ┆
215
+ ┆ ├─ decimal ┆
216
+ ┆ └─ hash ┆
217
+ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯
128
218
  ----
129
219
 
130
220
  .Example of LutaML Model instance transformed into a serialization model and serialized to JSON
131
221
  ====
132
222
  [source]
133
223
  ----
134
- Studio (Core Model) JSON Model Serialized JSON
135
- │ │ │
136
- ▼ ▼ ▼
137
- name: "Studio 1" ┌─► { "name": "...", {
138
- address: │ "address": { "name": "Studio 1",
139
- ├── street: "..." "street": "...", "address": {
140
- └── city: "..." │ "city": "..." ──► "street": "...",
141
- kilns: }, "city": "..."
142
- ├── count: 3 "kilnsCount": ..., },
143
- └── temp: 1200 │ "kilnsTemp": ... "kilnsCount": 3,
144
- └─► } "kilnsTemp": 1200
145
- }
224
+ ╔═════════════════════╗ ╔═════════════════════╗ ╔═════════════════════╗
225
+ ║ Studio (Core Model) ║ ║ JSON Model ║ ║ Serialized JSON ║
226
+ ╚═════════════════════╝ ╚═════════════════════╝ ╚═════════════════════╝
227
+
228
+ name: "Studio 1" ┌─► { ┌─► {
229
+ address: "name": "...", "name": "Studio 1",
230
+ ├── street: "..." │ "address": { │ "address": {
231
+ └── city: "..." "street": "...","street": "...",
232
+ kilns: ──┤ "city": "..." ──┤ "city": "..."
233
+ ├── count: 3 │ }, │ },
234
+ └── temp: 1200 │ "kilnsCount": ..., │ "kilnsCount": 3,
235
+ │ "kilnsTemp": ... │ "kilnsTemp": 1200
236
+ └─► } └─► }
146
237
  ----
147
238
  ====
148
239
 
@@ -278,6 +369,7 @@ attribute :name_of_attribute, {symbol | string | class}
278
369
  | `:time` | `Time` | `xs:dateTime` | `string` | `string` | `"2024-01-01T12:00:00+00:00"`
279
370
  | `:decimal` (optional) | `BigDecimal` | `xs:decimal` | `number` | `float` | `123.45`
280
371
  | `:hash` | `Hash` | complex element | object | map | `{key: "value"}`
372
+ | (nil value) | `nil` | `xs:anyType` | `null` | `null` | `null`
281
373
  // | `class` | Custom class | complex element | object | map | `CustomObject`
282
374
  // | `collection: true` | `Array` of type | repeated elements | array | sequence | `[obj1, obj2]`
283
375
  // | `any`
@@ -921,10 +1013,8 @@ end
921
1013
  ----
922
1014
  ====
923
1015
 
924
-
925
- ==== Mapping all content (XML only)
926
-
927
- WARNING: This feature is only applicable to XML (for now).
1016
+ [[xml-map-all]]
1017
+ ==== Mapping all XML content
928
1018
 
929
1019
  The `map_all` tag in XML mapping captures and maps all content within an XML
930
1020
  element into a single attribute in the target Ruby object.
@@ -932,6 +1022,12 @@ element into a single attribute in the target Ruby object.
932
1022
  The use case for `map_all` is to tell Lutaml::Model to not parse the content of
933
1023
  the XML element at all, and instead handle it as an XML string.
934
1024
 
1025
+ NOTE: The corresponding method for key-value formats is at <<key-value-map-all>>.
1026
+
1027
+ WARNING: Notice that usage of mapping all will lead to incompatibility between
1028
+ serialization formats, i.e. the raw string content will not be portable as
1029
+ objects are across different formats.
1030
+
935
1031
  This is useful in the case where the content of an XML element is not to be
936
1032
  handled by a Lutaml::Model::Serializable object.
937
1033
 
@@ -944,8 +1040,9 @@ This includes:
944
1040
  * attributes
945
1041
  * text nodes
946
1042
 
947
- The `map_all` tag is **exclusive** and cannot be combined with other mappings (`map_element`, `map_content`) except for `map_attribute` for the same element, ensuring
948
- it captures the entire inner XML content.
1043
+ The `map_all` tag is **exclusive** and cannot be combined with other mappings
1044
+ (`map_element`, `map_content`) except for `map_attribute` for the same element,
1045
+ ensuring it captures the entire inner XML content.
949
1046
 
950
1047
  NOTE: An error is raised if `map_all` is defined alongside any other mapping in
951
1048
  the same XML mapping context.
@@ -1667,6 +1764,7 @@ end
1667
1764
 
1668
1765
  // TODO: How to create mixed content from `#new`?
1669
1766
 
1767
+
1670
1768
  [[xml-schema-location]]
1671
1769
  ==== Automatic support of `xsi:schemaLocation`
1672
1770
 
@@ -1926,6 +2024,102 @@ end
1926
2024
  ----
1927
2025
  ====
1928
2026
 
2027
+ [[key-value-map-all]]
2028
+ ==== Mapping all key-value content
2029
+
2030
+ The `map_all` tag captures and maps all content within a serialization format
2031
+ into a single attribute in the target Ruby object.
2032
+
2033
+ The use case for `map_all` is to tell Lutaml::Model to not parse the content at
2034
+ all, and instead handle it as a raw string.
2035
+
2036
+ NOTE: The corresponding method for XML is at <<xml-map-all>>.
2037
+
2038
+ WARNING: Notice that usage of mapping all will lead to incompatibility between
2039
+ serialization formats, i.e. the raw string content will not be portable as
2040
+ objects are across different formats.
2041
+
2042
+ This is useful when the content needs to be handled as-is without parsing into
2043
+ individual attributes.
2044
+
2045
+ The `map_all` tag is **exclusive** and cannot be combined with other mappings,
2046
+ ensuring it captures the entire content.
2047
+
2048
+ NOTE: An error is raised if `map_all` is defined alongside any other mapping in
2049
+ the same mapping context.
2050
+
2051
+ Syntax:
2052
+
2053
+ [source,ruby]
2054
+ ----
2055
+ json | yaml | toml | key_value do
2056
+ map_all to: :name_of_attribute
2057
+ end
2058
+ ----
2059
+
2060
+ .Using `map_all` to capture all content across different formats
2061
+ [example]
2062
+ ====
2063
+ [source,ruby]
2064
+ ----
2065
+ class Document < Lutaml::Model::Serializable
2066
+ attribute :content, :string
2067
+
2068
+ json do
2069
+ map_all to: :content
2070
+ end
2071
+
2072
+ yaml do
2073
+ map_all to: :content
2074
+ end
2075
+
2076
+ toml do
2077
+ map_all to: :content
2078
+ end
2079
+ end
2080
+ ----
2081
+
2082
+ For JSON:
2083
+ [source,json]
2084
+ ----
2085
+ {
2086
+ "sections": [
2087
+ { "title": "Introduction", "text": "Chapter 1" },
2088
+ { "title": "Conclusion", "text": "Final chapter" }
2089
+ ],
2090
+ "metadata": {
2091
+ "author": "John Doe",
2092
+ "date": "2024-01-15"
2093
+ }
2094
+ }
2095
+ ----
2096
+
2097
+ For YAML:
2098
+ [source,yaml]
2099
+ ----
2100
+ sections:
2101
+ - title: Introduction
2102
+ text: Chapter 1
2103
+ - title: Conclusion
2104
+ text: Final chapter
2105
+ metadata:
2106
+ author: John Doe
2107
+ date: 2024-01-15
2108
+ ----
2109
+
2110
+ The content is preserved exactly as provided:
2111
+
2112
+ [source,ruby]
2113
+ ----
2114
+ > doc = Document.from_json(json_content)
2115
+ > puts doc.content
2116
+ > # "{\"sections\":[{\"title\":\"Introduction\",\"text\":\"Chapter 1\"},{\"title\":\"Conclusion\",\"text\":\"Final chapter\"}],\"metadata\":{\"author\":\"John Doe\",\"date\":\"2024-01-15\"}}"
2117
+
2118
+ > doc = Document.from_yaml(yaml_content)
2119
+ > puts doc.content
2120
+ > # "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"
2121
+ ----
2122
+ ====
1929
2123
 
1930
2124
  ==== Nested attribute mappings
1931
2125
 
@@ -3305,6 +3499,181 @@ end
3305
3499
  ====
3306
3500
 
3307
3501
 
3502
+ == Importing data models
3503
+
3504
+ === General
3505
+
3506
+ Lutaml::Model provides a way to import data models defined from various formats
3507
+ into the LutaML data modeling system.
3508
+
3509
+ Data model languages supported are:
3510
+
3511
+ * XSD (https://w3.org/TR/xmlschema-1/[XML Schema Definition Language])
3512
+ // * RNC (https://relaxng.org/compact-tutorial-20030326.html[RELAX NG Compact Syntax])
3513
+ // * RNG (https://relaxng.org/relaxng-compact.html[RELAX NG XML Syntax])
3514
+ // * JSON Schema (https://json-schema.org/understanding-json-schema/[JSON Schema])
3515
+ // * YAML Schema (https://yaml.org/spec/1.2/spec.html[YAML])
3516
+ // * LutaML
3517
+
3518
+
3519
+ The following figure illustrates the process of importing an XML Schema model to
3520
+ create LutaML core models. Once the LutaML core models are created, they can be
3521
+ used to parse and generate XML documents according to the imported XML Schema
3522
+ model.
3523
+
3524
+ Today, the LutaML core models are written into Ruby files, which can be used to
3525
+ parse and generate XML documents according to the imported XML Schema.
3526
+ This is to be changed so that the LutaML core models are directly loaded and
3527
+ interpreted.
3528
+
3529
+ .Importing an XML Schema model to create LutaML core models
3530
+ [source]
3531
+ ----
3532
+ ╔════════════════════════════╗ ╔═══════════════════════╗
3533
+ ║ Serialization Models ║ ║ Core Model ║
3534
+ ╚════════════════════════════╝ ╚═══════════════════════╝
3535
+
3536
+ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
3537
+ ┆ XML Schema (XSD/RNG/RNC) ┆ ┆ Model ┆
3538
+ ┆ │ ┆ ┌────────────────┐ ┆ │ ┆
3539
+ ┆ ┌──────┴──────┐ ┆ │ │ ┆ ┌────────┴──┐ ┆
3540
+ ┆ │ │ ┆ │ Model │ ┆ │ │ ┆
3541
+ ┆ Models Value Types ┆──►│ Importing │──►┆ Models Value Types ┆
3542
+ ┆ │ │ ┆ │ │ ┆ │ │ ┆
3543
+ ┆ │ │ ┆ └────────────────┘ ┆ │ │ ┆
3544
+ ┆ ┌────┴────┐ ┌─┴─┐ ┆ │ ┆ │ ┌──────┴──┐ ┆
3545
+ ┆ │ │ │ │ ┆ │ ┆ │ │ │ ┆
3546
+ ┆ Element Value xs:string ┆ │ ┆ │ String Integer ┆
3547
+ ┆ Attribute Type xs:date ┆ │ ┆ │ Date Float ┆
3548
+ ┆ Union Complex xs:boolean ┆ │ ┆ │ Time Boolean ┆
3549
+ ┆ Sequence Choice xs:anyURI ┆ │ ┆ │ ┆
3550
+ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯ │ ┆ └──────┐ ┆
3551
+ │ ┆ │ ┆
3552
+ │ ┆ Contains ┆
3553
+ │ ┆ more Models ┆
3554
+ │ ┆ (recursive) ┆
3555
+ │ ┆ ┆
3556
+ │ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯
3557
+ │ ┌────────────────┐
3558
+ │ │ │
3559
+ │ │ Model │
3560
+ └──────────► │ Transformation │
3561
+ │ & │
3562
+ │ Mapping Rules │
3563
+ │ │
3564
+ └────────────────┘
3565
+ ----
3566
+
3567
+
3568
+ [[xml-schema-to-model-files]]
3569
+ === XML Schema (XSD)
3570
+
3571
+ W3C XSD is a schema language designed to define the structure of XML documents,
3572
+ alongside other XML schema languages like DTD, RELAX NG, and Schematron.
3573
+
3574
+ Lutaml::Model supports the import of XSD schema files to define information
3575
+ models that can be used to parse and generate XML documents.
3576
+
3577
+ Specifically, the `Lutaml::Model::Schema#from_xml` method loads XML Schema files
3578
+ (XSD, `*.xsd`) and generates Ruby files (`*.rb`) that inherit from
3579
+ `Lutaml::Model::Serializable` that are saved to disk.
3580
+
3581
+ Syntax:
3582
+
3583
+ [source,ruby]
3584
+ ----
3585
+ Lutaml::Model::Schema.from_xml(
3586
+ xsd_schema, <1>
3587
+ options: options <2>
3588
+ )
3589
+ ----
3590
+ <1> The `xsd_schema` is the XML Schema string to be converted to model files.
3591
+ <2> The `options` hash is an optional argument.
3592
+
3593
+ `options`:: Optional hash containing potentially the following key-values.
3594
+
3595
+ `output_dir`::: The directory where the model files will be saved. If not
3596
+ provided, a default directory named `lutaml_models_<timestamp>` is created.
3597
+ +
3598
+ [example]
3599
+ `"path/to/directory"`
3600
+
3601
+ `namespace`::: The namespace of the schema. This will be added in the
3602
+ `Lutaml::Model::Serializable` file's `xml do` block.
3603
+ +
3604
+ [example]
3605
+ `http://example.com/namespace`
3606
+
3607
+ `prefix`::: The prefix of the namespace provided in the `namespace` option.
3608
+ +
3609
+ [example]
3610
+ `example-prefix`
3611
+
3612
+ `location`::: The URL or path of the directory containing all the files of the
3613
+ schema. For more information, refer to the
3614
+ link:https://www.w3.org/TR/xmlschema-1/#include[XML Schema specification].
3615
+ +
3616
+ [example]
3617
+ `"http://example.com/example.xsd"`
3618
+ +
3619
+ [example]
3620
+ `"path/to/schema/directory"`
3621
+
3622
+
3623
+ The generated LutaML models consists of two different kind of Ruby classes
3624
+ depending on the XSD schema:
3625
+
3626
+ XSD "SimpleTypes":: converted into classes that inherit from
3627
+ `Lutaml::Model::Type::Value`, which define the data types with restrictions and
3628
+ other validations of these values.
3629
+
3630
+ XSD "ComplexTypes":: converted into classes that inherit from
3631
+ `Lutaml::Model::Serializable` that model according to the defined structure.
3632
+
3633
+ Lutaml::Model uses the https://github.com/lutaml/lutaml-xsd[`lutaml-xsd` gem] to
3634
+ automatically resolve the `include` and `import` elements, enabling
3635
+ *Lutaml-Model* to generate the corresponding model files.
3636
+
3637
+ This auto-resolving feature allows seamless integration of these files into your
3638
+ models without the need for manual resolution of includes and imports.
3639
+
3640
+ [example]
3641
+ .Using `Lutaml::Model::Schema#from_xml` to convert an XML Schema to model files
3642
+ ====
3643
+ [source,ruby]
3644
+ ----
3645
+ xsd_schema = <<~XSD
3646
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3647
+ /* your schema here */
3648
+ </xs:schema>
3649
+ XSD
3650
+ options = {
3651
+ # These are all optional:
3652
+ output_dir: 'path/to/directory',
3653
+ namespace: 'http://example.com/namespace',
3654
+ prefix: "example-prefix",
3655
+ location: "http://example.com/example.xsd"
3656
+ # or
3657
+ # location: "path/to/schema/directory"
3658
+ }
3659
+
3660
+ # generates the files in the output_dir | default_dir
3661
+ Lutaml::Model::Schema.from_xml(xsd_schema, options: options)
3662
+ ----
3663
+ ====
3664
+
3665
+ You could also directly load the generated Ruby files into your application by
3666
+ requiring them.
3667
+
3668
+ [example]
3669
+ .Using the generated Ruby files in your application
3670
+ ====
3671
+ [source,ruby]
3672
+ ----
3673
+ Lutaml::Model::Schema.from_xml(xsd_schema, options: {output_dir: 'path/to/directory'})
3674
+ require_relative 'path/to/directory/*.rb'
3675
+ ----
3676
+ ====
3308
3677
 
3309
3678
 
3310
3679
  == Validation
@@ -3696,6 +4065,15 @@ attribute for every element.
3696
4065
  | Requires manual specification on every XML element that uses it.
3697
4066
  |
3698
4067
 
4068
+
4069
+ | Compiling XML Schema to *Lutaml::Model::Serializable* classes
4070
+ | Yes. Using <<xml-schema-to-model-files, `Lutaml::Model::Schema#from_xml`>>
4071
+
4072
+ 1. *ComplexTypes* are compiled to *Lutaml::Model::Serializable* classes containing the attributes.
4073
+ 2. *SimpleTypes* are compiled to *Lutaml::Model::Type::Value* classes to support XML Schema level validations.
4074
+ | Yes, Provides only an array of the classes and doesn't support `simple types` with restrictions and/or other validations.
4075
+ |
4076
+
3699
4077
  4+h| Attribute features
3700
4078
 
3701
4079
  | Attribute delegation
@@ -0,0 +1,7 @@
1
+ module Lutaml
2
+ module Model
3
+ module Constants
4
+ RAW_MAPPING_KEY = "__raw_mapping".freeze
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Type
6
+ class InvalidValueError < Error
7
+ def initialize(message)
8
+ @message = message
9
+
10
+ super()
11
+ end
12
+
13
+ def to_s
14
+ @message
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -16,3 +16,4 @@ require_relative "error/type_error"
16
16
  require_relative "error/unknown_type_error"
17
17
  require_relative "error/multiple_mappings_error"
18
18
  require_relative "error/collection_true_missing_error"
19
+ require_relative "error/type/invalid_value_error"
@@ -36,6 +36,27 @@ module Lutaml
36
36
 
37
37
  alias map_element map
38
38
 
39
+ def map_all(
40
+ to: nil,
41
+ render_nil: false,
42
+ render_default: false,
43
+ with: {},
44
+ delegate: nil
45
+ )
46
+ @raw_mapping = true
47
+ validate!(Constants::RAW_MAPPING_KEY, to, with)
48
+ @mappings << KeyValueMappingRule.new(
49
+ Constants::RAW_MAPPING_KEY,
50
+ to: to,
51
+ render_nil: render_nil,
52
+ render_default: render_default,
53
+ with: with,
54
+ delegate: delegate,
55
+ )
56
+ end
57
+
58
+ alias map_all_content map_all
59
+
39
60
  def name_for_mapping(root_mappings, name)
40
61
  return "root_mapping" if root_mappings
41
62
 
@@ -43,12 +64,14 @@ module Lutaml
43
64
  end
44
65
 
45
66
  def validate!(key, to, with)
46
- if to.nil? && with.empty?
67
+ validate_mappings!(key)
68
+
69
+ if to.nil? && with.empty? && !@raw_mapping
47
70
  msg = ":to or :with argument is required for mapping '#{key}'"
48
71
  raise IncorrectMappingArgumentsError.new(msg)
49
72
  end
50
73
 
51
- if !with.empty? && (with[:from].nil? || with[:to].nil?)
74
+ if !with.empty? && (with[:from].nil? || with[:to].nil?) && !@raw_mapping
52
75
  msg = ":with argument for mapping '#{key}' requires :to and :from keys"
53
76
  raise IncorrectMappingArgumentsError.new(msg)
54
77
  end
@@ -62,6 +85,12 @@ module Lutaml
62
85
  end
63
86
  end
64
87
 
88
+ def validate_mappings!(_type)
89
+ if (@raw_mapping && Utils.present?(@mappings)) || (!@raw_mapping && @mappings.any?(&:raw_mapping?))
90
+ raise StandardError, "map_all is not allowed with other mappings"
91
+ end
92
+ end
93
+
65
94
  def deep_dup
66
95
  self.class.new.tap do |new_mapping|
67
96
  new_mapping.instance_variable_set(:@mappings, duplicate_mappings)