lutaml-model 0.2.1 → 0.3.1

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