lutaml-model 0.3.10 → 0.3.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,7 +7,8 @@ module Lutaml
7
7
  :custom_methods,
8
8
  :delegate,
9
9
  :mixed_content,
10
- :child_mappings
10
+ :child_mappings,
11
+ :default_namespace
11
12
 
12
13
  def initialize(
13
14
  name,
@@ -17,6 +18,8 @@ module Lutaml
17
18
  delegate: nil,
18
19
  mixed_content: false,
19
20
  namespace_set: false,
21
+ prefix_set: false,
22
+ default_namespace: nil,
20
23
  child_mappings: nil
21
24
  )
22
25
  @name = name
@@ -26,7 +29,9 @@ module Lutaml
26
29
  @delegate = delegate
27
30
  @mixed_content = mixed_content
28
31
  @namespace_set = namespace_set
32
+ @prefix_set = prefix_set
29
33
  @child_mappings = child_mappings
34
+ @default_namespace = default_namespace
30
35
  end
31
36
 
32
37
  alias from name
@@ -40,25 +45,63 @@ module Lutaml
40
45
  end
41
46
  end
42
47
 
43
- def serialize(model, value)
48
+ def namespaced_name
49
+ if name == "lang"
50
+ "#{prefix}:#{name}"
51
+ elsif namespace || default_namespace
52
+ "#{namespace || default_namespace}:#{name}"
53
+ else
54
+ name
55
+ end
56
+ end
57
+
58
+ def serialize_attribute(model, element, doc)
44
59
  if custom_methods[:to]
45
- model.send(custom_methods[:to], model, value)
60
+ model.send(custom_methods[:to], model, element, doc)
61
+ end
62
+ end
63
+
64
+ def to_value_for(model)
65
+ if delegate
66
+ model.public_send(delegate).public_send(to)
46
67
  else
47
- value
68
+ model.public_send(to)
48
69
  end
49
70
  end
50
71
 
51
- def deserialize(model, doc)
72
+ def serialize(model, parent = nil, doc = nil)
73
+ if custom_methods[:to]
74
+ model.send(custom_methods[:to], model, parent, doc)
75
+ else
76
+ to_value_for(model)
77
+ end
78
+ end
79
+
80
+ def deserialize(model, value, attributes, mapper_class = nil)
52
81
  if custom_methods[:from]
53
- model.send(custom_methods[:from], model, doc)
82
+ mapper_class.new.send(custom_methods[:from], model, value)
83
+ elsif delegate
84
+ if model.public_send(delegate).nil?
85
+ model.public_send(:"#{delegate}=", attributes[delegate].type.new)
86
+ end
87
+
88
+ model.public_send(delegate).public_send(:"#{to}=", value)
54
89
  else
55
- doc[name.to_s]
90
+ model.public_send(:"#{to}=", value)
56
91
  end
57
92
  end
58
93
 
59
94
  def namespace_set?
60
95
  @namespace_set
61
96
  end
97
+
98
+ def prefix_set?
99
+ @prefix_set
100
+ end
101
+
102
+ def content_mapping?
103
+ name.nil?
104
+ end
62
105
  end
63
106
  end
64
107
  end
@@ -11,6 +11,7 @@ require_relative "json_adapter"
11
11
  require_relative "comparable_model"
12
12
  require_relative "schema_location"
13
13
  require_relative "validation"
14
+ require_relative "error"
14
15
 
15
16
  module Lutaml
16
17
  module Model
@@ -39,11 +40,42 @@ module Lutaml
39
40
  def model(klass = nil)
40
41
  if klass
41
42
  @model = klass
43
+ add_order_handling_methods_to_model(klass)
42
44
  else
43
45
  @model
44
46
  end
45
47
  end
46
48
 
49
+ def add_order_handling_methods_to_model(klass)
50
+ Utils.add_method_if_not_defined(klass, :ordered=) do |ordered|
51
+ @ordered = ordered
52
+ end
53
+
54
+ Utils.add_method_if_not_defined(klass, :ordered?) do
55
+ !!@ordered
56
+ end
57
+
58
+ Utils.add_method_if_not_defined(klass, :mixed=) do |mixed|
59
+ @mixed = mixed
60
+ end
61
+
62
+ Utils.add_method_if_not_defined(klass, :mixed?) do
63
+ !!@mixed
64
+ end
65
+
66
+ Utils.add_method_if_not_defined(klass, :element_order=) do |order|
67
+ @element_order = order
68
+ end
69
+
70
+ Utils.add_method_if_not_defined(klass, :element_order) do
71
+ @element_order
72
+ end
73
+ end
74
+
75
+ def cast(value)
76
+ value
77
+ end
78
+
47
79
  # Define an attribute for the model
48
80
  def attribute(name, type, options = {})
49
81
  attr = Attribute.new(name, type, options)
@@ -54,15 +86,14 @@ module Lutaml
54
86
  end
55
87
 
56
88
  define_method(:"#{name}=") do |value|
57
- instance_variable_set(:"@#{name}", value)
58
- # validate!(name)
89
+ instance_variable_set(:"@#{name}", attr.cast_value(value))
59
90
  end
60
91
  end
61
92
 
62
93
  Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
63
94
  define_method(format) do |&block|
64
95
  klass = format == :xml ? XmlMapping : KeyValueMapping
65
- mappings[format] = klass.new
96
+ mappings[format] ||= klass.new
66
97
  mappings[format].instance_eval(&block)
67
98
 
68
99
  if format == :xml && !mappings[format].root_element
@@ -74,17 +105,22 @@ module Lutaml
74
105
  adapter = Lutaml::Model::Config.send(:"#{format}_adapter")
75
106
 
76
107
  doc = adapter.parse(data)
77
- public_send(:"of_#{format}", doc.to_h)
108
+ public_send(:"of_#{format}", doc)
78
109
  end
79
110
 
80
- define_method(:"of_#{format}") do |hash|
81
- if hash.is_a?(Array)
82
- return hash.map do |item|
83
- apply_mappings(item, format)
111
+ define_method(:"of_#{format}") do |doc|
112
+ if doc.is_a?(Array)
113
+ return doc.map do |item|
114
+ send(:"of_#{format}", item)
84
115
  end
85
116
  end
86
117
 
87
- apply_mappings(hash, format)
118
+ if format == :xml
119
+ doc_hash = doc.parse_element(doc.root, self, :xml)
120
+ apply_mappings(doc_hash, format)
121
+ else
122
+ apply_mappings(doc.to_h, format)
123
+ end
88
124
  end
89
125
 
90
126
  define_method(:"to_#{format}") do |instance|
@@ -185,6 +221,7 @@ module Lutaml
185
221
 
186
222
  def default_mappings(format)
187
223
  klass = format == :xml ? XmlMapping : KeyValueMapping
224
+
188
225
  klass.new.tap do |mapping|
189
226
  attributes&.each do |name, attr|
190
227
  mapping.map_element(
@@ -193,6 +230,8 @@ module Lutaml
193
230
  render_nil: attr.render_nil?,
194
231
  )
195
232
  end
233
+
234
+ mapping.root(to_s.split("::").last) if format == :xml
196
235
  end
197
236
  end
198
237
 
@@ -242,20 +281,34 @@ module Lutaml
242
281
  hash
243
282
  end
244
283
 
284
+ def valid_rule?(rule)
285
+ attribute = attribute_for_rule(rule)
286
+
287
+ !!attribute || rule.custom_methods[:from]
288
+ end
289
+
290
+ def attribute_for_rule(rule)
291
+ return attributes[rule.to] unless rule.delegate
292
+
293
+ attributes[rule.delegate].type.attributes[rule.to]
294
+ end
295
+
296
+ def attribute_for_child(child_name, format)
297
+ mapping_rule = mappings_for(format).find_by_name(child_name)
298
+
299
+ attribute_for_rule(mapping_rule) if mapping_rule
300
+ end
301
+
245
302
  def apply_mappings(doc, format, options = {})
246
303
  instance = options[:instance] || model.new
247
- return instance if !doc || doc.empty?
304
+ return instance if Utils.blank?(doc)
248
305
  return apply_xml_mapping(doc, instance, options) if format == :xml
249
306
 
250
307
  mappings = mappings_for(format).mappings
251
308
  mappings.each do |rule|
252
- attr = if rule.delegate
253
- attributes[rule.delegate].type.attributes[rule.to]
254
- else
255
- attributes[rule.to]
256
- end
309
+ raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
257
310
 
258
- raise "Attribute '#{rule.to}' not found in #{self}" unless attr
311
+ attr = attribute_for_rule(rule)
259
312
 
260
313
  value = if doc.key?(rule.name) || doc.key?(rule.name.to_sym)
261
314
  doc[rule.name] || doc[rule.name.to_sym]
@@ -264,26 +317,17 @@ module Lutaml
264
317
  end
265
318
 
266
319
  if rule.custom_methods[:from]
267
- if value && !value.empty?
268
- value = new.send(rule.custom_methods[:from], instance,
269
- value)
320
+ if Utils.present?(value)
321
+ value = new.send(rule.custom_methods[:from], instance, value)
270
322
  end
323
+
271
324
  next
272
325
  end
273
326
 
274
327
  value = apply_child_mappings(value, rule.child_mappings)
275
328
  value = attr.cast(value, format)
276
329
 
277
- if rule.delegate
278
- if instance.public_send(rule.delegate).nil?
279
- instance.public_send(:"#{rule.delegate}=",
280
- attributes[rule.delegate].type.new)
281
- end
282
- instance.public_send(rule.delegate).public_send(:"#{rule.to}=",
283
- value)
284
- else
285
- instance.public_send(:"#{rule.to}=", value)
286
- end
330
+ rule.deserialize(instance, value, attributes, self)
287
331
  end
288
332
 
289
333
  instance
@@ -301,7 +345,8 @@ module Lutaml
301
345
 
302
346
  if instance.respond_to?(:ordered=) && doc.is_a?(Lutaml::Model::MappingHash)
303
347
  instance.element_order = doc.item_order
304
- instance.ordered = mappings_for(:xml).mixed_content? || options[:mixed_content]
348
+ instance.ordered = mappings_for(:xml).ordered? || options[:ordered]
349
+ instance.mixed = mappings_for(:xml).mixed_content? || options[:mixed_content]
305
350
  end
306
351
 
307
352
  if doc["__schema_location"]
@@ -313,44 +358,55 @@ module Lutaml
313
358
  end
314
359
 
315
360
  mappings.each do |rule|
316
- attr = attributes[rule.to]
317
- raise "Attribute '#{rule.to}' not found in #{self}" unless attr
318
-
319
- is_content_mapping = rule.name.nil?
361
+ raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
320
362
 
321
- value = if is_content_mapping
363
+ value = if rule.content_mapping?
322
364
  doc["text"]
365
+ elsif doc.key?(rule.namespaced_name.to_s) || doc.key?(rule.namespaced_name.to_sym)
366
+ doc[rule.namespaced_name.to_s] || doc[rule.namespaced_name.to_sym]
323
367
  else
324
- doc[rule.name.to_s] || doc[rule.name.to_sym]
368
+ rule.to_value_for(instance)
325
369
  end
326
370
 
327
- value = [value].compact if attr.collection? && !value.is_a?(Array)
371
+ value = normalize_xml_value(value, rule)
372
+ rule.deserialize(instance, value, attributes, self)
373
+ end
328
374
 
329
- if value.is_a?(Array)
330
- value = value.map do |v|
331
- v.is_a?(Hash) && !(attr.type <= Serialize) ? v["text"] : v
332
- end
333
- elsif !(attr.type <= Serialize) && value.is_a?(Hash) && attr.type != Lutaml::Model::Type::Hash
334
- value = value["text"]
335
- end
375
+ instance
376
+ end
336
377
 
337
- unless is_content_mapping
338
- value = attr.cast(
339
- value,
340
- :xml,
341
- caller_class: self,
342
- mixed_content: rule.mixed_content,
343
- )
344
- end
378
+ def normalize_xml_value(value, rule)
379
+ attr = attribute_for_rule(rule)
345
380
 
346
- if rule.custom_methods[:from]
347
- new.send(rule.custom_methods[:from], instance, value)
348
- else
349
- instance.public_send(:"#{rule.to}=", value)
350
- end
381
+ value = [value].compact if attr&.collection? && !value.is_a?(Array)
382
+
383
+ value = if value.is_a?(Array)
384
+ value.map do |v|
385
+ text_hash?(attr, v) ? v["text"] : v
386
+ end
387
+ elsif text_hash?(attr, value)
388
+ value["text"]
389
+ else
390
+ value
391
+ end
392
+
393
+ if attr && !rule.content_mapping?
394
+ value = attr.cast(
395
+ value,
396
+ :xml,
397
+ caller_class: self,
398
+ mixed_content: rule.mixed_content,
399
+ )
351
400
  end
352
401
 
353
- instance
402
+ value
403
+ end
404
+
405
+ def text_hash?(attr, value)
406
+ return false unless value.is_a?(Hash)
407
+ return value.keys == ["text"] unless attr
408
+
409
+ !(attr.type <= Serialize) && attr.type != Lutaml::Model::Type::Hash
354
410
  end
355
411
 
356
412
  def ensure_utf8(value)
@@ -373,6 +429,7 @@ module Lutaml
373
429
  end
374
430
 
375
431
  attr_accessor :element_order, :schema_location
432
+ attr_writer :ordered, :mixed
376
433
 
377
434
  def initialize(attrs = {})
378
435
  @validate_on_set = attrs.delete(:validate_on_set) || false
@@ -422,11 +479,11 @@ module Lutaml
422
479
  end
423
480
 
424
481
  def ordered?
425
- @ordered
482
+ !!@ordered
426
483
  end
427
484
 
428
- def ordered=(ordered)
429
- @ordered = ordered
485
+ def mixed?
486
+ !!@mixed
430
487
  end
431
488
 
432
489
  def key_exist?(hash, key)
@@ -439,7 +496,6 @@ module Lutaml
439
496
 
440
497
  Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
441
498
  define_method(:"to_#{format}") do |options = {}|
442
- validate!
443
499
  adapter = Lutaml::Model::Config.public_send(:"#{format}_adapter")
444
500
  representation = if format == :xml
445
501
  self
@@ -62,6 +62,10 @@ module Lutaml
62
62
  when "Boolean"
63
63
  to_boolean(value)
64
64
  when "Decimal"
65
+ unless defined?(BigDecimal)
66
+ raise Lutaml::Model::TypeNotEnabledError.new("Decimal", value)
67
+ end
68
+
65
69
  BigDecimal(value.to_s)
66
70
  when "Hash"
67
71
  normalize_hash(Hash(value))
@@ -85,6 +89,10 @@ module Lutaml
85
89
  when "Boolean"
86
90
  to_boolean(value)
87
91
  when "Decimal"
92
+ unless defined?(BigDecimal)
93
+ raise Lutaml::Model::TypeNotEnabledError.new("Decimal", value)
94
+ end
95
+
88
96
  value.to_s("F")
89
97
  when "Hash"
90
98
  Hash(value)
@@ -108,6 +116,8 @@ module Lutaml
108
116
  def self.normalize_hash(hash)
109
117
  return hash["text"] if hash.keys == ["text"]
110
118
 
119
+ hash = hash.to_h if hash.is_a?(Lutaml::Model::MappingHash)
120
+
111
121
  hash.filter_map do |key, value|
112
122
  next if key == "text"
113
123
 
@@ -34,6 +34,22 @@ module Lutaml
34
34
  .downcase
35
35
  end
36
36
 
37
+ def present?(value)
38
+ !blank?(value)
39
+ end
40
+
41
+ def blank?(value)
42
+ value.respond_to?(:empty?) ? value.empty? : !value
43
+ end
44
+
45
+ def add_method_if_not_defined(klass, method_name, &block)
46
+ unless klass.method_defined?(method_name)
47
+ klass.class_eval do
48
+ define_method(method_name, &block)
49
+ end
50
+ end
51
+ end
52
+
37
53
  private
38
54
 
39
55
  def camelize_part(part)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.3.10"
5
+ VERSION = "0.3.12"
6
6
  end
7
7
  end
@@ -27,11 +27,21 @@ module Lutaml
27
27
  element.add_child(child)
28
28
  end
29
29
 
30
- def create_and_add_element(element_name, prefix: nil, attributes: {})
31
- add_namespace_prefix(prefix) if prefix
30
+ def add_attribute(element, name, value)
31
+ element[name] = value
32
+ end
33
+
34
+ def create_and_add_element(
35
+ element_name,
36
+ prefix: (prefix_unset = true
37
+ nil),
38
+ attributes: {}
39
+ )
40
+ add_namespace_prefix(prefix)
32
41
 
33
42
  if block_given?
34
43
  public_send(element_name, attributes) do
44
+ xml.parent.namespace = nil if prefix.nil? && !prefix_unset
35
45
  yield(self)
36
46
  end
37
47
  else
@@ -53,11 +63,9 @@ module Lutaml
53
63
  self
54
64
  end
55
65
 
56
- def method_missing(method_name, *args)
66
+ def method_missing(method_name, *args, &block)
57
67
  if block_given?
58
- xml.public_send(method_name, *args) do
59
- yield(xml)
60
- end
68
+ xml.public_send(method_name, *args, &block)
61
69
  else
62
70
  xml.public_send(method_name, *args)
63
71
  end
@@ -38,6 +38,10 @@ module Lutaml
38
38
  element << child
39
39
  end
40
40
 
41
+ def add_attribute(element, name, value)
42
+ element[name] = value
43
+ end
44
+
41
45
  def create_and_add_element(element_name, prefix: nil, attributes: {})
42
46
  prefixed_name = if prefix
43
47
  "#{prefix}:#{element_name}"
@@ -55,6 +55,7 @@ module Lutaml
55
55
  end
56
56
 
57
57
  index_hash = {}
58
+ content = []
58
59
 
59
60
  element.element_order.each do |name|
60
61
  index_hash[name] ||= -1
@@ -68,15 +69,20 @@ module Lutaml
68
69
  value = attribute_value_for(element, element_rule)
69
70
 
70
71
  if element_rule == xml_mapping.content_mapping
71
- text = element.send(xml_mapping.content_mapping.to)
72
+ text = xml_mapping.content_mapping.serialize(element)
72
73
  text = text[curr_index] if text.is_a?(Array)
73
74
 
74
- prefixed_xml.text text
75
+ if element.mixed?
76
+ prefixed_xml.text text
77
+ else
78
+ content << text
79
+ end
75
80
  elsif !value.nil? || element_rule.render_nil?
76
81
  value = value[curr_index] if attribute_def.collection?
77
82
 
78
83
  add_to_xml(
79
84
  xml,
85
+ element,
80
86
  element_rule.prefix,
81
87
  value,
82
88
  options.merge(
@@ -86,12 +92,14 @@ module Lutaml
86
92
  )
87
93
  end
88
94
  end
95
+
96
+ prefixed_xml.text content.join
89
97
  end
90
98
  end
91
99
  end
92
100
 
93
101
  class NokogiriElement < XmlElement
94
- def initialize(node, root_node: nil)
102
+ def initialize(node, root_node: nil, default_namespace: nil)
95
103
  if root_node
96
104
  node.namespaces.each do |prefix, name|
97
105
  namespace = XmlNamespace.new(name, prefix)
@@ -115,14 +123,15 @@ module Lutaml
115
123
  namespace_prefix: attr.namespace&.prefix,
116
124
  )
117
125
  end
118
-
126
+ default_namespace = node.namespace&.href if root_node.nil?
119
127
  super(
120
128
  node.name,
121
129
  attributes,
122
- parse_all_children(node, root_node: root_node || self),
130
+ parse_all_children(node, root_node: root_node || self, default_namespace: default_namespace),
123
131
  node.text,
124
132
  parent_document: root_node,
125
133
  namespace_prefix: node.namespace&.prefix,
134
+ default_namespace: default_namespace
126
135
  )
127
136
  end
128
137
 
@@ -137,8 +146,10 @@ module Lutaml
137
146
  if name == "text"
138
147
  builder.text(text)
139
148
  else
140
- builder.send(name, build_attributes(self)) do |xml|
141
- children.each { |child| child.to_xml(xml) }
149
+ builder.public_send(name, build_attributes(self)) do |xml|
150
+ children.each do |child|
151
+ child.to_xml(xml)
152
+ end
142
153
  end
143
154
  end
144
155
 
@@ -153,9 +164,9 @@ module Lutaml
153
164
  end
154
165
  end
155
166
 
156
- def parse_all_children(node, root_node: nil)
167
+ def parse_all_children(node, root_node: nil, default_namespace: nil)
157
168
  node.children.map do |child|
158
- NokogiriElement.new(child, root_node: root_node)
169
+ NokogiriElement.new(child, root_node: root_node, default_namespace: default_namespace)
159
170
  end
160
171
  end
161
172
 
@@ -42,6 +42,7 @@ module Lutaml
42
42
  builder.create_and_add_element(tag_name,
43
43
  attributes: attributes) do |el|
44
44
  index_hash = {}
45
+ content = []
45
46
 
46
47
  element.element_order.each do |name|
47
48
  index_hash[name] ||= -1
@@ -58,12 +59,17 @@ module Lutaml
58
59
  text = element.send(xml_mapping.content_mapping.to)
59
60
  text = text[curr_index] if text.is_a?(Array)
60
61
 
61
- el.add_text(el, text)
62
+ if element.mixed?
63
+ el.add_text(el, text)
64
+ else
65
+ content << text
66
+ end
62
67
  elsif !value.nil? || element_rule.render_nil?
63
68
  value = value[curr_index] if attribute_def.collection?
64
69
 
65
70
  add_to_xml(
66
71
  el,
72
+ element,
67
73
  nil,
68
74
  value,
69
75
  options.merge(
@@ -73,6 +79,8 @@ module Lutaml
73
79
  )
74
80
  end
75
81
  end
82
+
83
+ el.add_text(el, content.join)
76
84
  end
77
85
  end
78
86
  end