lutaml-model 0.2.1 → 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.
@@ -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"
@@ -22,14 +19,15 @@ module Lutaml
22
19
  base.extend(ClassMethods)
23
20
  end
24
21
 
25
- # rubocop:disable Metrics/MethodLength
26
- # rubocop:disable Metrics/BlockLength
27
- # rubocop:disable Metrics/AbcSize
28
- # rubocop:disable Metrics/CyclomaticComplexity
29
- # rubocop:disable Metrics/PerceivedComplexity
30
22
  module ClassMethods
31
23
  attr_accessor :attributes, :mappings
32
24
 
25
+ def inherited(subclass)
26
+ super
27
+
28
+ subclass.instance_variable_set(:@attributes, @attributes.dup)
29
+ end
30
+
33
31
  def attribute(name, type, options = {})
34
32
  self.attributes ||= {}
35
33
  attr = Attribute.new(name, type, options)
@@ -50,13 +48,17 @@ module Lutaml
50
48
  klass = format == :xml ? XmlMapping : KeyValueMapping
51
49
  self.mappings[format] = klass.new
52
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
53
55
  end
54
56
 
55
57
  define_method(:"from_#{format}") do |data|
56
58
  adapter = Lutaml::Model::Config.send(:"#{format}_adapter")
57
59
  doc = adapter.parse(data)
58
60
  mapped_attrs = apply_mappings(doc.to_h, format)
59
- apply_content_mapping(doc, mapped_attrs) if format == :xml
61
+ # apply_content_mapping(doc, mapped_attrs) if format == :xml
60
62
  new(mapped_attrs)
61
63
  end
62
64
  end
@@ -79,7 +81,7 @@ module Lutaml
79
81
  return apply_xml_mapping(doc) if format == :xml
80
82
 
81
83
  mappings = mappings_for(format).mappings
82
- mappings.each_with_object({}) do |rule, hash|
84
+ mappings.each_with_object(Lutaml::Model::MappingHash.new) do |rule, hash|
83
85
  attr = if rule.delegate
84
86
  attributes[rule.delegate].type.attributes[rule.to]
85
87
  else
@@ -95,15 +97,6 @@ module Lutaml
95
97
  else
96
98
  attr.default
97
99
  end
98
- # if attr.collection?
99
- # value = (value || []).map do |v|
100
- # attr.type <= Serialize ? attr.type.new(v) : v
101
- # end
102
- # elsif value.is_a?(Hash) && attr.type <= Serialize
103
- # value = attr.type.new(value)
104
- # else
105
- # value = attr.type.cast(value)
106
- # end
107
100
 
108
101
  if attr.collection?
109
102
  value = (value || []).map do |v|
@@ -122,93 +115,116 @@ module Lutaml
122
115
  end
123
116
  end
124
117
 
125
- def apply_xml_mapping(doc)
118
+ def apply_xml_mapping(doc, caller_class: nil, mixed_content: false)
119
+ return unless doc
120
+
126
121
  mappings = mappings_for(:xml).mappings
127
122
 
128
- mappings.each_with_object({}) do |rule, hash|
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|
129
133
  attr = attributes[rule.to]
130
134
  raise "Attribute '#{rule.to}' not found in #{self}" unless attr
131
135
 
132
- value = if rule.name
133
- doc[rule.name.to_s] || doc[rule.name.to_sym]
134
- else
136
+ is_content_mapping = rule.name.nil?
137
+ value = if is_content_mapping
135
138
  doc["text"]
139
+ else
140
+ doc[rule.name.to_s] || doc[rule.name.to_sym]
136
141
  end
137
142
 
138
- # if attr.collection?
139
- # value = (value || []).map do |v|
140
- # attr.type <= Serialize ? attr.type.from_hash(v) : v
141
- # end
142
- # elsif value.is_a?(Hash) && attr.type <= Serialize
143
- # value = attr.type.cast(value)
144
- # elsif value.is_a?(Array)
145
- # value = attr.type.cast(value.first["text"]&.first)
146
- # end
147
-
148
143
  if attr.collection?
144
+ if value && !value.is_a?(Array)
145
+ value = [value]
146
+ end
147
+
149
148
  value = (value || []).map do |v|
150
149
  if attr.type <= Serialize
151
- attr.type.apply_xml_mapping(v)
152
- else
150
+ attr.type.apply_xml_mapping(v, caller_class: self, mixed_content: rule.mixed_content)
151
+ elsif v.is_a?(Hash)
153
152
  v["text"]
153
+ else
154
+ v
154
155
  end
155
156
  end
156
157
  elsif attr.type <= Serialize
157
- value = attr.type.apply_xml_mapping(value) if value
158
+ value = attr.type.apply_xml_mapping(value, caller_class: self, mixed_content: rule.mixed_content)
158
159
  else
159
160
  if value.is_a?(Hash) && attr.type != Lutaml::Model::Type::Hash
160
161
  value = value["text"]
161
162
  end
162
163
 
163
- value = attr.type.cast(value)
164
+ value = attr.type.cast(value) unless is_content_mapping
164
165
  end
166
+
165
167
  hash[rule.to] = value
166
168
  end
167
169
  end
168
-
169
- def apply_content_mapping(doc, mapped_attrs)
170
- content_mapping = mappings_for(:xml).content_mapping
171
- return unless content_mapping
172
-
173
- content = doc.root.children.select(&:text?).map(&:text)
174
- mapped_attrs[content_mapping.to] = content
175
- end
176
170
  end
177
171
 
178
- # rubocop:disable Layout/LineLength
172
+ attr_reader :element_order
173
+
179
174
  def initialize(attrs = {})
180
175
  return unless self.class.attributes
181
176
 
177
+ if attrs.is_a?(Lutaml::Model::MappingHash)
178
+ @ordered = attrs.ordered?
179
+ @element_order = attrs.item_order
180
+ end
181
+
182
182
  self.class.attributes.each do |name, attr|
183
- value = if attrs.key?(name)
184
- attrs[name]
185
- elsif attrs.key?(name.to_sym)
186
- attrs[name.to_sym]
187
- elsif attrs.key?(name.to_s)
188
- attrs[name.to_s]
189
- else
190
- attr.default
191
- end
183
+ value = attr_value(attrs, name, attr)
192
184
 
193
- value = if attr.collection?
194
- (value || []).map do |v|
195
- if v.is_a?(Hash)
196
- attr.type.new(v)
197
- else
198
- Lutaml::Model::Type.cast(
199
- v, attr.type
200
- )
201
- end
202
- end
203
- elsif value.is_a?(Hash) && attr.type != Lutaml::Model::Type::Hash
204
- attr.type.new(value)
205
- else
206
- Lutaml::Model::Type.cast(value, attr.type)
207
- end
208
185
  send(:"#{name}=", ensure_utf8(value))
209
186
  end
210
187
  end
211
- # rubocop:enable Layout/LineLength
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)
204
+ else
205
+ Lutaml::Model::Type.cast(
206
+ v, attr_rule.type
207
+ )
208
+ end
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)
214
+ end
215
+ end
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
212
228
 
213
229
  # TODO: Make this work
214
230
  # FORMATS.each do |format|
@@ -325,11 +341,6 @@ module Lutaml
325
341
  value
326
342
  end
327
343
  end
328
- # rubocop:enable Metrics/MethodLength
329
- # rubocop:enable Metrics/BlockLength
330
- # rubocop:enable Metrics/AbcSize
331
- # rubocop:enable Metrics/CyclomaticComplexity
332
- # rubocop:enable Metrics/PerceivedComplexity
333
344
  end
334
345
  end
335
346
  end
@@ -74,6 +74,24 @@ module Lutaml
74
74
  end
75
75
  end
76
76
 
77
+ class TextWithTags
78
+ attr_reader :content
79
+
80
+ def initialize(ordered_text_with_tags)
81
+ @content = ordered_text_with_tags
82
+ end
83
+
84
+ def self.cast(value)
85
+ return value if value.is_a?(self)
86
+
87
+ new(value)
88
+ end
89
+
90
+ def self.serialize(value)
91
+ value.content.join
92
+ end
93
+ end
94
+
77
95
  class JSON
78
96
  attr_reader :value
79
97
 
@@ -94,7 +112,7 @@ module Lutaml
94
112
  end
95
113
 
96
114
  def self.cast(value)
97
- return value if value.is_a?(self)
115
+ return value if value.is_a?(self) || value.nil?
98
116
 
99
117
  new(::JSON.parse(value))
100
118
  end
@@ -104,15 +122,8 @@ module Lutaml
104
122
  end
105
123
  end
106
124
 
107
- # rubocop:disable Layout/LineLength
108
125
  UUID_REGEX = /\A[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\z/
109
- # rubocop:enable Layout/LineLength
110
126
 
111
- # rubocop:disable Metrics/MethodLength
112
- # rubocop:disable Layout/LineLength
113
- # rubocop:disable Metrics/AbcSize
114
- # rubocop:disable Metrics/CyclomaticComplexity
115
- # rubocop:disable Metrics/PerceivedComplexity
116
127
  def self.cast(value, type)
117
128
  return if value.nil?
118
129
 
@@ -203,11 +214,6 @@ module Lutaml
203
214
  end
204
215
  end.to_h
205
216
  end
206
- # rubocop:enable Metrics/MethodLength
207
- # rubocop:enable Layout/LineLength
208
- # rubocop:enable Metrics/AbcSize
209
- # rubocop:enable Metrics/CyclomaticComplexity
210
- # rubocop:enable Metrics/PerceivedComplexity
211
217
  end
212
218
  end
213
219
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.2.1"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
@@ -12,14 +12,14 @@ module Lutaml
12
12
  new(root)
13
13
  end
14
14
 
15
- def to_h
16
- # { @root.name => parse_element(@root) }
17
- parse_element(@root)
18
- end
19
-
20
15
  def to_xml(options = {})
21
16
  builder = Nokogiri::XML::Builder.new do |xml|
22
- build_element(xml, @root, options)
17
+ if root.is_a?(Lutaml::Model::XmlAdapter::NokogiriElement)
18
+ root.to_xml(xml)
19
+ else
20
+ options[:xml_attributes] = build_namespace_attributes(@root.class)
21
+ build_element(xml, @root, options)
22
+ end
23
23
  end
24
24
 
25
25
  xml_options = {}
@@ -31,123 +31,122 @@ module Lutaml
31
31
 
32
32
  private
33
33
 
34
- # rubocop:disable Metrics/MethodLength
35
- # rubocop:disable Metrics/BlockLength
36
- # rubocop:disable Metrics/AbcSize
37
- # rubocop:disable Metrics/CyclomaticComplexity
38
- # rubocop:disable Metrics/PerceivedComplexity
39
- def build_element(xml, element, _options = {})
40
- if element.is_a?(Lutaml::Model::XmlAdapter::NokogiriElement)
41
- return element.to_xml(xml)
42
- end
43
-
34
+ def build_unordered_element(xml, element, options = {})
44
35
  xml_mapping = element.class.mappings_for(:xml)
45
36
  return xml unless xml_mapping
46
37
 
47
- attributes = build_attributes(element, xml_mapping)
38
+ attributes = options[:xml_attributes] ||= {}
39
+ attributes = build_attributes(element,
40
+ xml_mapping).merge(attributes)&.compact
48
41
 
49
- prefixed_xml = if xml_mapping.namespace_prefix
42
+ prefixed_xml = if options.key?(:namespace_prefix)
43
+ options[:namespace_prefix] ? xml[options[:namespace_prefix]] : xml
44
+ elsif xml_mapping.namespace_prefix
50
45
  xml[xml_mapping.namespace_prefix]
51
46
  else
52
47
  xml
53
48
  end
54
49
 
55
- prefixed_xml.send(xml_mapping.root_element, attributes) do
50
+ prefixed_xml.public_send(xml_mapping.root_element, attributes) do
51
+ if options.key?(:namespace_prefix) && !options[:namespace_prefix]
52
+ xml.parent.namespace = nil
53
+ end
54
+
56
55
  xml_mapping.elements.each do |element_rule|
57
- if element_rule.delegate
58
- attribute_def =
59
- element
60
- .send(element_rule.delegate)
61
- .class
62
- .attributes[element_rule.to]
63
-
64
- value =
65
- element
66
- .send(element_rule.delegate)
67
- .send(element_rule.to)
68
- else
69
- attribute_def = element.class.attributes[element_rule.to]
70
- value = element.send(element_rule.to)
71
- end
56
+ attribute_def = attribute_definition_for(element, element_rule)
57
+ value = attribute_value_for(element, element_rule)
58
+
59
+ next if value.nil? && !element_rule.render_nil?
72
60
 
73
61
  nsp_xml = element_rule.prefix ? xml[element_rule.prefix] : xml
74
62
 
75
- val = if attribute_def.collection?
76
- value
77
- elsif !value.nil? || element_rule.render_nil?
78
- [value]
79
- else
80
- []
81
- end
82
-
83
- val.each do |v|
84
- if attribute_def&.type&.<= Lutaml::Model::Serialize
85
- handle_nested_elements(xml, element_rule, v)
86
- else
87
- nsp_xml.send(element_rule.name) do
88
- if !v.nil?
89
- serialized_value = attribute_def.type.serialize(v)
90
-
91
- if attribute_def.type == Lutaml::Model::Type::Hash
92
- serialized_value.each do |key, val|
93
- xml.send(key) { xml.text val }
94
- end
95
- else
96
- xml.text(serialized_value)
97
- end
98
- end
99
- end
63
+ if attribute_def.collection?
64
+ value.each do |v|
65
+ add_to_xml(nsp_xml, v, attribute_def, element_rule)
100
66
  end
67
+ elsif !value.nil? || element_rule.render_nil?
68
+ add_to_xml(nsp_xml, value, attribute_def, element_rule)
101
69
  end
102
70
  end
103
- prefixed_xml.text element.text unless xml_mapping.elements.any?
104
- end
105
- end
106
- # rubocop:enable Metrics/MethodLength
107
- # rubocop:enable Metrics/BlockLength
108
- # rubocop:enable Metrics/AbcSize
109
- # rubocop:enable Metrics/CyclomaticComplexity
110
- # rubocop:enable Metrics/PerceivedComplexity
111
-
112
- def handle_nested_elements(xml, _element_rule, value)
113
- case value
114
- when Array
115
- value.each { |val| build_element(xml, val) }
116
- else
117
- build_element(xml, value)
71
+
72
+ if xml_mapping.content_mapping
73
+ text = element.send(xml_mapping.content_mapping.to)
74
+ text = text.join if text.is_a?(Array)
75
+
76
+ prefixed_xml.text text
77
+ end
118
78
  end
119
79
  end
120
80
 
121
- # rubocop:disable Metrics/AbcSize
122
- # rubocop:disable Metrics/MethodLength
123
- def parse_element(element)
124
- result = element.children.each_with_object({}) do |child, hash|
125
- value = child.text? ? child.text : parse_element(child)
81
+ def build_ordered_element(xml, element, options = {})
82
+ xml_mapping = element.class.mappings_for(:xml)
83
+ return xml unless xml_mapping
126
84
 
127
- if hash[child.unprefixed_name]
128
- hash[child.unprefixed_name] =
129
- [hash[child.unprefixed_name], value].flatten
130
- else
131
- hash[child.unprefixed_name] = value
85
+ attributes = build_attributes(element, xml_mapping)&.compact
86
+
87
+ prefixed_xml = if options.key?(:namespace_prefix)
88
+ options[:namespace_prefix] ? xml[options[:namespace_prefix]] : xml
89
+ elsif xml_mapping.namespace_prefix
90
+ xml[xml_mapping.namespace_prefix]
91
+ else
92
+ xml
93
+ end
94
+
95
+ prefixed_xml.public_send(xml_mapping.root_element, attributes) do
96
+ if options.key?(:namespace_prefix) && !options[:namespace_prefix]
97
+ xml.parent.namespace = nil
132
98
  end
133
- end
134
99
 
135
- element.attributes.each do |name, attr|
136
- result[name] = attr.value
100
+ index_hash = {}
101
+
102
+ element.element_order.each do |name|
103
+ index_hash[name] ||= -1
104
+ curr_index = index_hash[name] += 1
105
+
106
+ element_rule = xml_mapping.find_by_name(name)
107
+ next if element_rule.nil?
108
+
109
+ attribute_def = attribute_definition_for(element, element_rule)
110
+ value = attribute_value_for(element, element_rule)
111
+ nsp_xml = element_rule.prefix ? xml[element_rule.prefix] : xml
112
+
113
+ if element_rule == xml_mapping.content_mapping
114
+ text = element.send(xml_mapping.content_mapping.to)
115
+ text = text[curr_index] if text.is_a?(Array)
116
+
117
+ prefixed_xml.text text
118
+ elsif attribute_def.collection?
119
+ add_to_xml(nsp_xml, value[curr_index], attribute_def,
120
+ element_rule)
121
+ elsif !value.nil? || element_rule.render_nil?
122
+ add_to_xml(nsp_xml, value, attribute_def, element_rule)
123
+ end
124
+ end
137
125
  end
126
+ end
138
127
 
139
- # result["_text"] = element.text if element.text
140
- result
128
+ def add_to_xml(xml, value, attribute, rule)
129
+ if value && (attribute&.type&.<= Lutaml::Model::Serialize)
130
+ handle_nested_elements(xml, value, rule)
131
+ else
132
+ xml.public_send(rule.name) do
133
+ if !value.nil?
134
+ serialized_value = attribute.type.serialize(value)
135
+
136
+ if attribute.type == Lutaml::Model::Type::Hash
137
+ serialized_value.each do |key, val|
138
+ xml.public_send(key) { xml.text val }
139
+ end
140
+ else
141
+ xml.text(serialized_value)
142
+ end
143
+ end
144
+ end
145
+ end
141
146
  end
142
- # rubocop:enable Metrics/AbcSize
143
- # rubocop:enable Metrics/MethodLength
144
147
  end
145
148
 
146
149
  class NokogiriElement < Element
147
- # rubocop:disable Metrics/MethodLength
148
- # rubocop:disable Metrics/AbcSize
149
- # rubocop:disable Metrics/CyclomaticComplexity
150
- # rubocop:disable Metrics/PerceivedComplexity
151
150
  def initialize(node, root_node: nil)
152
151
  if root_node
153
152
  node.namespaces.each do |prefix, name|
@@ -182,10 +181,6 @@ module Lutaml
182
181
  namespace_prefix: node.namespace&.prefix,
183
182
  )
184
183
  end
185
- # rubocop:enable Metrics/MethodLength
186
- # rubocop:enable Metrics/AbcSize
187
- # rubocop:enable Metrics/CyclomaticComplexity
188
- # rubocop:enable Metrics/PerceivedComplexity
189
184
 
190
185
  def text?
191
186
  # false