lutaml-model 0.2.1 → 0.3.0

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,16 @@ 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
+ if @root.is_a?(Lutaml::Model::XmlAdapter::OxElement)
18
+ @root.to_xml(builder)
19
+ elsif @root.ordered?
20
+ build_ordered_element(builder, @root, options)
21
+ else
22
+ build_element(builder, @root, options)
23
+ end
24
+
23
25
  # xml_data = Ox.dump(builder)
24
26
  xml_data = builder.to_s
25
27
  options[:declaration] ? declaration(options) + xml_data : xml_data
@@ -27,28 +29,24 @@ module Lutaml
27
29
 
28
30
  private
29
31
 
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
-
32
+ def build_unordered_element(builder, element, options = {})
38
33
  xml_mapping = element.class.mappings_for(:xml)
39
34
  return xml unless xml_mapping
40
35
 
41
- attributes = build_attributes(element, xml_mapping)
36
+ attributes = build_attributes(element, xml_mapping).compact
42
37
 
43
- builder.element(xml_mapping.root_element, attributes) do |el|
38
+ prefixed_name = if options.key?(:namespace_prefix)
39
+ [options[:namespace_prefix], xml_mapping.root_element].compact.join(":")
40
+ elsif xml_mapping.namespace_prefix
41
+ "#{xml_mapping.namespace_prefix}:#{xml_mapping.root_element}"
42
+ else
43
+ xml_mapping.root_element
44
+ end
45
+
46
+ builder.element(prefixed_name, attributes) do |el|
44
47
  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
48
+ attribute_def = attribute_definition_for(element, element_rule)
49
+ value = attribute_value_for(element, element_rule)
52
50
 
53
51
  val = if attribute_def.collection?
54
52
  value
@@ -60,70 +58,98 @@ module Lutaml
60
58
 
61
59
  val.each do |v|
62
60
  if attribute_def&.type&.<= Lutaml::Model::Serialize
63
- handle_nested_elements(el, element_rule, v)
61
+ handle_nested_elements(el, v, element_rule)
64
62
  else
65
- builder.element(element_rule.name) do |el|
63
+ builder.element(element_rule.prefixed_name) do |el|
66
64
  el.text(attribute_def.type.serialize(v)) if v
67
65
  end
68
66
  end
69
67
  end
70
68
  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
69
+
70
+ if xml_mapping.content_mapping
71
+ text = element.send(xml_mapping.content_mapping.to)
72
+ text = text.join if text.is_a?(Array)
73
+
74
+ el.text text
75
+ end
78
76
  end
79
77
  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)
78
+
79
+ def build_ordered_element(builder, element, _options = {})
80
+ xml_mapping = element.class.mappings_for(:xml)
81
+ return xml unless xml_mapping
82
+
83
+ attributes = build_attributes(element, xml_mapping).compact
84
+
85
+ builder.element(xml_mapping.root_element, attributes) do |el|
86
+ index_hash = {}
87
+
88
+ element.element_order.each do |name|
89
+ index_hash[name] ||= -1
90
+ curr_index = index_hash[name] += 1
91
+
92
+ element_rule = xml_mapping.find_by_name(name)
93
+
94
+ attribute_def = attribute_definition_for(element, element_rule)
95
+ value = attribute_value_for(element, element_rule)
96
+
97
+ if element_rule == xml_mapping.content_mapping
98
+ text = element.send(xml_mapping.content_mapping.to)
99
+ text = text[curr_index] if text.is_a?(Array)
100
+
101
+ el.text text
102
+ elsif attribute_def.collection?
103
+ add_to_xml(el, value[curr_index], attribute_def, element_rule)
104
+ elsif !value.nil? || element_rule.render_nil?
105
+ add_to_xml(el, value, attribute_def, element_rule)
106
+ end
107
+ end
92
108
  end
93
109
  end
94
110
 
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)
111
+ def add_to_xml(xml, value, attribute, rule)
112
+ if value && (attribute&.type&.<= Lutaml::Model::Serialize)
113
+ handle_nested_elements(xml, value, rule)
114
+ else
115
+ xml.element(rule.name) do |el|
116
+ if !value.nil?
117
+ serialized_value = attribute.type.serialize(value)
99
118
 
100
- result[child.name] ||= []
101
- result[child.name] << parse_element(child)
119
+ if attribute.type == Lutaml::Model::Type::Hash
120
+ serialized_value.each do |key, val|
121
+ el.element(key) { |child_el| child_el.text val }
122
+ end
123
+ else
124
+ el.text(serialized_value)
125
+ end
126
+ end
127
+ end
102
128
  end
103
- result
104
129
  end
105
130
  end
106
131
 
107
132
  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
133
  def initialize(node, root_node: nil)
114
- attributes = node.attributes.each_with_object({}) do |(name, value), hash|
115
- if attribute_is_namespace?(name)
134
+ if node.is_a?(String)
135
+ super("text", {}, [], node, parent_document: root_node)
136
+ else
137
+ namespace_attributes(node.attributes).each do |(name, value)|
116
138
  if root_node
117
- root_node.add_namespace(Lutaml::Model::XmlNamespace.new(value,
118
- name))
139
+ root_node.add_namespace(Lutaml::Model::XmlNamespace.new(value, name))
119
140
  else
120
141
  add_namespace(Lutaml::Model::XmlNamespace.new(value, name))
121
142
  end
122
- else
143
+ end
144
+
145
+ attributes = node.attributes.each_with_object({}) do |(name, value), hash|
146
+ next if attribute_is_namespace?(name)
147
+
123
148
  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
149
+ if (n = name.to_s.split(":")).length > 1
150
+ namespace = (root_node || self).namespaces[namespace_prefix]&.uri
151
+ namespace ||= Lutaml::Model::XmlAdapter::XML_NAMESPACE_URI
152
+ prefix = n.first
127
153
  end
128
154
 
129
155
  hash[name.to_s] = Attribute.new(
@@ -133,35 +159,39 @@ module Lutaml
133
159
  namespace_prefix: prefix,
134
160
  )
135
161
  end
136
- end
137
162
 
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
- )
163
+ super(
164
+ node.name.to_s,
165
+ attributes,
166
+ parse_children(node, root_node: root_node || self),
167
+ node.text,
168
+ parent_document: root_node
169
+ )
170
+ end
145
171
  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
172
 
152
173
  def to_xml(builder = nil)
153
174
  builder ||= Ox::Builder.new
154
175
  attrs = build_attributes(self)
155
176
 
156
- builder.element(name, attrs) do |el|
157
- if children.any?
177
+ if text?
178
+ builder.text(text)
179
+ else
180
+ builder.element(name, attrs) do |el|
158
181
  children.each { |child| child.to_xml(el) }
159
- elsif text
160
- el.text(text)
161
182
  end
162
183
  end
163
184
  end
164
185
 
186
+ def namespace_attributes(attributes)
187
+ attributes.select { |attr| attribute_is_namespace?(attr) }
188
+ end
189
+
190
+ def text?
191
+ # false
192
+ children.empty? && text&.length&.positive?
193
+ end
194
+
165
195
  def build_attributes(node)
166
196
  attrs = node.attributes.transform_values(&:value)
167
197
 
@@ -172,12 +202,14 @@ module Lutaml
172
202
  attrs
173
203
  end
174
204
 
205
+ def nodes
206
+ children
207
+ end
208
+
175
209
  private
176
210
 
177
211
  def parse_children(node, root_node: nil)
178
- node.nodes.select do |child|
179
- child.is_a?(Ox::Element)
180
- end.map do |child|
212
+ node.nodes.map do |child|
181
213
  OxElement.new(child, root_node: root_node)
182
214
  end
183
215
  end
@@ -1,10 +1,13 @@
1
1
  # lib/lutaml/model/xml_adapter.rb
2
2
 
3
3
  require_relative "xml_namespace"
4
+ require_relative "mapping_hash"
4
5
 
5
6
  module Lutaml
6
7
  module Model
7
8
  module XmlAdapter
9
+ XML_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace".freeze
10
+
8
11
  class Document
9
12
  attr_reader :root
10
13
 
@@ -33,8 +36,95 @@ module Lutaml
33
36
  declaration
34
37
  end
35
38
 
36
- # rubocop:disable Metrics/AbcSize
37
- # rubocop:disable Metrics/MethodLength
39
+ def to_h
40
+ parse_element(@root)
41
+ end
42
+
43
+ def order
44
+ @root.order
45
+ end
46
+
47
+ def handle_nested_elements(builder, value, rule = nil)
48
+ options = {}
49
+
50
+ if rule&.namespace_set?
51
+ options[:namespace_prefix] = rule.prefix
52
+ end
53
+
54
+ case value
55
+ when Array
56
+ value.each { |val| build_element(builder, val, options) }
57
+ else
58
+ build_element(builder, value, options)
59
+ end
60
+ end
61
+
62
+ def parse_element(element)
63
+ result = Lutaml::Model::MappingHash.new
64
+ result.item_order = element.order
65
+
66
+ element.children.each_with_object(result) do |child, hash|
67
+ value = child.text? ? child.text : parse_element(child)
68
+
69
+ if hash[child.unprefixed_name]
70
+ hash[child.unprefixed_name] =
71
+ [hash[child.unprefixed_name], value].flatten
72
+ else
73
+ hash[child.unprefixed_name] = value
74
+ end
75
+ end
76
+
77
+ element.attributes.each_value do |attr|
78
+ result[attr.unprefixed_name] = attr.value
79
+ end
80
+
81
+ result
82
+ end
83
+
84
+ def build_element(xml, element, _options = {})
85
+ if element.ordered?
86
+ build_ordered_element(xml, element, _options)
87
+ else
88
+ build_unordered_element(xml, element, _options)
89
+ end
90
+ end
91
+
92
+ def build_namespace_attributes(klass, processed = {})
93
+ xml_mappings = klass.mappings_for(:xml)
94
+ attributes = klass.attributes
95
+
96
+ attrs = {}
97
+
98
+ if xml_mappings.namespace_prefix
99
+ attrs["xmlns:#{xml_mappings.namespace_prefix}"] =
100
+ xml_mappings.namespace_uri
101
+ end
102
+
103
+ xml_mappings.mappings.each do |mapping_rule|
104
+ processed[klass] ||= {}
105
+
106
+ next if processed[klass][mapping_rule.name]
107
+
108
+ processed[klass][mapping_rule.name] = true
109
+
110
+ type = if mapping_rule.delegate
111
+ attributes[mapping_rule.delegate].type.attributes[mapping_rule.to].type
112
+ else
113
+ attributes[mapping_rule.to].type
114
+ end
115
+
116
+ if type <= Lutaml::Model::Serialize
117
+ attrs = attrs.merge(build_namespace_attributes(type, processed))
118
+ end
119
+
120
+ if mapping_rule.namespace
121
+ attrs["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
122
+ end
123
+ end
124
+
125
+ attrs
126
+ end
127
+
38
128
  def build_attributes(element, xml_mapping)
39
129
  attrs = namespace_attributes(xml_mapping)
40
130
 
@@ -52,8 +142,18 @@ module Lutaml
52
142
  end
53
143
  end
54
144
  end
55
- # rubocop:enable Metrics/AbcSize
56
- # rubocop:enable Metrics/MethodLength
145
+
146
+ def attribute_definition_for(element, rule)
147
+ return element.class.attributes[rule.to] unless rule.delegate
148
+
149
+ element.send(rule.delegate).class.attributes[rule.to]
150
+ end
151
+
152
+ def attribute_value_for(element, rule)
153
+ return element.send(rule.to) unless rule.delegate
154
+
155
+ element.send(rule.delegate).send(rule.to)
156
+ end
57
157
 
58
158
  def namespace_attributes(xml_mapping)
59
159
  return {} unless xml_mapping.namespace_uri
@@ -70,7 +170,6 @@ module Lutaml
70
170
  :namespace_prefix,
71
171
  :parent_document
72
172
 
73
- # rubocop:disable Metrics/ParameterLists
74
173
  def initialize(
75
174
  name,
76
175
  attributes = {},
@@ -86,10 +185,9 @@ module Lutaml
86
185
  @text = text
87
186
  @parent_document = parent_document
88
187
  end
89
- # rubocop:enable Metrics/ParameterLists
90
188
 
91
189
  def name
92
- if namespace_prefix && namespaces[namespace_prefix]
190
+ if namespace_prefix
93
191
  "#{namespace_prefix}:#{@name}"
94
192
  else
95
193
  @name
@@ -144,6 +242,12 @@ module Lutaml
144
242
 
145
243
  n.first
146
244
  end
245
+
246
+ def order
247
+ children.each_with_object([]) do |child, arr|
248
+ arr << child.unprefixed_name
249
+ end
250
+ end
147
251
  end
148
252
 
149
253
  class Attribute
@@ -155,6 +259,14 @@ module Lutaml
155
259
  @namespace = namespace
156
260
  @namespace_prefix = namespace_prefix
157
261
  end
262
+
263
+ def unprefixed_name
264
+ if namespace_prefix
265
+ name.split(":").last
266
+ else
267
+ name
268
+ end
269
+ end
158
270
  end
159
271
  end
160
272
  end
@@ -4,16 +4,23 @@ require_relative "xml_mapping_rule"
4
4
  module Lutaml
5
5
  module Model
6
6
  class XmlMapping
7
- attr_reader :root_element, :namespace_uri, :namespace_prefix
7
+ attr_reader :root_element,
8
+ :namespace_uri,
9
+ :namespace_prefix,
10
+ :mixed_content
8
11
 
9
12
  def initialize
10
13
  @elements = []
11
14
  @attributes = []
12
15
  @content_mapping = nil
16
+ @mixed_content = false
13
17
  end
14
18
 
15
- def root(name)
19
+ alias mixed_content? mixed_content
20
+
21
+ def root(name, mixed: false)
16
22
  @root_element = name
23
+ @mixed_content = mixed
17
24
  end
18
25
 
19
26
  def prefixed_root
@@ -36,8 +43,10 @@ module Lutaml
36
43
  render_nil: false,
37
44
  with: {},
38
45
  delegate: nil,
39
- namespace: nil,
40
- prefix: nil
46
+ namespace: (namespace_set = false
47
+ nil),
48
+ prefix: nil,
49
+ mixed: false
41
50
  )
42
51
  @elements << XmlMappingRule.new(
43
52
  name,
@@ -47,6 +56,8 @@ module Lutaml
47
56
  delegate: delegate,
48
57
  namespace: namespace,
49
58
  prefix: prefix,
59
+ mixed_content: mixed,
60
+ namespace_set: namespace_set != false,
50
61
  )
51
62
  end
52
63
 
@@ -56,7 +67,8 @@ module Lutaml
56
67
  render_nil: false,
57
68
  with: {},
58
69
  delegate: nil,
59
- namespace: nil,
70
+ namespace: (namespace_set = false
71
+ nil),
60
72
  prefix: nil
61
73
  )
62
74
  @attributes << XmlMappingRule.new(
@@ -67,17 +79,25 @@ module Lutaml
67
79
  delegate: delegate,
68
80
  namespace: namespace,
69
81
  prefix: prefix,
82
+ namespace_set: namespace_set != false,
70
83
  )
71
84
  end
72
85
  # rubocop:enable Metrics/ParameterLists
73
86
 
74
- def map_content(to:, render_nil: false, with: {}, delegate: nil)
87
+ def map_content(
88
+ to:,
89
+ render_nil: false,
90
+ with: {},
91
+ delegate: nil,
92
+ mixed: false
93
+ )
75
94
  @content_mapping = XmlMappingRule.new(
76
95
  nil,
77
96
  to: to,
78
97
  render_nil: render_nil,
79
98
  with: with,
80
99
  delegate: delegate,
100
+ mixed_content: mixed,
81
101
  )
82
102
  end
83
103
 
@@ -108,6 +128,16 @@ module Lutaml
108
128
  name == rule.to
109
129
  end
110
130
  end
131
+
132
+ def find_by_name(name)
133
+ if name.to_s == "text"
134
+ content_mapping
135
+ else
136
+ mappings.detect do |rule|
137
+ rule.name == name.to_s || rule.name == name.to_sym
138
+ end
139
+ end
140
+ end
111
141
  end
112
142
  end
113
143
  end
@@ -6,8 +6,6 @@ module Lutaml
6
6
  class XmlMappingRule < MappingRule
7
7
  attr_reader :namespace, :prefix
8
8
 
9
- # rubocop:disable Metrics/MethodLength
10
- # rubocop:disable Metrics/ParameterLists
11
9
  def initialize(
12
10
  name,
13
11
  to:,
@@ -15,14 +13,18 @@ module Lutaml
15
13
  with: {},
16
14
  delegate: nil,
17
15
  namespace: nil,
18
- prefix: nil
16
+ prefix: nil,
17
+ mixed_content: false,
18
+ namespace_set: false
19
19
  )
20
20
  super(
21
21
  name,
22
22
  to: to,
23
23
  render_nil: render_nil,
24
24
  with: with,
25
- delegate: delegate
25
+ delegate: delegate,
26
+ mixed_content: mixed_content,
27
+ namespace_set: namespace_set
26
28
  )
27
29
 
28
30
  @namespace = if namespace.to_s == "inherit"
@@ -33,8 +35,6 @@ module Lutaml
33
35
  end
34
36
  @prefix = prefix
35
37
  end
36
- # rubocop:enable Metrics/MethodLength
37
- # rubocop:enable Metrics/ParameterLists
38
38
  end
39
39
  end
40
40
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-10 00:00:00.000000000 Z
11
+ date: 2024-08-02 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: 'LutaML creating data models in Ruby
14
14
 
@@ -24,6 +24,7 @@ files:
24
24
  - ".gitignore"
25
25
  - ".rspec"
26
26
  - ".rubocop.yml"
27
+ - ".rubocop_todo.yml"
27
28
  - CODE_OF_CONDUCT.md
28
29
  - Gemfile
29
30
  - README.adoc
@@ -38,6 +39,7 @@ files:
38
39
  - lib/lutaml/model/json_adapter/standard.rb
39
40
  - lib/lutaml/model/key_value_mapping.rb
40
41
  - lib/lutaml/model/key_value_mapping_rule.rb
42
+ - lib/lutaml/model/mapping_hash.rb
41
43
  - lib/lutaml/model/mapping_rule.rb
42
44
  - lib/lutaml/model/schema.rb
43
45
  - lib/lutaml/model/schema/json_schema.rb