lutaml-model 0.7.1 → 0.7.2

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.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/.rubocop_todo.yml +49 -48
  4. data/Gemfile +4 -1
  5. data/README.adoc +791 -143
  6. data/RELEASE_NOTES.adoc +346 -0
  7. data/docs/custom_adapters.adoc +144 -0
  8. data/lib/lutaml/model/attribute.rb +17 -11
  9. data/lib/lutaml/model/config.rb +48 -42
  10. data/lib/lutaml/model/error/polymorphic_error.rb +7 -2
  11. data/lib/lutaml/model/format_registry.rb +41 -0
  12. data/lib/lutaml/model/hash/document.rb +11 -0
  13. data/lib/lutaml/model/hash/mapping.rb +19 -0
  14. data/lib/lutaml/model/hash/mapping_rule.rb +9 -0
  15. data/lib/lutaml/model/hash/standard_adapter.rb +17 -0
  16. data/lib/lutaml/model/hash/transform.rb +8 -0
  17. data/lib/lutaml/model/hash.rb +21 -0
  18. data/lib/lutaml/model/json/document.rb +11 -0
  19. data/lib/lutaml/model/json/mapping.rb +19 -0
  20. data/lib/lutaml/model/json/mapping_rule.rb +9 -0
  21. data/lib/lutaml/model/{json_adapter → json}/multi_json_adapter.rb +4 -5
  22. data/lib/lutaml/model/{json_adapter/standard_json_adapter.rb → json/standard_adapter.rb} +5 -3
  23. data/lib/lutaml/model/json/transform.rb +8 -0
  24. data/lib/lutaml/model/json.rb +21 -0
  25. data/lib/lutaml/model/key_value_document.rb +27 -0
  26. data/lib/lutaml/model/mapping/key_value_mapping.rb +8 -4
  27. data/lib/lutaml/model/mapping/mapping.rb +13 -0
  28. data/lib/lutaml/model/mapping/mapping_rule.rb +7 -6
  29. data/lib/lutaml/model/serialization_adapter.rb +22 -0
  30. data/lib/lutaml/model/serialize.rb +146 -521
  31. data/lib/lutaml/model/services/logger.rb +54 -0
  32. data/lib/lutaml/model/services/transformer.rb +48 -0
  33. data/lib/lutaml/model/services.rb +2 -0
  34. data/lib/lutaml/model/toml/document.rb +11 -0
  35. data/lib/lutaml/model/toml/mapping.rb +27 -0
  36. data/lib/lutaml/model/toml/mapping_rule.rb +9 -0
  37. data/lib/lutaml/model/{toml_adapter → toml}/toml_rb_adapter.rb +3 -3
  38. data/lib/lutaml/model/toml/tomlib_adapter.rb +19 -0
  39. data/lib/lutaml/model/toml/transform.rb +8 -0
  40. data/lib/lutaml/model/toml.rb +30 -0
  41. data/lib/lutaml/model/transform/key_value_transform.rb +291 -0
  42. data/lib/lutaml/model/transform/xml_transform.rb +239 -0
  43. data/lib/lutaml/model/transform.rb +78 -0
  44. data/lib/lutaml/model/type/value.rb +6 -9
  45. data/lib/lutaml/model/uninitialized_class.rb +1 -1
  46. data/lib/lutaml/model/utils.rb +30 -0
  47. data/lib/lutaml/model/version.rb +1 -1
  48. data/lib/lutaml/model/{xml_adapter → xml}/builder/nokogiri.rb +2 -2
  49. data/lib/lutaml/model/{xml_adapter → xml}/builder/oga.rb +10 -10
  50. data/lib/lutaml/model/{xml_adapter → xml}/builder/ox.rb +1 -1
  51. data/lib/lutaml/model/{xml_adapter/xml_document.rb → xml/document.rb} +6 -7
  52. data/lib/lutaml/model/xml/element.rb +32 -0
  53. data/lib/lutaml/model/xml/mapping.rb +410 -0
  54. data/lib/lutaml/model/xml/mapping_rule.rb +141 -0
  55. data/lib/lutaml/model/xml/nokogiri_adapter.rb +232 -0
  56. data/lib/lutaml/model/{xml_adapter → xml}/oga/document.rb +1 -1
  57. data/lib/lutaml/model/{xml_adapter → xml}/oga/element.rb +3 -1
  58. data/lib/lutaml/model/xml/oga_adapter.rb +171 -0
  59. data/lib/lutaml/model/xml/ox_adapter.rb +215 -0
  60. data/lib/lutaml/model/xml/transform.rb +8 -0
  61. data/lib/lutaml/model/{xml_adapter → xml}/xml_attribute.rb +1 -1
  62. data/lib/lutaml/model/{xml_adapter → xml}/xml_element.rb +6 -3
  63. data/lib/lutaml/model/{xml_adapter → xml}/xml_namespace.rb +1 -1
  64. data/lib/lutaml/model/xml.rb +31 -0
  65. data/lib/lutaml/model/xml_adapter/element.rb +11 -25
  66. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +6 -223
  67. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +13 -163
  68. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +10 -207
  69. data/lib/lutaml/model/yaml/document.rb +10 -0
  70. data/lib/lutaml/model/yaml/mapping.rb +19 -0
  71. data/lib/lutaml/model/yaml/mapping_rule.rb +9 -0
  72. data/lib/lutaml/model/{yaml_adapter/standard_yaml_adapter.rb → yaml/standard_adapter.rb} +4 -3
  73. data/lib/lutaml/model/yaml/transform.rb +8 -0
  74. data/lib/lutaml/model/yaml.rb +21 -0
  75. data/lib/lutaml/model.rb +39 -4
  76. data/lutaml-model.gemspec +0 -4
  77. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +4 -4
  78. data/spec/lutaml/model/cdata_spec.rb +7 -7
  79. data/spec/lutaml/model/custom_bibtex_adapter_spec.rb +598 -0
  80. data/spec/lutaml/model/custom_vobject_adapter_spec.rb +1226 -0
  81. data/spec/lutaml/model/group_spec.rb +18 -7
  82. data/spec/lutaml/model/hash/adapter_spec.rb +255 -0
  83. data/spec/lutaml/model/json_adapter_spec.rb +6 -6
  84. data/spec/lutaml/model/key_value_mapping_spec.rb +25 -1
  85. data/spec/lutaml/model/mixed_content_spec.rb +24 -24
  86. data/spec/lutaml/model/multiple_mapping_spec.rb +5 -5
  87. data/spec/lutaml/model/ordered_content_spec.rb +6 -6
  88. data/spec/lutaml/model/polymorphic_spec.rb +178 -0
  89. data/spec/lutaml/model/root_mappings_spec.rb +3 -3
  90. data/spec/lutaml/model/schema/xml_compiler_spec.rb +6 -6
  91. data/spec/lutaml/model/serializable_spec.rb +179 -103
  92. data/spec/lutaml/model/toml_adapter_spec.rb +6 -6
  93. data/spec/lutaml/model/toml_spec.rb +51 -0
  94. data/spec/lutaml/model/transformation_spec.rb +72 -15
  95. data/spec/lutaml/model/uninitialized_class_spec.rb +96 -0
  96. data/spec/lutaml/model/xml/namespace_spec.rb +57 -0
  97. data/spec/lutaml/model/xml/xml_element_spec.rb +1 -1
  98. data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +2 -2
  99. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +2 -2
  100. data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +2 -2
  101. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +6 -6
  102. data/spec/lutaml/model/xml_adapter_spec.rb +6 -6
  103. data/spec/lutaml/model/xml_mapping_rule_spec.rb +3 -3
  104. data/spec/lutaml/model/xml_mapping_spec.rb +26 -14
  105. data/spec/lutaml/model/xml_spec.rb +63 -0
  106. data/spec/lutaml/model/yaml_adapter_spec.rb +3 -5
  107. data/spec/spec_helper.rb +3 -3
  108. metadata +64 -59
  109. data/lib/lutaml/model/json_adapter/json_document.rb +0 -20
  110. data/lib/lutaml/model/json_adapter/json_object.rb +0 -28
  111. data/lib/lutaml/model/loggable.rb +0 -15
  112. data/lib/lutaml/model/mapping/json_mapping.rb +0 -17
  113. data/lib/lutaml/model/mapping/toml_mapping.rb +0 -25
  114. data/lib/lutaml/model/mapping/xml_mapping.rb +0 -389
  115. data/lib/lutaml/model/mapping/xml_mapping_rule.rb +0 -139
  116. data/lib/lutaml/model/mapping/yaml_mapping.rb +0 -17
  117. data/lib/lutaml/model/mapping.rb +0 -14
  118. data/lib/lutaml/model/toml_adapter/toml_document.rb +0 -20
  119. data/lib/lutaml/model/toml_adapter/toml_object.rb +0 -28
  120. data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +0 -20
  121. data/lib/lutaml/model/toml_adapter.rb +0 -6
  122. data/lib/lutaml/model/yaml_adapter/yaml_document.rb +0 -20
  123. data/lib/lutaml/model/yaml_adapter/yaml_object.rb +0 -28
  124. data/lib/lutaml/model/yaml_adapter.rb +0 -8
@@ -0,0 +1,232 @@
1
+ require "nokogiri"
2
+ require_relative "document"
3
+ require_relative "builder/nokogiri"
4
+
5
+ module Lutaml
6
+ module Model
7
+ module Xml
8
+ class NokogiriAdapter < Document
9
+ def self.parse(xml, options = {})
10
+ parsed = Nokogiri::XML(xml, nil, encoding(xml, options))
11
+ @root = NokogiriElement.new(parsed.root)
12
+ new(@root, parsed.encoding)
13
+ end
14
+
15
+ def to_xml(options = {})
16
+ builder_options = {}
17
+
18
+ if options.key?(:encoding)
19
+ builder_options[:encoding] = options[:encoding] unless options[:encoding].nil?
20
+ elsif options.key?(:parse_encoding)
21
+ builder_options[:encoding] = options[:parse_encoding]
22
+ else
23
+ builder_options[:encoding] = "UTF-8"
24
+ end
25
+
26
+ builder = Builder::Nokogiri.build(builder_options) do |xml|
27
+ if root.is_a?(Lutaml::Model::Xml::NokogiriElement)
28
+ root.build_xml(xml)
29
+ else
30
+ mapper_class = options[:mapper_class] || @root.class
31
+ options[:xml_attributes] =
32
+ build_namespace_attributes(mapper_class)
33
+ build_element(xml, @root, options)
34
+ end
35
+ end
36
+
37
+ xml_options = {}
38
+ xml_options[:indent] = 2 if options[:pretty]
39
+
40
+ xml_data = builder.doc.root.to_xml(xml_options)
41
+ options[:declaration] ? declaration(options) + xml_data : xml_data
42
+ end
43
+
44
+ private
45
+
46
+ def prefix_xml(xml, mapping, options)
47
+ if options.key?(:namespace_prefix)
48
+ xml[options[:namespace_prefix]] if options[:namespace_prefix]
49
+ elsif mapping.namespace_prefix
50
+ xml[mapping.namespace_prefix]
51
+ end
52
+ xml
53
+ end
54
+
55
+ def build_ordered_element(xml, element, options = {})
56
+ mapper_class = determine_mapper_class(element, options)
57
+ xml_mapping = mapper_class.mappings_for(:xml)
58
+ return xml unless xml_mapping
59
+
60
+ attributes = build_attributes(element, xml_mapping)&.compact
61
+
62
+ prefixed_xml = prefix_xml(xml, xml_mapping, options)
63
+
64
+ tag_name = options[:tag_name] || xml_mapping.root_element
65
+ tag_name = "#{tag_name}_" if prefixed_xml.respond_to?(tag_name)
66
+ prefixed_xml.public_send(tag_name, attributes) do
67
+ if options.key?(:namespace_prefix) && !options[:namespace_prefix]
68
+ xml.parent.namespace = nil
69
+ end
70
+
71
+ index_hash = {}
72
+ content = []
73
+
74
+ element.element_order.each do |object|
75
+ index_hash[object.name] ||= -1
76
+ curr_index = index_hash[object.name] += 1
77
+
78
+ element_rule = xml_mapping.find_by_name(object.name)
79
+ next if element_rule.nil?
80
+
81
+ attribute_def = attribute_definition_for(element, element_rule,
82
+ mapper_class: mapper_class)
83
+ value = attribute_value_for(element, element_rule)
84
+
85
+ if element_rule == xml_mapping.content_mapping
86
+ next if element_rule.cdata && object.text?
87
+
88
+ text = xml_mapping.content_mapping.serialize(element)
89
+ text = text[curr_index] if text.is_a?(Array)
90
+
91
+ next prefixed_xml.add_text(xml, text, cdata: element_rule.cdata) if element.mixed?
92
+
93
+ content << text
94
+ elsif !value.nil? || element_rule.render_nil?
95
+ value = value[curr_index] if attribute_def.collection?
96
+
97
+ add_to_xml(
98
+ xml,
99
+ element,
100
+ element_rule.prefix,
101
+ value,
102
+ options.merge(
103
+ attribute: attribute_def,
104
+ rule: element_rule,
105
+ mapper_class: mapper_class,
106
+ ),
107
+ )
108
+ end
109
+ end
110
+
111
+ prefixed_xml.text content.join
112
+ end
113
+ end
114
+ end
115
+
116
+ class NokogiriElement < XmlElement
117
+ def initialize(node, root_node: nil, default_namespace: nil)
118
+ if root_node
119
+ node.namespaces.each do |prefix, name|
120
+ namespace = XmlNamespace.new(name, prefix)
121
+
122
+ root_node.add_namespace(namespace)
123
+ end
124
+ end
125
+
126
+ attributes = {}
127
+
128
+ # Using `attribute_nodes` instead of `attributes` because
129
+ # `attribute_nodes` handles name collisions as well
130
+ # More info: https://devdocs.io/nokogiri/nokogiri/xml/node#method-i-attributes
131
+ node.attribute_nodes.each do |attr|
132
+ name = if attr.namespace
133
+ "#{attr.namespace.prefix}:#{attr.name}"
134
+ else
135
+ attr.name
136
+ end
137
+
138
+ attributes[name] = XmlAttribute.new(
139
+ name,
140
+ attr.value,
141
+ namespace: attr.namespace&.href,
142
+ namespace_prefix: attr.namespace&.prefix,
143
+ )
144
+ end
145
+
146
+ if root_node.nil? && !node.namespace&.prefix
147
+ default_namespace = node.namespace&.href
148
+ end
149
+
150
+ super(
151
+ node,
152
+ attributes,
153
+ parse_all_children(node, root_node: root_node || self,
154
+ default_namespace: default_namespace),
155
+ node.text,
156
+ parent_document: root_node,
157
+ namespace_prefix: node.namespace&.prefix,
158
+ default_namespace: default_namespace
159
+ )
160
+ end
161
+
162
+ def text?
163
+ # false
164
+ children.empty? && text.length.positive?
165
+ end
166
+
167
+ def to_xml
168
+ return text if text?
169
+
170
+ build_xml.doc.root.to_xml
171
+ end
172
+
173
+ def inner_xml
174
+ children.map(&:to_xml).join
175
+ end
176
+
177
+ def build_xml(builder = nil)
178
+ builder ||= Builder::Nokogiri.build
179
+
180
+ if name == "text"
181
+ builder.text(text)
182
+ else
183
+ builder.public_send(name, build_attributes(self)) do |xml|
184
+ children.each do |child|
185
+ child.build_xml(xml)
186
+ end
187
+ end
188
+ end
189
+
190
+ builder
191
+ end
192
+
193
+ private
194
+
195
+ def parse_children(node, root_node: nil)
196
+ node.children.select(&:element?).map do |child|
197
+ NokogiriElement.new(child, root_node: root_node)
198
+ end
199
+ end
200
+
201
+ def parse_all_children(node, root_node: nil, default_namespace: nil)
202
+ node.children.map do |child|
203
+ NokogiriElement.new(child, root_node: root_node,
204
+ default_namespace: default_namespace)
205
+ end
206
+ end
207
+
208
+ def build_attributes(node, _options = {})
209
+ attrs = node.attributes.transform_values(&:value)
210
+
211
+ attrs.merge(build_namespace_attributes(node))
212
+ end
213
+
214
+ def build_namespace_attributes(node)
215
+ namespace_attrs = {}
216
+
217
+ node.own_namespaces.each_value do |namespace|
218
+ namespace_attrs[namespace.attr_name] = namespace.uri
219
+ end
220
+
221
+ node.children.each do |child|
222
+ namespace_attrs = namespace_attrs.merge(
223
+ build_namespace_attributes(child),
224
+ )
225
+ end
226
+
227
+ namespace_attrs
228
+ end
229
+ end
230
+ end
231
+ end
232
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- module XmlAdapter
5
+ module Xml
6
6
  module Oga
7
7
  class Document < ::Oga::XML::Document
8
8
  def initialize(options = {})
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../xml_element"
4
+
3
5
  module Lutaml
4
6
  module Model
5
- module XmlAdapter
7
+ module Xml
6
8
  module Oga
7
9
  class Element < XmlElement
8
10
  def initialize(node, parent: nil)
@@ -0,0 +1,171 @@
1
+ require "oga"
2
+ require "moxml/adapter/oga"
3
+ require_relative "document"
4
+ require_relative "oga/document"
5
+ require_relative "oga/element"
6
+ require_relative "builder/oga"
7
+
8
+ module Lutaml
9
+ module Model
10
+ module Xml
11
+ class OgaAdapter < Document
12
+ TEXT_CLASSES = [Moxml::Text, Moxml::Cdata].freeze
13
+
14
+ def self.parse(xml, options = {})
15
+ parsed = Moxml::Adapter::Oga.parse(xml)
16
+ @root = Oga::Element.new(parsed.children.first)
17
+ new(@root, encoding(xml, options))
18
+ end
19
+
20
+ def to_xml(options = {})
21
+ builder_options = {}
22
+ builder_options[:encoding] = if options.key?(:encoding)
23
+ options[:encoding]
24
+ elsif options.key?(:parse_encoding)
25
+ options[:parse_encoding]
26
+ else
27
+ "UTF-8"
28
+ end
29
+
30
+ builder = Builder::Oga.build(builder_options) do |xml|
31
+ if @root.is_a?(Oga::Element)
32
+ @root.build_xml(xml)
33
+ else
34
+ build_element(xml, @root, options)
35
+ end
36
+ end
37
+ xml_data = builder.to_xml
38
+ options[:declaration] ? declaration(options) + xml_data : xml_data
39
+ end
40
+
41
+ def attributes_hash(element)
42
+ result = Lutaml::Model::MappingHash.new
43
+
44
+ element.attributes.each do |attr|
45
+ if attr.name == "schemaLocation"
46
+ result["__schema_location"] = {
47
+ namespace: attr.namespace,
48
+ prefix: attr.namespace.prefix,
49
+ schema_location: attr.value,
50
+ }
51
+ else
52
+ result[self.class.namespaced_attr_name(attr)] = attr.value
53
+ end
54
+ end
55
+
56
+ result
57
+ end
58
+
59
+ def self.name_of(element)
60
+ case element
61
+ when Moxml::Text
62
+ "text"
63
+ when Moxml::Cdata
64
+ "cdata"
65
+ else
66
+ element.name
67
+ end
68
+ end
69
+
70
+ def self.prefixed_name_of(node)
71
+ return name_of(node) if TEXT_CLASSES.include?(node.class)
72
+
73
+ [node&.namespace&.prefix, node.name].compact.join(":")
74
+ end
75
+
76
+ def self.text_of(element)
77
+ element.content
78
+ end
79
+
80
+ def self.namespaced_attr_name(attribute)
81
+ attr_ns = attribute.namespace
82
+ attr_name = attribute.name
83
+ return attr_name unless attr_ns
84
+
85
+ prefix = attr_name == "lang" ? attr_ns.prefix : attr_ns.uri
86
+ [prefix, attr_name].compact.join(":")
87
+ end
88
+
89
+ def self.namespaced_name_of(node)
90
+ return name_of(node) unless node.respond_to?(:namespace)
91
+
92
+ [node&.namespace&.uri, node.name].compact.join(":")
93
+ end
94
+
95
+ def order
96
+ children.map do |child|
97
+ type = child.text? ? "Text" : "Element"
98
+ Element.new(type, child.unprefixed_name)
99
+ end
100
+ end
101
+
102
+ def self.order_of(element)
103
+ element.children.map do |child|
104
+ instance_args = if TEXT_CLASSES.include?(child.class)
105
+ ["Text", "text"]
106
+ else
107
+ ["Element", name_of(child)]
108
+ end
109
+ Element.new(*instance_args)
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ def build_ordered_element(builder, element, options = {})
116
+ mapper_class = determine_mapper_class(element, options)
117
+ xml_mapping = mapper_class.mappings_for(:xml)
118
+ return xml unless xml_mapping
119
+
120
+ attributes = build_attributes(element, xml_mapping).compact
121
+
122
+ tag_name = options[:tag_name] || xml_mapping.root_element
123
+ builder.create_and_add_element(tag_name,
124
+ attributes: attributes) do |el|
125
+ index_hash = {}
126
+ content = []
127
+
128
+ element.element_order.each do |object|
129
+ index_hash[object.name] ||= -1
130
+ curr_index = index_hash[object.name] += 1
131
+
132
+ element_rule = xml_mapping.find_by_name(object.name)
133
+ next if element_rule.nil?
134
+
135
+ attribute_def = attribute_definition_for(element, element_rule,
136
+ mapper_class: mapper_class)
137
+ value = attribute_value_for(element, element_rule)
138
+
139
+ next if element_rule == xml_mapping.content_mapping && element_rule.cdata && object.text?
140
+
141
+ if element_rule == xml_mapping.content_mapping
142
+ text = xml_mapping.content_mapping.serialize(element)
143
+ text = text[curr_index] if text.is_a?(Array)
144
+
145
+ next el.add_text(el, text, cdata: element_rule.cdata) if element.mixed?
146
+
147
+ content << text
148
+ elsif !value.nil? || element_rule.render_nil?
149
+ value = value[curr_index] if attribute_def.collection?
150
+
151
+ add_to_xml(
152
+ el,
153
+ element,
154
+ nil,
155
+ value,
156
+ options.merge(
157
+ attribute: attribute_def,
158
+ rule: element_rule,
159
+ mapper_class: mapper_class,
160
+ ),
161
+ )
162
+ end
163
+ end
164
+
165
+ el.add_text(el, content.join)
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,215 @@
1
+ require "ox"
2
+ require_relative "document"
3
+ require_relative "builder/ox"
4
+
5
+ module Lutaml
6
+ module Model
7
+ module Xml
8
+ class OxAdapter < Document
9
+ def self.parse(xml, options = {})
10
+ Ox.default_options = Ox.default_options.merge(encoding: encoding(xml, options))
11
+
12
+ parsed = Ox.parse(xml)
13
+ @root = OxElement.new(parsed)
14
+ new(@root, Ox.default_options[:encoding])
15
+ end
16
+
17
+ def to_xml(options = {})
18
+ builder_options = { version: options[:version] }
19
+
20
+ builder_options[:encoding] = if options.key?(:encoding)
21
+ options[:encoding] unless options[:encoding].nil?
22
+ elsif options.key?(:parse_encoding)
23
+ options[:parse_encoding]
24
+ else
25
+ "UTF-8"
26
+ end
27
+
28
+ builder = Builder::Ox.build(builder_options)
29
+ builder.xml.instruct(:xml, encoding: builder_options[:encoding])
30
+
31
+ if @root.is_a?(Lutaml::Model::Xml::OxElement)
32
+ @root.build_xml(builder)
33
+ elsif ordered?(@root, options)
34
+ build_ordered_element(builder, @root, options)
35
+ else
36
+ mapper_class = options[:mapper_class] || @root.class
37
+ options[:xml_attributes] = build_namespace_attributes(mapper_class)
38
+ build_element(builder, @root, options)
39
+ end
40
+
41
+ xml_data = builder.xml.to_s
42
+ stripped_data = xml_data.lines.drop(1).join
43
+ options[:declaration] ? declaration(options) + stripped_data : stripped_data
44
+ end
45
+
46
+ private
47
+
48
+ def build_ordered_element(builder, element, options = {})
49
+ mapper_class = determine_mapper_class(element, options)
50
+ xml_mapping = mapper_class.mappings_for(:xml)
51
+ return xml unless xml_mapping
52
+
53
+ attributes = build_attributes(element, xml_mapping).compact
54
+
55
+ tag_name = options[:tag_name] || xml_mapping.root_element
56
+ builder.create_and_add_element(tag_name,
57
+ attributes: attributes) do |el|
58
+ index_hash = {}
59
+ content = []
60
+
61
+ element.element_order.each do |object|
62
+ index_hash[object.name] ||= -1
63
+ curr_index = index_hash[object.name] += 1
64
+
65
+ element_rule = xml_mapping.find_by_name(object.name)
66
+ next if element_rule.nil?
67
+
68
+ attribute_def = attribute_definition_for(element, element_rule,
69
+ mapper_class: mapper_class)
70
+ value = attribute_value_for(element, element_rule)
71
+
72
+ next if element_rule == xml_mapping.content_mapping && element_rule.cdata && object.text?
73
+
74
+ if element_rule == xml_mapping.content_mapping
75
+ text = element.send(xml_mapping.content_mapping.to)
76
+ text = text[curr_index] if text.is_a?(Array)
77
+
78
+ next el.add_text(el, text, cdata: element_rule.cdata) if element.mixed?
79
+
80
+ content << text
81
+ elsif !value.nil? || element_rule.render_nil?
82
+ value = value[curr_index] if attribute_def.collection?
83
+
84
+ add_to_xml(
85
+ el,
86
+ element,
87
+ nil,
88
+ value,
89
+ options.merge(
90
+ attribute: attribute_def,
91
+ rule: element_rule,
92
+ ),
93
+ )
94
+ end
95
+ end
96
+
97
+ el.add_text(el, content.join)
98
+ end
99
+ end
100
+ end
101
+
102
+ class OxElement < XmlElement
103
+ def initialize(node, root_node: nil)
104
+ case node
105
+ when String
106
+ super("text", {}, [], node, parent_document: root_node)
107
+ when Ox::Comment
108
+ super("comment", {}, [], node.value, parent_document: root_node)
109
+ when Ox::CData
110
+ super("#cdata-section", {}, [], node.value, parent_document: root_node)
111
+ else
112
+ namespace_attributes(node.attributes).each do |(name, value)|
113
+ if root_node
114
+ root_node.add_namespace(XmlNamespace.new(value, name))
115
+ else
116
+ add_namespace(XmlNamespace.new(value, name))
117
+ end
118
+ end
119
+
120
+ attributes = node.attributes.each_with_object({}) do |(name, value), hash|
121
+ next if attribute_is_namespace?(name)
122
+
123
+ namespace_prefix = name.to_s.split(":").first
124
+ if (n = name.to_s.split(":")).length > 1
125
+ namespace = (root_node || self).namespaces[namespace_prefix]&.uri
126
+ namespace ||= XML_NAMESPACE_URI
127
+ prefix = n.first
128
+ end
129
+
130
+ hash[name.to_s] = XmlAttribute.new(
131
+ name.to_s,
132
+ value,
133
+ namespace: namespace,
134
+ namespace_prefix: prefix,
135
+ )
136
+ end
137
+
138
+ super(
139
+ node,
140
+ attributes,
141
+ parse_children(node, root_node: root_node || self),
142
+ node.text,
143
+ parent_document: root_node,
144
+ )
145
+ end
146
+ end
147
+
148
+ def to_xml
149
+ return text if text?
150
+
151
+ build_xml.xml.to_s
152
+ end
153
+
154
+ def inner_xml
155
+ # Ox builder by default, adds a newline at the end, so `chomp` is used
156
+ children.map { |child| child.to_xml.chomp }.join
157
+ end
158
+
159
+ def build_xml(builder = nil)
160
+ builder ||= Builder::Ox.build
161
+ attrs = build_attributes(self)
162
+
163
+ if text?
164
+ builder.add_text(builder, text)
165
+ else
166
+ builder.create_and_add_element(name, attributes: attrs) do |el|
167
+ children.each { |child| child.build_xml(el) }
168
+ end
169
+ end
170
+
171
+ builder
172
+ end
173
+
174
+ def namespace_attributes(attributes)
175
+ attributes.select { |attr| attribute_is_namespace?(attr) }
176
+ end
177
+
178
+ def text?
179
+ # false
180
+ children.empty? && text&.length&.positive?
181
+ end
182
+
183
+ def build_attributes(node)
184
+ attrs = node.attributes.transform_values(&:value)
185
+
186
+ node.own_namespaces.each_value do |namespace|
187
+ attrs[namespace.attr_name] = namespace.uri
188
+ end
189
+
190
+ attrs
191
+ end
192
+
193
+ def nodes
194
+ children
195
+ end
196
+
197
+ def cdata
198
+ super || cdata_children.first&.text
199
+ end
200
+
201
+ def text
202
+ super || cdata
203
+ end
204
+
205
+ private
206
+
207
+ def parse_children(node, root_node: nil)
208
+ node.nodes.map do |child|
209
+ OxElement.new(child, root_node: root_node)
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,8 @@
1
+ module Lutaml
2
+ module Model
3
+ module Xml
4
+ class Transform < Lutaml::Model::XmlTransform
5
+ end
6
+ end
7
+ end
8
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- module XmlAdapter
5
+ module Xml
6
6
  # Represents an XML attribute
7
7
  class XmlAttribute
8
8
  attr_reader :name, :value, :namespace, :namespace_prefix
@@ -1,9 +1,12 @@
1
1
  require_relative "xml_attribute"
2
+ require_relative "document"
2
3
 
3
4
  module Lutaml
4
5
  module Model
5
- module XmlAdapter
6
+ module Xml
6
7
  class XmlElement
8
+ XML_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace".freeze
9
+
7
10
  attr_reader :attributes,
8
11
  :children,
9
12
  :namespace_prefix,
@@ -59,7 +62,7 @@ module Lutaml
59
62
  end
60
63
 
61
64
  def document
62
- XmlDocument.new(self)
65
+ Document.new(self)
63
66
  end
64
67
 
65
68
  def namespaces
@@ -118,7 +121,7 @@ module Lutaml
118
121
  def order
119
122
  children.map do |child|
120
123
  type = child.text? ? "Text" : "Element"
121
- Lutaml::Model::XmlAdapter::Element.new(type, child.unprefixed_name)
124
+ Lutaml::Model::Xml::Element.new(type, child.unprefixed_name)
122
125
  end
123
126
  end
124
127
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- module XmlAdapter
5
+ module Xml
6
6
  class XmlNamespace
7
7
  # Return name
8
8
  #