lutaml-model 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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