lutaml-model 0.3.10 → 0.3.14

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -21,6 +21,16 @@ module Lutaml
21
21
  name
22
22
  end
23
23
  end
24
+
25
+ def namespaced_name
26
+ if unprefixed_name == "lang"
27
+ name
28
+ elsif namespace
29
+ "#{namespace}:#{unprefixed_name}"
30
+ else
31
+ unprefixed_name
32
+ end
33
+ end
24
34
  end
25
35
  end
26
36
  end
@@ -66,24 +66,43 @@ module Lutaml
66
66
  options[:tag_name] = rule.name
67
67
 
68
68
  options[:mapper_class] = attribute&.type if attribute
69
+ options[:namespace_set] = set_namespace?(rule)
69
70
 
70
71
  options
71
72
  end
72
73
 
73
- def parse_element(element)
74
+ def parse_element(element, klass = nil, format = nil)
74
75
  result = Lutaml::Model::MappingHash.new
75
76
  result.item_order = element.order
76
77
 
77
78
  element.children.each_with_object(result) do |child, hash|
78
- value = child.text? ? child.text : parse_element(child)
79
+ attr = klass.attribute_for_child(child.name, format) if klass&.<= Serialize
80
+
81
+ value = if child.text?
82
+ child.text
83
+ elsif attr&.raw?
84
+ child.children.map do |c|
85
+ next c.text if c.text?
86
+
87
+ c.to_xml.doc.root.to_xml({})
88
+ end.join
89
+ else
90
+ parse_element(child, attr&.type || klass, format)
91
+ end
79
92
 
80
- hash[child.unprefixed_name] = if hash[child.unprefixed_name]
81
- [hash[child.unprefixed_name], value].flatten
93
+ hash[child.namespaced_name] = if hash[child.namespaced_name]
94
+ [hash[child.namespaced_name], value].flatten
82
95
  else
83
96
  value
84
97
  end
85
98
  end
86
99
 
100
+ result.merge(attributes_hash(element))
101
+ end
102
+
103
+ def attributes_hash(element)
104
+ result = Lutaml::Model::MappingHash.new
105
+
87
106
  element.attributes.each_value do |attr|
88
107
  if attr.unprefixed_name == "schemaLocation"
89
108
  result["__schema_location"] = {
@@ -92,7 +111,7 @@ module Lutaml
92
111
  schema_location: attr.value,
93
112
  }
94
113
  else
95
- result[attr.unprefixed_name] = attr.value
114
+ result[attr.namespaced_name] = attr.value
96
115
  end
97
116
  end
98
117
 
@@ -107,10 +126,10 @@ module Lutaml
107
126
  end
108
127
  end
109
128
 
110
- def add_to_xml(xml, prefix, value, options = {})
129
+ def add_to_xml(xml, element, prefix, value, options = {})
111
130
  if value.is_a?(Array)
112
131
  value.each do |item|
113
- add_to_xml(xml, prefix, item, options)
132
+ add_to_xml(xml, element, prefix, item, options)
114
133
  end
115
134
 
116
135
  return
@@ -120,7 +139,7 @@ module Lutaml
120
139
  rule = options[:rule]
121
140
 
122
141
  if rule.custom_methods[:to]
123
- @root.send(rule.custom_methods[:to], @root, xml.parent, xml)
142
+ options[:mapper_class].new.send(rule.custom_methods[:to], element, xml.parent, xml)
124
143
  return
125
144
  end
126
145
 
@@ -130,21 +149,29 @@ module Lutaml
130
149
  value,
131
150
  options.merge({ rule: rule, attribute: attribute }),
132
151
  )
133
- else
152
+ elsif rule.prefix_set?
134
153
  xml.create_and_add_element(rule.name, prefix: prefix) do
135
- if !value.nil?
136
- serialized_value = attribute.type.serialize(value)
154
+ add_value(xml, value, attribute)
155
+ end
156
+ else
157
+ xml.create_and_add_element(rule.name) do
158
+ add_value(xml, value, attribute)
159
+ end
160
+ end
161
+ end
137
162
 
138
- if attribute.type == Lutaml::Model::Type::Hash
139
- serialized_value.each do |key, val|
140
- xml.create_and_add_element(key) do |element|
141
- element.text(val)
142
- end
143
- end
144
- else
145
- xml.add_text(xml, serialized_value)
163
+ def add_value(xml, value, attribute)
164
+ if !value.nil?
165
+ serialized_value = attribute.type.serialize(value)
166
+
167
+ if attribute.type == Lutaml::Model::Type::Hash
168
+ serialized_value.each do |key, val|
169
+ xml.create_and_add_element(key) do |element|
170
+ element.text(val)
146
171
  end
147
172
  end
173
+ else
174
+ xml.add_text(xml, serialized_value)
148
175
  end
149
176
  end
150
177
  end
@@ -176,21 +203,28 @@ module Lutaml
176
203
  prefixed_xml.add_namespace_prefix(nil)
177
204
  end
178
205
 
206
+ xml_mapping.attributes.each do |attribute_rule|
207
+ attribute_rule.serialize_attribute(element, prefixed_xml.parent, xml)
208
+ end
209
+
179
210
  xml_mapping.elements.each do |element_rule|
180
211
  attribute_def = attribute_definition_for(element, element_rule,
181
212
  mapper_class: mapper_class)
182
213
 
183
- value = attribute_value_for(element, element_rule)
214
+ if attribute_def
215
+ value = attribute_value_for(element, element_rule)
184
216
 
185
- next if value.nil? && !element_rule.render_nil?
217
+ next if value.nil? && !element_rule.render_nil?
186
218
 
187
- value = [value] if attribute_def.collection? && !value.is_a?(Array)
219
+ value = [value] if attribute_def.collection? && !value.is_a?(Array)
220
+ end
188
221
 
189
222
  add_to_xml(
190
223
  prefixed_xml,
224
+ element,
191
225
  element_rule.prefix,
192
226
  value,
193
- options.merge({ attribute: attribute_def, rule: element_rule }),
227
+ options.merge({ attribute: attribute_def, rule: element_rule, mapper_class: mapper_class }),
194
228
  )
195
229
  end
196
230
 
@@ -199,7 +233,7 @@ module Lutaml
199
233
  @root.send(content_rule.custom_methods[:to], element,
200
234
  prefixed_xml.parent, prefixed_xml)
201
235
  else
202
- text = element.send(content_rule.to)
236
+ text = content_rule.serialize(element)
203
237
  text = text.join if text.is_a?(Array)
204
238
  prefixed_xml.add_text(xml, text)
205
239
  end
@@ -216,15 +250,21 @@ module Lutaml
216
250
  mapper_class ? mapper_class.mappings_for(:xml).mixed_content? : false
217
251
  end
218
252
 
219
- def build_namespace_attributes(klass, processed = {})
253
+ def set_namespace?(rule)
254
+ rule.nil? || !rule.namespace_set? || !rule.namespace.nil?
255
+ end
256
+
257
+ def build_namespace_attributes(klass, processed = {}, options = {})
220
258
  xml_mappings = klass.mappings_for(:xml)
221
259
  attributes = klass.attributes
222
260
 
223
261
  attrs = {}
224
262
 
225
- if xml_mappings.namespace_uri
226
- prefixed_name = ["xmlns",
227
- xml_mappings.namespace_prefix].compact.join(":")
263
+ if xml_mappings.namespace_uri && set_namespace?(options[:caller_rule])
264
+ prefixed_name = [
265
+ "xmlns",
266
+ xml_mappings.namespace_prefix,
267
+ ].compact.join(":")
228
268
 
229
269
  attrs[prefixed_name] = xml_mappings.namespace_uri
230
270
  end
@@ -239,14 +279,16 @@ module Lutaml
239
279
  type = if mapping_rule.delegate
240
280
  attributes[mapping_rule.delegate].type.attributes[mapping_rule.to].type
241
281
  else
242
- attributes[mapping_rule.to].type
282
+ attributes[mapping_rule.to]&.type
243
283
  end
244
284
 
285
+ next unless type
286
+
245
287
  if type <= Lutaml::Model::Serialize
246
- attrs = attrs.merge(build_namespace_attributes(type, processed))
288
+ attrs = attrs.merge(build_namespace_attributes(type, processed, { caller_rule: mapping_rule }))
247
289
  end
248
290
 
249
- if mapping_rule.namespace
291
+ if mapping_rule.namespace && mapping_rule.prefix && mapping_rule.name != "lang"
250
292
  attrs["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
251
293
  end
252
294
  end
@@ -255,22 +297,31 @@ module Lutaml
255
297
  end
256
298
 
257
299
  def build_attributes(element, xml_mapping, options = {})
258
- attrs = namespace_attributes(xml_mapping)
300
+ attrs = if options.fetch(:namespace_set, true)
301
+ namespace_attributes(xml_mapping)
302
+ else
303
+ {}
304
+ end
305
+
306
+ if element.respond_to?(:schema_location) && element.schema_location
307
+ attrs.merge!(element.schema_location.to_xml_attributes)
308
+ end
259
309
 
260
310
  xml_mapping.attributes.each_with_object(attrs) do |mapping_rule, hash|
261
311
  next if options[:except]&.include?(mapping_rule.to)
312
+ next if mapping_rule.custom_methods[:to]
262
313
 
263
- if mapping_rule.namespace
314
+ if mapping_rule.namespace && mapping_rule.prefix && mapping_rule.name != "lang"
264
315
  hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
265
316
  end
266
317
 
267
- hash[mapping_rule.prefixed_name] = element.send(mapping_rule.to)
318
+ hash[mapping_rule.prefixed_name] = mapping_rule.to_value_for(element)
268
319
  end
269
320
 
270
321
  xml_mapping.elements.each_with_object(attrs) do |mapping_rule, hash|
271
322
  next if options[:except]&.include?(mapping_rule.to)
272
323
 
273
- if mapping_rule.namespace
324
+ if mapping_rule.namespace && mapping_rule.prefix
274
325
  hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
275
326
  end
276
327
  end
@@ -16,7 +16,8 @@ module Lutaml
16
16
  children = [],
17
17
  text = nil,
18
18
  parent_document: nil,
19
- namespace_prefix: nil
19
+ namespace_prefix: nil,
20
+ default_namespace: nil
20
21
  )
21
22
  @name = extract_name(name)
22
23
  @namespace_prefix = namespace_prefix || extract_namespace_prefix(name)
@@ -24,11 +25,20 @@ module Lutaml
24
25
  @children = children
25
26
  @text = text
26
27
  @parent_document = parent_document
28
+ @default_namespace = default_namespace
27
29
  end
28
30
 
29
31
  def name
30
- if namespace_prefix
31
- "#{namespace_prefix}:#{@name}"
32
+ return @name unless namespace_prefix
33
+
34
+ "#{namespace_prefix}:#{@name}"
35
+ end
36
+
37
+ def namespaced_name
38
+ if namespaces[namespace_prefix] && !text?
39
+ "#{namespaces[namespace_prefix].uri}:#{@name}"
40
+ elsif @default_namespace && !text?
41
+ "#{@default_namespace}:#{name}"
32
42
  else
33
43
  @name
34
44
  end
@@ -37,7 +37,7 @@ module Lutaml
37
37
  end
38
38
 
39
39
  def attr_name
40
- if prefix && !prefix.empty?
40
+ if Utils.present?(prefix)
41
41
  "xmlns:#{prefix}"
42
42
  else
43
43
  "xmlns"
@@ -6,7 +6,8 @@ module Lutaml
6
6
  attr_reader :root_element,
7
7
  :namespace_uri,
8
8
  :namespace_prefix,
9
- :mixed_content
9
+ :mixed_content,
10
+ :ordered
10
11
 
11
12
  def initialize
12
13
  @elements = {}
@@ -16,10 +17,12 @@ module Lutaml
16
17
  end
17
18
 
18
19
  alias mixed_content? mixed_content
20
+ alias ordered? ordered
19
21
 
20
- def root(name, mixed: false)
22
+ def root(name, mixed: false, ordered: false)
21
23
  @root_element = name
22
24
  @mixed_content = mixed
25
+ @ordered = ordered || mixed # mixed contenet will always be ordered
23
26
  end
24
27
 
25
28
  def prefixed_root
@@ -38,37 +41,46 @@ module Lutaml
38
41
  # rubocop:disable Metrics/ParameterLists
39
42
  def map_element(
40
43
  name,
41
- to:,
44
+ to: nil,
42
45
  render_nil: false,
43
46
  with: {},
44
47
  delegate: nil,
45
48
  namespace: (namespace_set = false
46
49
  nil),
47
- prefix: nil
50
+ prefix: (prefix_set = false
51
+ nil)
48
52
  )
49
- @elements[name] = XmlMappingRule.new(
53
+ validate!(name, to, with)
54
+
55
+ rule = XmlMappingRule.new(
50
56
  name,
51
57
  to: to,
52
58
  render_nil: render_nil,
53
59
  with: with,
54
60
  delegate: delegate,
55
61
  namespace: namespace,
62
+ default_namespace: namespace_uri,
56
63
  prefix: prefix,
57
64
  namespace_set: namespace_set != false,
65
+ prefix_set: prefix_set != false,
58
66
  )
67
+ @elements[rule.namespaced_name] = rule
59
68
  end
60
69
 
61
70
  def map_attribute(
62
71
  name,
63
- to:,
72
+ to: nil,
64
73
  render_nil: false,
65
74
  with: {},
66
75
  delegate: nil,
67
76
  namespace: (namespace_set = false
68
77
  nil),
69
- prefix: nil
78
+ prefix: (prefix_set = false
79
+ nil)
70
80
  )
71
- @attributes[name] = XmlMappingRule.new(
81
+ validate!(name, to, with)
82
+
83
+ rule = XmlMappingRule.new(
72
84
  name,
73
85
  to: to,
74
86
  render_nil: render_nil,
@@ -76,19 +88,25 @@ module Lutaml
76
88
  delegate: delegate,
77
89
  namespace: namespace,
78
90
  prefix: prefix,
91
+ attribute: true,
92
+ default_namespace: namespace_uri,
79
93
  namespace_set: namespace_set != false,
94
+ prefix_set: prefix_set != false,
80
95
  )
96
+ @attributes[rule.namespaced_name] = rule
81
97
  end
82
98
 
83
99
  # rubocop:enable Metrics/ParameterLists
84
100
 
85
101
  def map_content(
86
- to:,
102
+ to: nil,
87
103
  render_nil: false,
88
104
  with: {},
89
105
  delegate: nil,
90
106
  mixed: false
91
107
  )
108
+ validate!("content", to, with)
109
+
92
110
  @content_mapping = XmlMappingRule.new(
93
111
  nil,
94
112
  to: to,
@@ -99,6 +117,18 @@ module Lutaml
99
117
  )
100
118
  end
101
119
 
120
+ def validate!(key, to, with)
121
+ if to.nil? && with.empty?
122
+ msg = ":to or :with argument is required for mapping '#{key}'"
123
+ raise IncorrectMappingArgumentsError.new(msg)
124
+ end
125
+
126
+ if !with.empty? && (with[:from].nil? || with[:to].nil?)
127
+ msg = ":with argument for mapping '#{key}' requires :to and :from keys"
128
+ raise IncorrectMappingArgumentsError.new(msg)
129
+ end
130
+ end
131
+
102
132
  def elements
103
133
  @elements.values
104
134
  end
@@ -136,6 +166,27 @@ module Lutaml
136
166
  end
137
167
  end
138
168
  end
169
+
170
+ def deep_dup
171
+ self.class.new.tap do |xml_mapping|
172
+ xml_mapping.root(@root_element.dup, mixed: @mixed_content, ordered: @ordered)
173
+ xml_mapping.namespace(@namespace_uri.dup, @namespace_prefix.dup)
174
+
175
+ xml_mapping.instance_variable_set(:@attributes, dup_mappings(@attributes))
176
+ xml_mapping.instance_variable_set(:@elements, dup_mappings(@elements))
177
+ xml_mapping.instance_variable_set(:@content_mapping, @content_mapping&.deep_dup)
178
+ end
179
+ end
180
+
181
+ def dup_mappings(mappings)
182
+ new_mappings = {}
183
+
184
+ mappings.each do |key, mapping_rule|
185
+ new_mappings[key] = mapping_rule.deep_dup
186
+ end
187
+
188
+ new_mappings
189
+ end
139
190
  end
140
191
  end
141
192
  end
@@ -3,7 +3,7 @@ require_relative "mapping_rule"
3
3
  module Lutaml
4
4
  module Model
5
5
  class XmlMappingRule < MappingRule
6
- attr_reader :namespace, :prefix
6
+ attr_reader :namespace, :prefix, :mixed_content, :default_namespace
7
7
 
8
8
  def initialize(
9
9
  name,
@@ -14,7 +14,10 @@ module Lutaml
14
14
  namespace: nil,
15
15
  prefix: nil,
16
16
  mixed_content: false,
17
- namespace_set: false
17
+ namespace_set: false,
18
+ prefix_set: false,
19
+ attribute: false,
20
+ default_namespace: nil
18
21
  )
19
22
  super(
20
23
  name,
@@ -22,8 +25,7 @@ module Lutaml
22
25
  render_nil: render_nil,
23
26
  with: with,
24
27
  delegate: delegate,
25
- mixed_content: mixed_content,
26
- namespace_set: namespace_set,
28
+ attribute: attribute,
27
29
  )
28
30
 
29
31
  @namespace = if namespace.to_s == "inherit"
@@ -33,6 +35,64 @@ module Lutaml
33
35
  namespace
34
36
  end
35
37
  @prefix = prefix
38
+ @mixed_content = mixed_content
39
+
40
+ @default_namespace = default_namespace
41
+
42
+ @namespace_set = namespace_set
43
+ @prefix_set = prefix_set
44
+ end
45
+
46
+ def namespace_set?
47
+ !!@namespace_set
48
+ end
49
+
50
+ def prefix_set?
51
+ !!@prefix_set
52
+ end
53
+
54
+ def content_mapping?
55
+ name.nil?
56
+ end
57
+
58
+ def mixed_content?
59
+ !!@mixed_content
60
+ end
61
+
62
+ def prefixed_name
63
+ if prefix
64
+ "#{prefix}:#{name}"
65
+ else
66
+ name
67
+ end
68
+ end
69
+
70
+ def namespaced_name
71
+ if name == "lang"
72
+ "#{prefix}:#{name}"
73
+ elsif namespace_set? || @attribute
74
+ [namespace, name].compact.join(":")
75
+ elsif default_namespace
76
+ "#{default_namespace}:#{name}"
77
+ else
78
+ name
79
+ end
80
+ end
81
+
82
+ def deep_dup
83
+ self.class.new(
84
+ name.dup,
85
+ to: to,
86
+ render_nil: render_nil,
87
+ with: Utils.deep_dup(custom_methods),
88
+ delegate: delegate,
89
+ namespace: namespace.dup,
90
+ prefix: prefix.dup,
91
+ mixed_content: mixed_content,
92
+ namespace_set: namespace_set?,
93
+ prefix_set: prefix_set?,
94
+ default_namespace: default_namespace.dup,
95
+ )
36
96
  end
37
97
  end
38
98
  end
@@ -5,12 +5,17 @@ module Lutaml
5
5
  module Model
6
6
  module YamlAdapter
7
7
  class StandardYamlAdapter < YamlDocument
8
+ PERMITTED_CLASSES_BASE = [Date, Time, DateTime, Symbol, Hash,
9
+ Array].freeze
10
+
11
+ PERMITTED_CLASSES = if defined?(BigDecimal)
12
+ PERMITTED_CLASSES_BASE + [BigDecimal]
13
+ else
14
+ PERMITTED_CLASSES_BASE
15
+ end.freeze
16
+
8
17
  def self.parse(yaml)
9
- YAML.safe_load(
10
- yaml,
11
- permitted_classes: [Date, Time, DateTime, Symbol,
12
- BigDecimal, Hash, Array],
13
- )
18
+ YAML.safe_load(yaml, permitted_classes: PERMITTED_CLASSES)
14
19
  end
15
20
 
16
21
  def to_yaml(options = {})