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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.rubocop.yml +7 -0
- data/.rubocop_todo.yml +207 -0
- data/Gemfile +6 -2
- data/README.adoc +1054 -126
- data/lib/lutaml/model/attribute.rb +4 -0
- data/lib/lutaml/model/error/invalid_value_error.rb +18 -0
- data/lib/lutaml/model/error.rb +8 -0
- data/lib/lutaml/model/key_value_mapping.rb +9 -1
- data/lib/lutaml/model/mapping_hash.rb +42 -0
- data/lib/lutaml/model/mapping_rule.rb +24 -2
- data/lib/lutaml/model/serialize.rb +270 -189
- data/lib/lutaml/model/toml_adapter/toml_rb_adapter.rb +2 -2
- data/lib/lutaml/model/type.rb +19 -13
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +104 -101
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +125 -85
- data/lib/lutaml/model/xml_adapter.rb +138 -7
- data/lib/lutaml/model/xml_mapping.rb +36 -6
- data/lib/lutaml/model/xml_mapping_rule.rb +6 -6
- data/lib/lutaml/model/yaml_adapter.rb +11 -3
- data/lib/lutaml/model.rb +7 -0
- metadata +6 -2
@@ -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
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
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 =
|
40
|
+
attributes = options[:xml_attributes] ||= {}
|
41
|
+
attributes = build_attributes(element,
|
42
|
+
xml_mapping).merge(attributes)&.compact
|
48
43
|
|
49
|
-
prefixed_xml = if
|
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.
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
136
|
-
|
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
|
-
|
140
|
-
|
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
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
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
|
-
|
46
|
-
|
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,
|
63
|
+
handle_nested_elements(el, v, rule: element_rule, attribute: attribute_def)
|
64
64
|
else
|
65
|
-
builder.element(element_rule.
|
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
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
-
|
101
|
-
|
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
|
-
|
115
|
-
|
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
|
-
|
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
|
125
|
-
namespace =
|
126
|
-
|
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
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
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
|
-
|
157
|
-
|
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.
|
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
|