lutaml-model 0.3.10 → 0.3.14

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.
@@ -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 = {})