lutaml-model 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/rake.yml +1 -1
- data/.github/workflows/release.yml +2 -2
- data/.gitignore +2 -0
- data/.rubocop.yml +8 -1
- data/.rubocop_todo.yml +207 -0
- data/Gemfile +24 -0
- data/README.adoc +74 -3
- data/lib/lutaml/model/attribute.rb +7 -1
- data/lib/lutaml/model/key_value_mapping.rb +7 -1
- data/lib/lutaml/model/mapping_hash.rb +42 -0
- data/lib/lutaml/model/mapping_rule.rb +30 -2
- data/lib/lutaml/model/schema/json_schema.rb +10 -20
- data/lib/lutaml/model/schema/relaxng_schema.rb +9 -19
- data/lib/lutaml/model/schema/xsd_schema.rb +11 -24
- data/lib/lutaml/model/schema/yaml_schema.rb +11 -21
- data/lib/lutaml/model/serialize.rb +204 -37
- data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +3 -2
- data/lib/lutaml/model/type.rb +144 -39
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +181 -56
- data/lib/lutaml/model/xml_adapter/oga_adapter.rb +5 -8
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +176 -32
- data/lib/lutaml/model/xml_adapter.rb +221 -8
- data/lib/lutaml/model/xml_mapping.rb +86 -8
- data/lib/lutaml/model/xml_mapping_rule.rb +27 -3
- data/lib/lutaml/model/xml_namespace.rb +47 -0
- data/lib/lutaml/model/yaml_adapter.rb +3 -1
- data/lutaml-model.gemspec +2 -15
- metadata +11 -149
- data/.github/workflows/main.yml +0 -27
@@ -5,14 +5,12 @@ module Lutaml
|
|
5
5
|
module Model
|
6
6
|
module Schema
|
7
7
|
class XsdSchema
|
8
|
-
def self.generate(klass,
|
8
|
+
def self.generate(klass, _options = {})
|
9
9
|
schema = Nokogiri::XML::Builder.new do |xml|
|
10
10
|
xml.schema(xmlns: "http://www.w3.org/2001/XMLSchema") do
|
11
11
|
xml.element(name: klass.name) do
|
12
12
|
xml.complexType do
|
13
|
-
xml.sequence
|
14
|
-
generate_elements(klass, xml)
|
15
|
-
end
|
13
|
+
xml.sequence { generate_elements(klass, xml) }
|
16
14
|
end
|
17
15
|
end
|
18
16
|
end
|
@@ -20,8 +18,6 @@ module Lutaml
|
|
20
18
|
schema.to_xml
|
21
19
|
end
|
22
20
|
|
23
|
-
private
|
24
|
-
|
25
21
|
def self.generate_elements(klass, xml)
|
26
22
|
klass.attributes.each do |name, attr|
|
27
23
|
xml.element(name: name, type: get_xsd_type(attr.type))
|
@@ -29,24 +25,15 @@ module Lutaml
|
|
29
25
|
end
|
30
26
|
|
31
27
|
def self.get_xsd_type(type)
|
32
|
-
|
33
|
-
|
34
|
-
"xs:
|
35
|
-
|
36
|
-
"xs:
|
37
|
-
|
38
|
-
"xs:
|
39
|
-
|
40
|
-
|
41
|
-
when Lutaml::Model::Type::Decimal
|
42
|
-
"xs:decimal"
|
43
|
-
when Lutaml::Model::Type::Array
|
44
|
-
"xs:array"
|
45
|
-
when Lutaml::Model::Type::Hash
|
46
|
-
"xs:object"
|
47
|
-
else
|
48
|
-
"xs:string" # Default to string for unknown types
|
49
|
-
end
|
28
|
+
{
|
29
|
+
Lutaml::Model::Type::String => "xs:string",
|
30
|
+
Lutaml::Model::Type::Integer => "xs:integer",
|
31
|
+
Lutaml::Model::Type::Boolean => "xs:boolean",
|
32
|
+
Lutaml::Model::Type::Float => "xs:float",
|
33
|
+
Lutaml::Model::Type::Decimal => "xs:decimal",
|
34
|
+
Lutaml::Model::Type::Array => "xs:array",
|
35
|
+
Lutaml::Model::Type::Hash => "xs:object",
|
36
|
+
}[type] || "xs:string" # Default to string for unknown types
|
50
37
|
end
|
51
38
|
end
|
52
39
|
end
|
@@ -5,7 +5,7 @@ module Lutaml
|
|
5
5
|
module Model
|
6
6
|
module Schema
|
7
7
|
class YamlSchema
|
8
|
-
def self.generate(klass,
|
8
|
+
def self.generate(klass, _options = {})
|
9
9
|
schema = {
|
10
10
|
"type" => "map",
|
11
11
|
"mapping" => generate_mapping(klass),
|
@@ -13,8 +13,6 @@ module Lutaml
|
|
13
13
|
YAML.dump(schema)
|
14
14
|
end
|
15
15
|
|
16
|
-
private
|
17
|
-
|
18
16
|
def self.generate_mapping(klass)
|
19
17
|
klass.attributes.each_with_object({}) do |(name, attr), mapping|
|
20
18
|
mapping[name.to_s] = { "type" => get_yaml_type(attr.type) }
|
@@ -22,24 +20,16 @@ module Lutaml
|
|
22
20
|
end
|
23
21
|
|
24
22
|
def self.get_yaml_type(type)
|
25
|
-
|
26
|
-
|
27
|
-
"
|
28
|
-
|
29
|
-
"
|
30
|
-
|
31
|
-
"
|
32
|
-
|
33
|
-
"
|
34
|
-
|
35
|
-
"float" # YAML does not have a separate decimal type, so we use float
|
36
|
-
when Lutaml::Model::Type::Array
|
37
|
-
"seq"
|
38
|
-
when Lutaml::Model::Type::Hash
|
39
|
-
"map"
|
40
|
-
else
|
41
|
-
"str" # Default to string for unknown types
|
42
|
-
end
|
23
|
+
{
|
24
|
+
Lutaml::Model::Type::String => "str",
|
25
|
+
Lutaml::Model::Type::Integer => "int",
|
26
|
+
Lutaml::Model::Type::Boolean => "bool",
|
27
|
+
Lutaml::Model::Type::Float => "float",
|
28
|
+
# YAML does not have a separate decimal type, so we use float
|
29
|
+
Lutaml::Model::Type::Decimal => "float",
|
30
|
+
Lutaml::Model::Type::Array => "seq",
|
31
|
+
Lutaml::Model::Type::Hash => "map",
|
32
|
+
}[type] || "str" # Default to string for unknown types
|
43
33
|
end
|
44
34
|
end
|
45
35
|
end
|
@@ -1,14 +1,11 @@
|
|
1
1
|
# lib/lutaml/model/serialize.rb
|
2
|
-
require_relative "json_adapter/standard"
|
3
|
-
require_relative "json_adapter/multi_json"
|
4
2
|
require_relative "yaml_adapter"
|
5
3
|
require_relative "xml_adapter"
|
6
|
-
require_relative "toml_adapter/toml_rb_adapter"
|
7
|
-
require_relative "toml_adapter/tomlib_adapter"
|
8
4
|
require_relative "config"
|
9
5
|
require_relative "type"
|
10
6
|
require_relative "attribute"
|
11
7
|
require_relative "mapping_rule"
|
8
|
+
require_relative "mapping_hash"
|
12
9
|
require_relative "xml_mapping"
|
13
10
|
require_relative "key_value_mapping"
|
14
11
|
require_relative "json_adapter"
|
@@ -25,17 +22,23 @@ module Lutaml
|
|
25
22
|
module ClassMethods
|
26
23
|
attr_accessor :attributes, :mappings
|
27
24
|
|
25
|
+
def inherited(subclass)
|
26
|
+
super
|
27
|
+
|
28
|
+
subclass.instance_variable_set(:@attributes, @attributes.dup)
|
29
|
+
end
|
30
|
+
|
28
31
|
def attribute(name, type, options = {})
|
29
32
|
self.attributes ||= {}
|
30
33
|
attr = Attribute.new(name, type, options)
|
31
34
|
attributes[name] = attr
|
32
35
|
|
33
36
|
define_method(name) do
|
34
|
-
instance_variable_get("@#{name}")
|
37
|
+
instance_variable_get(:"@#{name}")
|
35
38
|
end
|
36
39
|
|
37
|
-
define_method("#{name}=") do |value|
|
38
|
-
instance_variable_set("@#{name}", value)
|
40
|
+
define_method(:"#{name}=") do |value|
|
41
|
+
instance_variable_set(:"@#{name}", value)
|
39
42
|
end
|
40
43
|
end
|
41
44
|
|
@@ -45,13 +48,17 @@ module Lutaml
|
|
45
48
|
klass = format == :xml ? XmlMapping : KeyValueMapping
|
46
49
|
self.mappings[format] = klass.new
|
47
50
|
self.mappings[format].instance_eval(&block)
|
51
|
+
|
52
|
+
if format == :xml && !self.mappings[format].root_element
|
53
|
+
self.mappings[format].root(to_s)
|
54
|
+
end
|
48
55
|
end
|
49
56
|
|
50
|
-
define_method("from_#{format}") do |data|
|
51
|
-
adapter = Lutaml::Model::Config.send("#{format}_adapter")
|
57
|
+
define_method(:"from_#{format}") do |data|
|
58
|
+
adapter = Lutaml::Model::Config.send(:"#{format}_adapter")
|
52
59
|
doc = adapter.parse(data)
|
53
60
|
mapped_attrs = apply_mappings(doc.to_h, format)
|
54
|
-
apply_content_mapping(doc, mapped_attrs) if format == :xml
|
61
|
+
# apply_content_mapping(doc, mapped_attrs) if format == :xml
|
55
62
|
new(mapped_attrs)
|
56
63
|
end
|
57
64
|
end
|
@@ -64,55 +71,170 @@ module Lutaml
|
|
64
71
|
klass = format == :xml ? XmlMapping : KeyValueMapping
|
65
72
|
klass.new.tap do |mapping|
|
66
73
|
attributes&.each do |name, attr|
|
67
|
-
mapping.map_element(name.to_s, to: name,
|
74
|
+
mapping.map_element(name.to_s, to: name,
|
75
|
+
render_nil: attr.render_nil?)
|
68
76
|
end
|
69
77
|
end
|
70
78
|
end
|
71
79
|
|
72
80
|
def apply_mappings(doc, format)
|
81
|
+
return apply_xml_mapping(doc) if format == :xml
|
82
|
+
|
73
83
|
mappings = mappings_for(format).mappings
|
74
|
-
mappings.each_with_object(
|
75
|
-
attr =
|
84
|
+
mappings.each_with_object(Lutaml::Model::MappingHash.new) do |rule, hash|
|
85
|
+
attr = if rule.delegate
|
86
|
+
attributes[rule.delegate].type.attributes[rule.to]
|
87
|
+
else
|
88
|
+
attributes[rule.to]
|
89
|
+
end
|
90
|
+
|
76
91
|
raise "Attribute '#{rule.to}' not found in #{self}" unless attr
|
77
92
|
|
78
|
-
value =
|
93
|
+
value = if rule.custom_methods[:from]
|
94
|
+
new.send(rule.custom_methods[:from], hash, doc)
|
95
|
+
elsif doc.key?(rule.name) || doc.key?(rule.name.to_sym)
|
96
|
+
doc[rule.name] || doc[rule.name.to_sym]
|
97
|
+
else
|
98
|
+
attr.default
|
99
|
+
end
|
100
|
+
|
79
101
|
if attr.collection?
|
80
|
-
value = (value || []).map
|
81
|
-
|
82
|
-
|
102
|
+
value = (value || []).map do |v|
|
103
|
+
attr.type <= Serialize ? attr.type.apply_mappings(v, format) : v
|
104
|
+
end
|
105
|
+
elsif value.is_a?(Hash) && attr.type != Lutaml::Model::Type::Hash
|
106
|
+
value = attr.type.apply_mappings(value, format)
|
107
|
+
end
|
108
|
+
|
109
|
+
if rule.delegate
|
110
|
+
hash[rule.delegate] ||= {}
|
111
|
+
hash[rule.delegate][rule.to] = value
|
112
|
+
else
|
113
|
+
hash[rule.to] = value
|
83
114
|
end
|
84
|
-
hash[rule.to] = value
|
85
115
|
end
|
86
116
|
end
|
87
117
|
|
88
|
-
def
|
89
|
-
|
90
|
-
|
118
|
+
def apply_xml_mapping(doc, caller_class: nil, mixed_content: false)
|
119
|
+
return unless doc
|
120
|
+
|
121
|
+
mappings = mappings_for(:xml).mappings
|
122
|
+
|
123
|
+
if doc.is_a?(Array)
|
124
|
+
raise "May be `collection: true` is" \
|
125
|
+
"missing for #{self} in #{caller_class}"
|
126
|
+
end
|
127
|
+
|
128
|
+
mapping_hash = Lutaml::Model::MappingHash.new
|
129
|
+
mapping_hash.item_order = doc.item_order
|
130
|
+
mapping_hash.ordered = mappings_for(:xml).mixed_content? || mixed_content
|
131
|
+
|
132
|
+
mappings.each_with_object(mapping_hash) do |rule, hash|
|
133
|
+
attr = attributes[rule.to]
|
134
|
+
raise "Attribute '#{rule.to}' not found in #{self}" unless attr
|
135
|
+
|
136
|
+
is_content_mapping = rule.name.nil?
|
137
|
+
value = if is_content_mapping
|
138
|
+
doc["text"]
|
139
|
+
else
|
140
|
+
doc[rule.name.to_s] || doc[rule.name.to_sym]
|
141
|
+
end
|
142
|
+
|
143
|
+
if attr.collection?
|
144
|
+
if value && !value.is_a?(Array)
|
145
|
+
value = [value]
|
146
|
+
end
|
147
|
+
|
148
|
+
value = (value || []).map do |v|
|
149
|
+
if attr.type <= Serialize
|
150
|
+
attr.type.apply_xml_mapping(v, caller_class: self, mixed_content: rule.mixed_content)
|
151
|
+
elsif v.is_a?(Hash)
|
152
|
+
v["text"]
|
153
|
+
else
|
154
|
+
v
|
155
|
+
end
|
156
|
+
end
|
157
|
+
elsif attr.type <= Serialize
|
158
|
+
value = attr.type.apply_xml_mapping(value, caller_class: self, mixed_content: rule.mixed_content)
|
159
|
+
else
|
160
|
+
if value.is_a?(Hash) && attr.type != Lutaml::Model::Type::Hash
|
161
|
+
value = value["text"]
|
162
|
+
end
|
163
|
+
|
164
|
+
value = attr.type.cast(value) unless is_content_mapping
|
165
|
+
end
|
91
166
|
|
92
|
-
|
93
|
-
|
167
|
+
hash[rule.to] = value
|
168
|
+
end
|
94
169
|
end
|
95
170
|
end
|
96
171
|
|
172
|
+
attr_reader :element_order
|
173
|
+
|
97
174
|
def initialize(attrs = {})
|
98
|
-
return
|
175
|
+
return unless self.class.attributes
|
176
|
+
|
177
|
+
if attrs.is_a?(Lutaml::Model::MappingHash)
|
178
|
+
@ordered = attrs.ordered?
|
179
|
+
@element_order = attrs.item_order
|
180
|
+
end
|
99
181
|
|
100
182
|
self.class.attributes.each do |name, attr|
|
101
|
-
value = attrs
|
102
|
-
|
103
|
-
|
183
|
+
value = attr_value(attrs, name, attr)
|
184
|
+
|
185
|
+
send(:"#{name}=", ensure_utf8(value))
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def attr_value(attrs, name, attr_rule)
|
190
|
+
value = if attrs.key?(name)
|
191
|
+
attrs[name]
|
192
|
+
elsif attrs.key?(name.to_sym)
|
193
|
+
attrs[name.to_sym]
|
194
|
+
elsif attrs.key?(name.to_s)
|
195
|
+
attrs[name.to_s]
|
196
|
+
else
|
197
|
+
attr_rule.default
|
198
|
+
end
|
199
|
+
|
200
|
+
if attr_rule.collection? || value.is_a?(Array)
|
201
|
+
(value || []).map do |v|
|
202
|
+
if v.is_a?(Hash)
|
203
|
+
attr_rule.type.new(v)
|
104
204
|
else
|
105
|
-
Lutaml::Model::Type.cast(
|
205
|
+
Lutaml::Model::Type.cast(
|
206
|
+
v, attr_rule.type
|
207
|
+
)
|
106
208
|
end
|
107
|
-
|
209
|
+
end
|
210
|
+
elsif value.is_a?(Hash) && attr_rule.type != Lutaml::Model::Type::Hash
|
211
|
+
attr_rule.type.new(value)
|
212
|
+
else
|
213
|
+
Lutaml::Model::Type.cast(value, attr_rule.type)
|
108
214
|
end
|
109
215
|
end
|
110
216
|
|
217
|
+
def ordered?
|
218
|
+
@ordered
|
219
|
+
end
|
220
|
+
|
221
|
+
def key_exist?(hash, key)
|
222
|
+
hash.key?(key) || hash.key?(key.to_sym) || hash.key?(key.to_s)
|
223
|
+
end
|
224
|
+
|
225
|
+
def key_value(hash, key)
|
226
|
+
hash[key] || hash[key.to_sym] || hash[key.to_s]
|
227
|
+
end
|
228
|
+
|
111
229
|
# TODO: Make this work
|
112
230
|
# FORMATS.each do |format|
|
113
231
|
# define_method("to_#{format}") do |options = {}|
|
114
232
|
# adapter = Lutaml::Model::Config.send("#{format}_adapter")
|
115
|
-
# representation = format == :yaml
|
233
|
+
# representation = if format == :yaml
|
234
|
+
# self
|
235
|
+
# else
|
236
|
+
# hash_representation(format, options)
|
237
|
+
# end
|
116
238
|
# adapter.new(representation).send("to_#{format}", options)
|
117
239
|
# end
|
118
240
|
# end
|
@@ -148,20 +270,63 @@ module Lutaml
|
|
148
270
|
name = rule.to
|
149
271
|
next if except&.include?(name) || (only && !only.include?(name))
|
150
272
|
|
151
|
-
|
273
|
+
next handle_delegate(self, rule, hash) if rule.delegate
|
274
|
+
|
275
|
+
value = if rule.custom_methods[:to]
|
276
|
+
send(rule.custom_methods[:to], self, send(name))
|
277
|
+
else
|
278
|
+
send(name)
|
279
|
+
end
|
280
|
+
|
152
281
|
next if value.nil? && !rule.render_nil
|
153
282
|
|
283
|
+
attribute = self.class.attributes[name]
|
284
|
+
|
154
285
|
hash[rule.from] = case value
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
286
|
+
when Array
|
287
|
+
value.map do |v|
|
288
|
+
if v.is_a?(Serialize)
|
289
|
+
v.hash_representation(format, options)
|
290
|
+
else
|
291
|
+
attribute.type.serialize(v)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
else
|
295
|
+
if value.is_a?(Serialize)
|
296
|
+
value.hash_representation(format, options)
|
297
|
+
else
|
298
|
+
attribute.type.serialize(value)
|
299
|
+
end
|
300
|
+
end
|
160
301
|
end
|
161
302
|
end
|
162
303
|
|
163
304
|
private
|
164
305
|
|
306
|
+
def handle_delegate(_obj, rule, hash)
|
307
|
+
name = rule.to
|
308
|
+
value = send(rule.delegate).send(name)
|
309
|
+
return if value.nil? && !rule.render_nil
|
310
|
+
|
311
|
+
attribute = send(rule.delegate).class.attributes[name]
|
312
|
+
hash[rule.from] = case value
|
313
|
+
when Array
|
314
|
+
value.map do |v|
|
315
|
+
if v.is_a?(Serialize)
|
316
|
+
v.hash_representation(format, options)
|
317
|
+
else
|
318
|
+
attribute.type.serialize(v)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
else
|
322
|
+
if value.is_a?(Serialize)
|
323
|
+
value.hash_representation(format, options)
|
324
|
+
else
|
325
|
+
attribute.type.serialize(value)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
165
330
|
def ensure_utf8(value)
|
166
331
|
case value
|
167
332
|
when String
|
@@ -169,7 +334,9 @@ module Lutaml
|
|
169
334
|
when Array
|
170
335
|
value.map { |v| ensure_utf8(v) }
|
171
336
|
when Hash
|
172
|
-
value.transform_keys
|
337
|
+
value.transform_keys do |k|
|
338
|
+
ensure_utf8(k)
|
339
|
+
end.transform_values { |v| ensure_utf8(v) }
|
173
340
|
else
|
174
341
|
value
|
175
342
|
end
|
@@ -7,12 +7,13 @@ module Lutaml
|
|
7
7
|
module TomlAdapter
|
8
8
|
class TomlibDocument < Document
|
9
9
|
def self.parse(toml)
|
10
|
-
data = Tomlib
|
10
|
+
data = Tomlib.load(toml)
|
11
11
|
new(data)
|
12
12
|
end
|
13
13
|
|
14
14
|
def to_toml(*args)
|
15
|
-
Tomlib
|
15
|
+
Tomlib.dump(to_h, *args)
|
16
|
+
# Tomlib::Generator.new(to_h).toml_str
|
16
17
|
end
|
17
18
|
end
|
18
19
|
end
|