lutaml-model 0.2.1 → 0.3.0

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