lutaml-model 0.2.1 → 0.3.1

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.
@@ -12,14 +12,15 @@ 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
- builder = Nokogiri::XML::Builder.new do |xml|
22
- build_element(xml, @root, options)
16
+ builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
17
+ if root.is_a?(Lutaml::Model::XmlAdapter::NokogiriElement)
18
+ root.to_xml(xml)
19
+ else
20
+ mapper_class = options[:mapper_class] || @root.class
21
+ options[:xml_attributes] = build_namespace_attributes(mapper_class)
22
+ build_element(xml, @root, options)
23
+ end
23
24
  end
24
25
 
25
26
  xml_options = {}
@@ -31,123 +32,129 @@ module Lutaml
31
32
 
32
33
  private
33
34
 
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
-
44
- xml_mapping = element.class.mappings_for(:xml)
35
+ def build_unordered_element(xml, element, options = {})
36
+ mapper_class = options[:mapper_class] || element.class
37
+ xml_mapping = mapper_class.mappings_for(:xml)
45
38
  return xml unless xml_mapping
46
39
 
47
- attributes = build_attributes(element, xml_mapping)
40
+ attributes = options[:xml_attributes] ||= {}
41
+ attributes = build_attributes(element,
42
+ xml_mapping).merge(attributes)&.compact
48
43
 
49
- prefixed_xml = if xml_mapping.namespace_prefix
44
+ prefixed_xml = if options.key?(:namespace_prefix)
45
+ options[:namespace_prefix] ? xml[options[:namespace_prefix]] : xml
46
+ elsif xml_mapping.namespace_prefix
50
47
  xml[xml_mapping.namespace_prefix]
51
48
  else
52
49
  xml
53
50
  end
54
51
 
55
- prefixed_xml.send(xml_mapping.root_element, attributes) do
52
+ prefixed_xml.public_send(xml_mapping.root_element, attributes) do
53
+ if options.key?(:namespace_prefix) && !options[:namespace_prefix]
54
+ xml.parent.namespace = nil
55
+ end
56
+
56
57
  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
58
+ attribute_def = attribute_definition_for(element, element_rule, mapper_class: mapper_class)
59
+ value = attribute_value_for(element, element_rule)
60
+
61
+ next if value.nil? && !element_rule.render_nil?
72
62
 
73
63
  nsp_xml = element_rule.prefix ? xml[element_rule.prefix] : xml
74
64
 
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
65
+ if attribute_def.collection?
66
+ value.each do |v|
67
+ add_to_xml(nsp_xml, v, attribute_def, element_rule)
100
68
  end
69
+ elsif !value.nil? || element_rule.render_nil?
70
+ add_to_xml(nsp_xml, value, attribute_def, element_rule)
101
71
  end
102
72
  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)
73
+
74
+ if xml_mapping.content_mapping
75
+ text = element.send(xml_mapping.content_mapping.to)
76
+ text = text.join if text.is_a?(Array)
77
+
78
+ prefixed_xml.text text
79
+ end
118
80
  end
119
81
  end
120
82
 
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)
83
+ def build_ordered_element(xml, element, options = {})
84
+ mapper_class = options[:mapper_class] || element.class
85
+ xml_mapping = mapper_class.mappings_for(:xml)
86
+ return xml unless xml_mapping
126
87
 
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
88
+ attributes = build_attributes(element, xml_mapping)&.compact
89
+
90
+ prefixed_xml = if options.key?(:namespace_prefix)
91
+ options[:namespace_prefix] ? xml[options[:namespace_prefix]] : xml
92
+ elsif xml_mapping.namespace_prefix
93
+ xml[xml_mapping.namespace_prefix]
94
+ else
95
+ xml
96
+ end
97
+
98
+ prefixed_xml.public_send(xml_mapping.root_element, attributes) do
99
+ if options.key?(:namespace_prefix) && !options[:namespace_prefix]
100
+ xml.parent.namespace = nil
132
101
  end
133
- end
134
102
 
135
- element.attributes.each do |name, attr|
136
- result[name] = attr.value
103
+ index_hash = {}
104
+
105
+ element.element_order.each do |name|
106
+ index_hash[name] ||= -1
107
+ curr_index = index_hash[name] += 1
108
+
109
+ element_rule = xml_mapping.find_by_name(name)
110
+ next if element_rule.nil?
111
+
112
+ attribute_def = attribute_definition_for(element, element_rule, mapper_class: mapper_class)
113
+ value = attribute_value_for(element, element_rule)
114
+ nsp_xml = element_rule.prefix ? xml[element_rule.prefix] : xml
115
+
116
+ if element_rule == xml_mapping.content_mapping
117
+ text = element.send(xml_mapping.content_mapping.to)
118
+ text = text[curr_index] if text.is_a?(Array)
119
+
120
+ prefixed_xml.text text
121
+ elsif attribute_def.collection?
122
+ add_to_xml(nsp_xml, value[curr_index], attribute_def,
123
+ element_rule)
124
+ elsif !value.nil? || element_rule.render_nil?
125
+ add_to_xml(nsp_xml, value, attribute_def, element_rule)
126
+ end
127
+ end
137
128
  end
129
+ end
130
+
131
+ def add_to_xml(xml, value, attribute, rule)
132
+ if value && (attribute&.type&.<= Lutaml::Model::Serialize)
133
+ handle_nested_elements(
134
+ xml,
135
+ value,
136
+ rule: rule,
137
+ attribute: attribute,
138
+ )
139
+ else
140
+ xml.public_send(rule.name) do
141
+ if !value.nil?
142
+ serialized_value = attribute.type.serialize(value)
138
143
 
139
- # result["_text"] = element.text if element.text
140
- result
144
+ if attribute.type == Lutaml::Model::Type::Hash
145
+ serialized_value.each do |key, val|
146
+ xml.public_send(key) { xml.text val }
147
+ end
148
+ else
149
+ xml.text(serialized_value)
150
+ end
151
+ end
152
+ end
153
+ end
141
154
  end
142
- # rubocop:enable Metrics/AbcSize
143
- # rubocop:enable Metrics/MethodLength
144
155
  end
145
156
 
146
157
  class NokogiriElement < Element
147
- # rubocop:disable Metrics/MethodLength
148
- # rubocop:disable Metrics/AbcSize
149
- # rubocop:disable Metrics/CyclomaticComplexity
150
- # rubocop:disable Metrics/PerceivedComplexity
151
158
  def initialize(node, root_node: nil)
152
159
  if root_node
153
160
  node.namespaces.each do |prefix, name|
@@ -182,10 +189,6 @@ module Lutaml
182
189
  namespace_prefix: node.namespace&.prefix,
183
190
  )
184
191
  end
185
- # rubocop:enable Metrics/MethodLength
186
- # rubocop:enable Metrics/AbcSize
187
- # rubocop:enable Metrics/CyclomaticComplexity
188
- # rubocop:enable Metrics/PerceivedComplexity
189
192
 
190
193
  def text?
191
194
  # false
@@ -12,14 +12,17 @@ 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 = Ox::Builder.new
22
- build_element(builder, @root, options)
17
+
18
+ if @root.is_a?(Lutaml::Model::XmlAdapter::OxElement)
19
+ @root.to_xml(builder)
20
+ elsif ordered?(@root, options)
21
+ build_ordered_element(builder, @root, options)
22
+ else
23
+ build_element(builder, @root, options)
24
+ end
25
+
23
26
  # xml_data = Ox.dump(builder)
24
27
  xml_data = builder.to_s
25
28
  options[:declaration] ? declaration(options) + xml_data : xml_data
@@ -27,28 +30,25 @@ module Lutaml
27
30
 
28
31
  private
29
32
 
30
- # rubocop:disable Layout/LineLength
31
- # rubocop:disable Layout/MethodLength
32
- # rubocop:disable Metrics/AbcSize
33
- # rubocop:disable Metrics/CyclomaticComplexity
34
- # rubocop:disable Metrics/PerceivedComplexity
35
- def build_element(builder, element, _options = {})
36
- return element.to_xml(builder) if element.is_a?(Lutaml::Model::XmlAdapter::OxElement)
37
-
38
- xml_mapping = element.class.mappings_for(:xml)
33
+ def build_unordered_element(builder, element, options = {})
34
+ mapper_class = options[:mapper_class] || element.class
35
+ xml_mapping = mapper_class.mappings_for(:xml)
39
36
  return xml unless xml_mapping
40
37
 
41
- attributes = build_attributes(element, xml_mapping)
38
+ attributes = build_attributes(element, xml_mapping).compact
42
39
 
43
- builder.element(xml_mapping.root_element, attributes) do |el|
40
+ prefixed_name = if options.key?(:namespace_prefix)
41
+ [options[:namespace_prefix], xml_mapping.root_element].compact.join(":")
42
+ elsif xml_mapping.namespace_prefix
43
+ "#{xml_mapping.namespace_prefix}:#{xml_mapping.root_element}"
44
+ else
45
+ xml_mapping.root_element
46
+ end
47
+
48
+ builder.element(prefixed_name, attributes) do |el|
44
49
  xml_mapping.elements.each do |element_rule|
45
- if element_rule.delegate
46
- attribute_def = element.send(element_rule.delegate).class.attributes[element_rule.to]
47
- value = element.send(element_rule.delegate).send(element_rule.to)
48
- else
49
- attribute_def = element.class.attributes[element_rule.to]
50
- value = element.send(element_rule.to)
51
- end
50
+ attribute_def = attribute_definition_for(element, element_rule, mapper_class: mapper_class)
51
+ value = attribute_value_for(element, element_rule)
52
52
 
53
53
  val = if attribute_def.collection?
54
54
  value
@@ -60,70 +60,104 @@ module Lutaml
60
60
 
61
61
  val.each do |v|
62
62
  if attribute_def&.type&.<= Lutaml::Model::Serialize
63
- handle_nested_elements(el, element_rule, v)
63
+ handle_nested_elements(el, v, rule: element_rule, attribute: attribute_def)
64
64
  else
65
- builder.element(element_rule.name) do |el|
65
+ builder.element(element_rule.prefixed_name) do |el|
66
66
  el.text(attribute_def.type.serialize(v)) if v
67
67
  end
68
68
  end
69
69
  end
70
70
  end
71
- # if element.children.any?
72
- # element.children.each do |child|
73
- # build_element(el, child, options)
74
- # end
75
- # elsif element.text
76
- # el.text(element.text)
77
- # end
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
+ el.text text
77
+ end
78
78
  end
79
79
  end
80
- # rubocop:enable Layout/LineLength
81
- # rubocop:enable Layout/MethodLength
82
- # rubocop:enable Metrics/AbcSize
83
- # rubocop:enable Metrics/CyclomaticComplexity
84
- # rubocop:enable Metrics/PerceivedComplexity
85
-
86
- def handle_nested_elements(builder, _element_rule, value)
87
- case value
88
- when Array
89
- value.each { |val| build_element(builder, val) }
90
- else
91
- build_element(builder, value)
80
+
81
+ def build_ordered_element(builder, element, options = {})
82
+ mapper_class = options[:mapper_class] || element.class
83
+ xml_mapping = mapper_class.mappings_for(:xml)
84
+ return xml unless xml_mapping
85
+
86
+ attributes = build_attributes(element, xml_mapping).compact
87
+
88
+ builder.element(xml_mapping.root_element, attributes) do |el|
89
+ index_hash = {}
90
+
91
+ element.element_order.each do |name|
92
+ index_hash[name] ||= -1
93
+ curr_index = index_hash[name] += 1
94
+
95
+ element_rule = xml_mapping.find_by_name(name)
96
+
97
+ attribute_def = attribute_definition_for(element, element_rule, mapper_class: mapper_class)
98
+ value = attribute_value_for(element, element_rule)
99
+
100
+ if element_rule == xml_mapping.content_mapping
101
+ text = element.send(xml_mapping.content_mapping.to)
102
+ text = text[curr_index] if text.is_a?(Array)
103
+
104
+ el.text text
105
+ elsif attribute_def.collection?
106
+ add_to_xml(el, value[curr_index], attribute_def, element_rule)
107
+ elsif !value.nil? || element_rule.render_nil?
108
+ add_to_xml(el, value, attribute_def, element_rule)
109
+ end
110
+ end
92
111
  end
93
112
  end
94
113
 
95
- def parse_element(element)
96
- result = { "_text" => element.text }
97
- element.nodes.each do |child|
98
- next if child.is_a?(Ox::Raw) || child.is_a?(Ox::Comment)
114
+ def add_to_xml(xml, value, attribute, rule)
115
+ if value && (attribute&.type&.<= Lutaml::Model::Serialize)
116
+ handle_nested_elements(
117
+ xml,
118
+ value,
119
+ rule: rule,
120
+ attribute: attribute,
121
+ )
122
+ else
123
+ xml.element(rule.name) do |el|
124
+ if !value.nil?
125
+ serialized_value = attribute.type.serialize(value)
99
126
 
100
- result[child.name] ||= []
101
- result[child.name] << parse_element(child)
127
+ if attribute.type == Lutaml::Model::Type::Hash
128
+ serialized_value.each do |key, val|
129
+ el.element(key) { |child_el| child_el.text val }
130
+ end
131
+ else
132
+ el.text(serialized_value)
133
+ end
134
+ end
135
+ end
102
136
  end
103
- result
104
137
  end
105
138
  end
106
139
 
107
140
  class OxElement < Element
108
- # rubocop:disable Metrics/AbcSize
109
- # rubocop:disable Metrics/MethodLength
110
- # rubocop:disable Layout/LineLength
111
- # rubocop:disable Metrics/CyclomaticComplexity
112
- # rubocop:disable Metrics/PerceivedComplexity
113
141
  def initialize(node, root_node: nil)
114
- attributes = node.attributes.each_with_object({}) do |(name, value), hash|
115
- if attribute_is_namespace?(name)
142
+ if node.is_a?(String)
143
+ super("text", {}, [], node, parent_document: root_node)
144
+ else
145
+ namespace_attributes(node.attributes).each do |(name, value)|
116
146
  if root_node
117
- root_node.add_namespace(Lutaml::Model::XmlNamespace.new(value,
118
- name))
147
+ root_node.add_namespace(Lutaml::Model::XmlNamespace.new(value, name))
119
148
  else
120
149
  add_namespace(Lutaml::Model::XmlNamespace.new(value, name))
121
150
  end
122
- else
151
+ end
152
+
153
+ attributes = node.attributes.each_with_object({}) do |(name, value), hash|
154
+ next if attribute_is_namespace?(name)
155
+
123
156
  namespace_prefix = name.to_s.split(":").first
124
- if root_node && (n = root_node.namespaces[namespace_prefix])
125
- namespace = n.uri
126
- prefix = n.prefix
157
+ if (n = name.to_s.split(":")).length > 1
158
+ namespace = (root_node || self).namespaces[namespace_prefix]&.uri
159
+ namespace ||= Lutaml::Model::XmlAdapter::XML_NAMESPACE_URI
160
+ prefix = n.first
127
161
  end
128
162
 
129
163
  hash[name.to_s] = Attribute.new(
@@ -133,35 +167,39 @@ module Lutaml
133
167
  namespace_prefix: prefix,
134
168
  )
135
169
  end
136
- end
137
170
 
138
- super(
139
- node.name.to_s,
140
- attributes,
141
- parse_children(node, root_node: root_node || self),
142
- node.text,
143
- parent_document: root_node
144
- )
171
+ super(
172
+ node.name.to_s,
173
+ attributes,
174
+ parse_children(node, root_node: root_node || self),
175
+ node.text,
176
+ parent_document: root_node
177
+ )
178
+ end
145
179
  end
146
- # rubocop:enable Metrics/AbcSize
147
- # rubocop:enable Metrics/MethodLength
148
- # rubocop:enable Layout/LineLength
149
- # rubocop:enable Metrics/CyclomaticComplexity
150
- # rubocop:enable Metrics/PerceivedComplexity
151
180
 
152
181
  def to_xml(builder = nil)
153
182
  builder ||= Ox::Builder.new
154
183
  attrs = build_attributes(self)
155
184
 
156
- builder.element(name, attrs) do |el|
157
- if children.any?
185
+ if text?
186
+ builder.text(text)
187
+ else
188
+ builder.element(name, attrs) do |el|
158
189
  children.each { |child| child.to_xml(el) }
159
- elsif text
160
- el.text(text)
161
190
  end
162
191
  end
163
192
  end
164
193
 
194
+ def namespace_attributes(attributes)
195
+ attributes.select { |attr| attribute_is_namespace?(attr) }
196
+ end
197
+
198
+ def text?
199
+ # false
200
+ children.empty? && text&.length&.positive?
201
+ end
202
+
165
203
  def build_attributes(node)
166
204
  attrs = node.attributes.transform_values(&:value)
167
205
 
@@ -172,12 +210,14 @@ module Lutaml
172
210
  attrs
173
211
  end
174
212
 
213
+ def nodes
214
+ children
215
+ end
216
+
175
217
  private
176
218
 
177
219
  def parse_children(node, root_node: nil)
178
- node.nodes.select do |child|
179
- child.is_a?(Ox::Element)
180
- end.map do |child|
220
+ node.nodes.map do |child|
181
221
  OxElement.new(child, root_node: root_node)
182
222
  end
183
223
  end