lutaml-model 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-tests.yml +2 -0
  3. data/.rubocop_todo.yml +86 -23
  4. data/Gemfile +2 -0
  5. data/README.adoc +1441 -220
  6. data/lib/lutaml/model/attribute.rb +33 -10
  7. data/lib/lutaml/model/choice.rb +56 -0
  8. data/lib/lutaml/model/config.rb +1 -0
  9. data/lib/lutaml/model/constants.rb +7 -0
  10. data/lib/lutaml/model/error/choice_lower_bound_error.rb +9 -0
  11. data/lib/lutaml/model/error/choice_upper_bound_error.rb +9 -0
  12. data/lib/lutaml/model/error/import_model_with_root_error.rb +9 -0
  13. data/lib/lutaml/model/error/incorrect_sequence_error.rb +9 -0
  14. data/lib/lutaml/model/error/invalid_choice_range_error.rb +20 -0
  15. data/lib/lutaml/model/error/no_root_mapping_error.rb +9 -0
  16. data/lib/lutaml/model/error/no_root_namespace_error.rb +9 -0
  17. data/lib/lutaml/model/error/type/invalid_value_error.rb +19 -0
  18. data/lib/lutaml/model/error/unknown_sequence_mapping_error.rb +9 -0
  19. data/lib/lutaml/model/error.rb +9 -0
  20. data/lib/lutaml/model/json_adapter/standard_json_adapter.rb +6 -1
  21. data/lib/lutaml/model/key_value_mapping.rb +34 -3
  22. data/lib/lutaml/model/key_value_mapping_rule.rb +4 -2
  23. data/lib/lutaml/model/liquefiable.rb +59 -0
  24. data/lib/lutaml/model/mapping_hash.rb +9 -1
  25. data/lib/lutaml/model/mapping_rule.rb +19 -2
  26. data/lib/lutaml/model/schema/templates/simple_type.rb +247 -0
  27. data/lib/lutaml/model/schema/xml_compiler.rb +762 -0
  28. data/lib/lutaml/model/schema.rb +5 -0
  29. data/lib/lutaml/model/schema_location.rb +7 -0
  30. data/lib/lutaml/model/sequence.rb +71 -0
  31. data/lib/lutaml/model/serialize.rb +139 -33
  32. data/lib/lutaml/model/toml_adapter/toml_rb_adapter.rb +1 -2
  33. data/lib/lutaml/model/type/decimal.rb +0 -4
  34. data/lib/lutaml/model/type/hash.rb +11 -11
  35. data/lib/lutaml/model/type/time.rb +3 -3
  36. data/lib/lutaml/model/utils.rb +19 -15
  37. data/lib/lutaml/model/validation.rb +12 -1
  38. data/lib/lutaml/model/version.rb +1 -1
  39. data/lib/lutaml/model/xml_adapter/builder/oga.rb +10 -7
  40. data/lib/lutaml/model/xml_adapter/builder/ox.rb +20 -13
  41. data/lib/lutaml/model/xml_adapter/element.rb +32 -0
  42. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +13 -9
  43. data/lib/lutaml/model/xml_adapter/oga/element.rb +14 -13
  44. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +86 -19
  45. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +19 -15
  46. data/lib/lutaml/model/xml_adapter/xml_document.rb +82 -25
  47. data/lib/lutaml/model/xml_adapter/xml_element.rb +57 -3
  48. data/lib/lutaml/model/xml_mapping.rb +53 -9
  49. data/lib/lutaml/model/xml_mapping_rule.rb +8 -6
  50. data/lib/lutaml/model.rb +2 -0
  51. data/lutaml-model.gemspec +5 -0
  52. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +75 -0
  53. data/spec/ceramic_spec.rb +39 -0
  54. data/spec/fixtures/ceramic.rb +23 -0
  55. data/spec/fixtures/xml/address_example_260.xsd +9 -0
  56. data/spec/fixtures/xml/invalid_math_document.xml +4 -0
  57. data/spec/fixtures/xml/math_document_schema.xsd +56 -0
  58. data/spec/fixtures/xml/test_schema.xsd +53 -0
  59. data/spec/fixtures/xml/user.xsd +10 -0
  60. data/spec/fixtures/xml/valid_math_document.xml +4 -0
  61. data/spec/lutaml/model/cdata_spec.rb +4 -5
  62. data/spec/lutaml/model/choice_spec.rb +168 -0
  63. data/spec/lutaml/model/collection_spec.rb +1 -1
  64. data/spec/lutaml/model/custom_model_spec.rb +7 -21
  65. data/spec/lutaml/model/custom_serialization_spec.rb +74 -2
  66. data/spec/lutaml/model/defaults_spec.rb +3 -1
  67. data/spec/lutaml/model/delegation_spec.rb +7 -5
  68. data/spec/lutaml/model/enum_spec.rb +35 -0
  69. data/spec/lutaml/model/group_spec.rb +160 -0
  70. data/spec/lutaml/model/inheritance_spec.rb +25 -0
  71. data/spec/lutaml/model/key_value_mapping_spec.rb +27 -0
  72. data/spec/lutaml/model/liquefiable_spec.rb +121 -0
  73. data/spec/lutaml/model/map_all_spec.rb +188 -0
  74. data/spec/lutaml/model/mixed_content_spec.rb +95 -56
  75. data/spec/lutaml/model/multiple_mapping_spec.rb +22 -10
  76. data/spec/lutaml/model/schema/xml_compiler_spec.rb +1624 -0
  77. data/spec/lutaml/model/sequence_spec.rb +216 -0
  78. data/spec/lutaml/model/transformation_spec.rb +230 -0
  79. data/spec/lutaml/model/type_spec.rb +138 -31
  80. data/spec/lutaml/model/utils_spec.rb +32 -0
  81. data/spec/lutaml/model/with_child_mapping_spec.rb +2 -2
  82. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +11 -7
  83. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +52 -0
  84. data/spec/lutaml/model/xml_mapping_rule_spec.rb +51 -0
  85. data/spec/lutaml/model/xml_mapping_spec.rb +250 -112
  86. metadata +77 -2
@@ -0,0 +1,32 @@
1
+ module Lutaml
2
+ module Model
3
+ module XmlAdapter
4
+ class Element
5
+ attr_reader :type, :name
6
+
7
+ def initialize(type, name)
8
+ @type = type
9
+ @name = name
10
+ end
11
+
12
+ def text?
13
+ @type == "Text" && @name != "#cdata-section"
14
+ end
15
+
16
+ def element_tag
17
+ @name unless text?
18
+ end
19
+
20
+ def eql?(other)
21
+ return false unless other.is_a?(self.class)
22
+
23
+ instance_variables.all? do |var|
24
+ instance_variable_get(var) == other.instance_variable_get(var)
25
+ end
26
+ end
27
+
28
+ alias == eql?
29
+ end
30
+ end
31
+ end
32
+ end
@@ -7,9 +7,9 @@ module Lutaml
7
7
  module XmlAdapter
8
8
  class NokogiriAdapter < XmlDocument
9
9
  def self.parse(xml, options = {})
10
- parsed = Nokogiri::XML(xml, nil, options[:encoding])
11
- root = NokogiriElement.new(parsed.root)
12
- new(root, parsed.encoding)
10
+ parsed = Nokogiri::XML(xml, nil, encoding(xml, options))
11
+ @root = NokogiriElement.new(parsed.root)
12
+ new(@root, parsed.encoding)
13
13
  end
14
14
 
15
15
  def to_xml(options = {})
@@ -71,11 +71,11 @@ module Lutaml
71
71
  index_hash = {}
72
72
  content = []
73
73
 
74
- element.element_order.each do |name|
75
- index_hash[name] ||= -1
76
- curr_index = index_hash[name] += 1
74
+ element.element_order.each do |object|
75
+ index_hash[object.name] ||= -1
76
+ curr_index = index_hash[object.name] += 1
77
77
 
78
- element_rule = xml_mapping.find_by_name(name)
78
+ element_rule = xml_mapping.find_by_name(object.name)
79
79
  next if element_rule.nil?
80
80
 
81
81
  attribute_def = attribute_definition_for(element, element_rule,
@@ -83,7 +83,7 @@ module Lutaml
83
83
  value = attribute_value_for(element, element_rule)
84
84
 
85
85
  if element_rule == xml_mapping.content_mapping
86
- next if element_rule.cdata && name == "text"
86
+ next if element_rule.cdata && object.text?
87
87
 
88
88
  text = xml_mapping.content_mapping.serialize(element)
89
89
  text = text[curr_index] if text.is_a?(Array)
@@ -124,7 +124,11 @@ module Lutaml
124
124
  end
125
125
 
126
126
  attributes = {}
127
- node.attributes.transform_values do |attr|
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|
128
132
  name = if attr.namespace
129
133
  "#{attr.namespace.prefix}:#{attr.name}"
130
134
  else
@@ -6,21 +6,22 @@ module Lutaml
6
6
  module Oga
7
7
  class Element < XmlElement
8
8
  def initialize(node, parent: nil)
9
- name = case node
10
- when ::Oga::XML::Element
11
- namespace_name = node.namespace_name
9
+ text = case node
10
+ when Moxml::Element
11
+ namespace_name = node.namespace&.prefix
12
12
  add_namespaces(node)
13
13
  children = parse_children(node)
14
14
  attributes = node_attributes(node)
15
- node.name
16
- when ::Oga::XML::Text
17
- "text"
15
+ @root = node
16
+ node.inner_text
17
+ when Moxml::Text
18
+ node.content
18
19
  end
19
20
  super(
20
- name,
21
+ OgaAdapter.name_of(node),
21
22
  Hash(attributes),
22
23
  Array(children),
23
- node.text,
24
+ text,
24
25
  parent_document: parent,
25
26
  namespace_prefix: namespace_name,
26
27
  )
@@ -31,7 +32,7 @@ module Lutaml
31
32
  end
32
33
 
33
34
  def text
34
- @text
35
+ super || @text
35
36
  end
36
37
 
37
38
  def to_xml(builder = Builder::Oga.build)
@@ -61,7 +62,7 @@ module Lutaml
61
62
  next if attr_is_namespace?(attr)
62
63
 
63
64
  name = if attr.namespace
64
- "#{attr.namespace.name}:#{attr.name}"
65
+ "#{attr.namespace.prefix}:#{attr.name}"
65
66
  else
66
67
  attr.name
67
68
  end
@@ -69,7 +70,7 @@ module Lutaml
69
70
  name,
70
71
  attr.value,
71
72
  namespace: attr.namespace&.uri,
72
- namespace_prefix: attr.namespace&.name,
73
+ namespace_prefix: attr.namespace&.prefix,
73
74
  )
74
75
  end
75
76
  end
@@ -79,8 +80,8 @@ module Lutaml
79
80
  end
80
81
 
81
82
  def add_namespaces(node)
82
- node.namespaces.each_value do |namespace|
83
- add_namespace(XmlNamespace.new(namespace.uri, namespace.name))
83
+ node.namespaces.each do |namespace|
84
+ add_namespace(XmlNamespace.new(namespace.uri, namespace.prefix))
84
85
  end
85
86
  end
86
87
 
@@ -1,4 +1,5 @@
1
1
  require "oga"
2
+ require "moxml/adapter/oga"
2
3
  require_relative "xml_document"
3
4
  require_relative "oga/document"
4
5
  require_relative "oga/element"
@@ -8,35 +9,105 @@ module Lutaml
8
9
  module Model
9
10
  module XmlAdapter
10
11
  class OgaAdapter < XmlDocument
12
+ TEXT_CLASSES = [Moxml::Text, Moxml::Cdata].freeze
13
+
11
14
  def self.parse(xml, options = {})
12
- encoding = options[:encoding] || xml.encoding.to_s
13
- xml = xml.encode("UTF-16").encode("UTF-8") if encoding && encoding != "UTF-8"
14
- parsed = ::Oga.parse_xml(xml)
15
+ parsed = Moxml::Adapter::Oga.parse(xml)
15
16
  @root = Oga::Element.new(parsed.children.first)
16
- new(@root, encoding)
17
+ new(@root, encoding(xml, options))
17
18
  end
18
19
 
19
20
  def to_xml(options = {})
20
21
  builder_options = {}
21
-
22
22
  builder_options[:encoding] = if options.key?(:encoding)
23
- options[:encoding] || "UTF-8"
23
+ options[:encoding]
24
24
  elsif options.key?(:parse_encoding)
25
25
  options[:parse_encoding]
26
26
  else
27
27
  "UTF-8"
28
28
  end
29
- builder = Builder::Oga.build(options) do |xml|
29
+
30
+ builder = Builder::Oga.build(builder_options) do |xml|
30
31
  if @root.is_a?(Oga::Element)
31
32
  @root.build_xml(xml)
32
33
  else
33
34
  build_element(xml, @root, options)
34
35
  end
35
36
  end
36
- xml_data = builder.to_xml.encode!(builder_options[:encoding])
37
+ xml_data = builder.to_xml
37
38
  options[:declaration] ? declaration(options) + xml_data : xml_data
38
- rescue Encoding::ConverterNotFoundError
39
- invalid_encoding!(builder_options[:encoding])
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
40
111
  end
41
112
 
42
113
  private
@@ -54,18 +125,18 @@ module Lutaml
54
125
  index_hash = {}
55
126
  content = []
56
127
 
57
- element.element_order.each do |name|
58
- index_hash[name] ||= -1
59
- curr_index = index_hash[name] += 1
128
+ element.element_order.each do |object|
129
+ index_hash[object.name] ||= -1
130
+ curr_index = index_hash[object.name] += 1
60
131
 
61
- element_rule = xml_mapping.find_by_name(name)
132
+ element_rule = xml_mapping.find_by_name(object.name)
62
133
  next if element_rule.nil?
63
134
 
64
135
  attribute_def = attribute_definition_for(element, element_rule,
65
136
  mapper_class: mapper_class)
66
137
  value = attribute_value_for(element, element_rule)
67
138
 
68
- next if element_rule == xml_mapping.content_mapping && element_rule.cdata && name == "text"
139
+ next if element_rule == xml_mapping.content_mapping && element_rule.cdata && object.text?
69
140
 
70
141
  if element_rule == xml_mapping.content_mapping
71
142
  text = xml_mapping.content_mapping.serialize(element)
@@ -94,10 +165,6 @@ module Lutaml
94
165
  el.add_text(el, content.join)
95
166
  end
96
167
  end
97
-
98
- def invalid_encoding!(encoding)
99
- raise Error, "unknown encoding name - #{encoding}"
100
- end
101
168
  end
102
169
  end
103
170
  end
@@ -7,26 +7,26 @@ module Lutaml
7
7
  module XmlAdapter
8
8
  class OxAdapter < XmlDocument
9
9
  def self.parse(xml, options = {})
10
- Ox.default_options = Ox.default_options.merge(encoding: options[:encoding] || "UTF-8")
10
+ Ox.default_options = Ox.default_options.merge(encoding: encoding(xml, options))
11
11
 
12
12
  parsed = Ox.parse(xml)
13
- root = OxElement.new(parsed)
14
- new(root, Ox.default_options[:encoding])
13
+ @root = OxElement.new(parsed)
14
+ new(@root, Ox.default_options[:encoding])
15
15
  end
16
16
 
17
17
  def to_xml(options = {})
18
18
  builder_options = { version: options[:version] }
19
19
 
20
20
  builder_options[:encoding] = if options.key?(:encoding)
21
- options[:encoding]
21
+ options[:encoding] unless options[:encoding].nil?
22
22
  elsif options.key?(:parse_encoding)
23
23
  options[:parse_encoding]
24
24
  else
25
25
  "UTF-8"
26
26
  end
27
27
 
28
- builder = Builder::Ox.build
29
- builder.xml.instruct(:xml, encoding: options[:parse_encoding])
28
+ builder = Builder::Ox.build(builder_options)
29
+ builder.xml.instruct(:xml, encoding: builder_options[:encoding])
30
30
 
31
31
  if @root.is_a?(Lutaml::Model::XmlAdapter::OxElement)
32
32
  @root.build_xml(builder)
@@ -39,10 +39,6 @@ module Lutaml
39
39
  end
40
40
 
41
41
  xml_data = builder.xml.to_s
42
- if builder_options[:encoding] && xml_data.valid_encoding?
43
- xml_data = xml_data.encode(builder_options[:encoding])
44
- end
45
-
46
42
  stripped_data = xml_data.lines.drop(1).join
47
43
  options[:declaration] ? declaration(options) + stripped_data : stripped_data
48
44
  end
@@ -62,18 +58,18 @@ module Lutaml
62
58
  index_hash = {}
63
59
  content = []
64
60
 
65
- element.element_order.each do |name|
66
- index_hash[name] ||= -1
67
- curr_index = index_hash[name] += 1
61
+ element.element_order.each do |object|
62
+ index_hash[object.name] ||= -1
63
+ curr_index = index_hash[object.name] += 1
68
64
 
69
- element_rule = xml_mapping.find_by_name(name)
65
+ element_rule = xml_mapping.find_by_name(object.name)
70
66
  next if element_rule.nil?
71
67
 
72
68
  attribute_def = attribute_definition_for(element, element_rule,
73
69
  mapper_class: mapper_class)
74
70
  value = attribute_value_for(element, element_rule)
75
71
 
76
- next if element_rule == xml_mapping.content_mapping && element_rule.cdata && name == "text"
72
+ next if element_rule == xml_mapping.content_mapping && element_rule.cdata && object.text?
77
73
 
78
74
  if element_rule == xml_mapping.content_mapping
79
75
  text = element.send(xml_mapping.content_mapping.to)
@@ -198,6 +194,14 @@ module Lutaml
198
194
  children
199
195
  end
200
196
 
197
+ def cdata
198
+ super || cdata_children.first&.text
199
+ end
200
+
201
+ def text
202
+ super || cdata
203
+ end
204
+
201
205
  private
202
206
 
203
207
  def parse_children(node, root_node: nil)
@@ -2,6 +2,7 @@ require_relative "../mapping_hash"
2
2
  require_relative "xml_element"
3
3
  require_relative "xml_attribute"
4
4
  require_relative "xml_namespace"
5
+ require_relative "element"
5
6
 
6
7
  module Lutaml
7
8
  module Model
@@ -22,6 +23,22 @@ module Lutaml
22
23
  @root.children
23
24
  end
24
25
 
26
+ def root
27
+ @root
28
+ end
29
+
30
+ def attributes
31
+ root.attributes
32
+ end
33
+
34
+ def self.encoding(xml, options)
35
+ if options.key?(:encoding)
36
+ options[:encoding]
37
+ else
38
+ xml.encoding.to_s
39
+ end
40
+ end
41
+
25
42
  def declaration(options)
26
43
  version = "1.0"
27
44
  version = options[:declaration] if options[:declaration].is_a?(String)
@@ -75,28 +92,33 @@ module Lutaml
75
92
  def parse_element(element, klass = nil, format = nil)
76
93
  result = Lutaml::Model::MappingHash.new
77
94
  result.node = element
78
- result.item_order = element.order
95
+ result.item_order = self.class.order_of(element)
79
96
 
80
- element.children.each_with_object(result) do |child, hash|
97
+ element.children.each do |child|
81
98
  if klass&.<= Serialize
82
- attr = klass.attribute_for_child(child.name,
99
+ attr = klass.attribute_for_child(self.class.name_of(child),
83
100
  format)
84
101
  end
85
102
 
86
- value = if child.text?
87
- child.text
88
- else
89
- parse_element(child, attr&.type || klass, format)
90
- end
91
-
92
- hash[child.namespaced_name] = if hash[child.namespaced_name]
93
- [hash[child.namespaced_name], value].flatten
94
- else
95
- value
96
- end
103
+ if child.respond_to?(:text?) && child.text?
104
+ result.assign_or_append_value(
105
+ self.class.name_of(child),
106
+ self.class.text_of(child),
107
+ )
108
+ next
109
+ end
110
+
111
+ result["elements"] ||= Lutaml::Model::MappingHash.new
112
+ result["elements"].assign_or_append_value(
113
+ self.class.namespaced_name_of(child),
114
+ parse_element(child, attr&.type || klass, format),
115
+ )
97
116
  end
98
117
 
118
+ result["attributes"] = attributes_hash(element) if element.attributes&.any?
119
+
99
120
  result.merge(attributes_hash(element))
121
+ result
100
122
  end
101
123
 
102
124
  def attributes_hash(element)
@@ -126,14 +148,6 @@ module Lutaml
126
148
  end
127
149
 
128
150
  def add_to_xml(xml, element, prefix, value, options = {})
129
- if value.is_a?(Array)
130
- value.each do |item|
131
- add_to_xml(xml, element, prefix, item, options)
132
- end
133
-
134
- return
135
- end
136
-
137
151
  attribute = options[:attribute]
138
152
  rule = options[:rule]
139
153
 
@@ -143,6 +157,19 @@ module Lutaml
143
157
  return
144
158
  end
145
159
 
160
+ # Only transform when recursion is not called
161
+ if (!attribute.collection? || value.is_a?(Array)) && transform_method = rule.transform[:export] || attribute.transform_export_method
162
+ value = transform_method.call(value)
163
+ end
164
+
165
+ if value.is_a?(Array)
166
+ value.each do |item|
167
+ add_to_xml(xml, element, prefix, item, options)
168
+ end
169
+
170
+ return
171
+ end
172
+
146
173
  return if !render_element?(rule, element, value)
147
174
 
148
175
  if value && (attribute&.type&.<= Lutaml::Model::Serialize)
@@ -166,9 +193,10 @@ module Lutaml
166
193
 
167
194
  def add_value(xml, value, attribute, cdata: false)
168
195
  if !value.nil?
169
- serialized_value = attribute.type.serialize(value)
170
-
171
- if attribute.type == Lutaml::Model::Type::Hash
196
+ serialized_value = attribute.serialize(value, :xml)
197
+ if attribute.raw?
198
+ xml.add_xml_fragment(xml, value)
199
+ elsif attribute.type == Lutaml::Model::Type::Hash
172
200
  serialized_value.each do |key, val|
173
201
  xml.create_and_add_element(key) do |element|
174
202
  element.text(val)
@@ -343,6 +371,9 @@ module Lutaml
343
371
  end
344
372
 
345
373
  value = mapping_rule.to_value_for(element)
374
+ attr = attribute_definition_for(element, mapping_rule, mapper_class: options[:mapper_class])
375
+ value = attr.serialize(value, :xml) if attr
376
+
346
377
  if render_element?(mapping_rule, element, value)
347
378
  hash[mapping_rule.prefixed_name] = value ? value.to_s : value
348
379
  end
@@ -380,6 +411,32 @@ module Lutaml
380
411
  def self.type
381
412
  Utils.snake_case(self).split("/").last.split("_").first
382
413
  end
414
+
415
+ def self.order_of(element)
416
+ element.order
417
+ end
418
+
419
+ def self.name_of(element)
420
+ element.name
421
+ end
422
+
423
+ def self.text_of(element)
424
+ element.text
425
+ end
426
+
427
+ def self.namespaced_name_of(element)
428
+ element.namespaced_name
429
+ end
430
+
431
+ def text
432
+ return @root.text_children.map(&:text) if @root.children.count > 1
433
+
434
+ @root.text
435
+ end
436
+
437
+ def cdata
438
+ @root.cdata
439
+ end
383
440
  end
384
441
  end
385
442
  end
@@ -8,7 +8,8 @@ module Lutaml
8
8
  :children,
9
9
  :text,
10
10
  :namespace_prefix,
11
- :parent_document
11
+ :parent_document,
12
+ :default_namespace
12
13
 
13
14
  attr_accessor :adapter_node
14
15
 
@@ -117,10 +118,63 @@ module Lutaml
117
118
  end
118
119
 
119
120
  def order
120
- children.each_with_object([]) do |child, arr|
121
- arr << child.unprefixed_name
121
+ children.map do |child|
122
+ type = child.text? ? "Text" : "Element"
123
+ Lutaml::Model::XmlAdapter::Element.new(type, child.unprefixed_name)
122
124
  end
123
125
  end
126
+
127
+ def root
128
+ self
129
+ end
130
+
131
+ def text
132
+ return text_children.map(&:text) if children.count > 1
133
+
134
+ @text
135
+ end
136
+
137
+ def cdata_children
138
+ find_children_by_name("#cdata-section")
139
+ end
140
+
141
+ def text_children
142
+ find_children_by_name("text")
143
+ end
144
+
145
+ def find_attribute_value(attribute_name)
146
+ if attribute_name.is_a?(Array)
147
+ attributes.values.find do |attr|
148
+ attribute_name.include?(attr.namespaced_name)
149
+ end&.value
150
+ else
151
+ attributes.values.find do |attr|
152
+ attribute_name == attr.namespaced_name
153
+ end&.value
154
+ end
155
+ end
156
+
157
+ def find_children_by_name(name)
158
+ if name.is_a?(Array)
159
+ children.select { |child| name.include?(child.namespaced_name) }
160
+ else
161
+ children.select { |child| child.namespaced_name == name }
162
+ end
163
+ end
164
+
165
+ def find_child_by_name(name)
166
+ find_children_by_name(name).first
167
+ end
168
+
169
+ def cdata
170
+ return cdata_children.map(&:text) if children.count > 1
171
+
172
+ @text
173
+ end
174
+
175
+ def to_h
176
+ document.to_h
177
+ end
124
178
  end
125
179
  end
126
180
  end