lutaml-model 0.6.7 → 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 (148) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-repos-todo.json +7 -0
  3. data/.github/workflows/dependent-repos.json +17 -9
  4. data/.rubocop.yml +1 -1
  5. data/.rubocop_todo.yml +51 -65
  6. data/Gemfile +4 -1
  7. data/README.adoc +5083 -2612
  8. data/RELEASE_NOTES.adoc +346 -0
  9. data/docs/custom_adapters.adoc +144 -0
  10. data/lib/lutaml/model/attribute.rb +101 -16
  11. data/lib/lutaml/model/choice.rb +7 -0
  12. data/lib/lutaml/model/comparable_model.rb +48 -9
  13. data/lib/lutaml/model/config.rb +48 -42
  14. data/lib/lutaml/model/error/collection_count_out_of_range_error.rb +1 -1
  15. data/lib/lutaml/model/error/polymorphic_error.rb +14 -0
  16. data/lib/lutaml/model/error.rb +1 -0
  17. data/lib/lutaml/model/format_registry.rb +41 -0
  18. data/lib/lutaml/model/hash/document.rb +11 -0
  19. data/lib/lutaml/model/hash/mapping.rb +19 -0
  20. data/lib/lutaml/model/hash/mapping_rule.rb +9 -0
  21. data/lib/lutaml/model/hash/standard_adapter.rb +17 -0
  22. data/lib/lutaml/model/hash/transform.rb +8 -0
  23. data/lib/lutaml/model/hash.rb +21 -0
  24. data/lib/lutaml/model/json/document.rb +11 -0
  25. data/lib/lutaml/model/json/mapping.rb +19 -0
  26. data/lib/lutaml/model/json/mapping_rule.rb +9 -0
  27. data/lib/lutaml/model/{json_adapter → json}/multi_json_adapter.rb +4 -5
  28. data/lib/lutaml/model/{json_adapter/standard_json_adapter.rb → json/standard_adapter.rb} +5 -3
  29. data/lib/lutaml/model/json/transform.rb +8 -0
  30. data/lib/lutaml/model/json.rb +21 -0
  31. data/lib/lutaml/model/key_value_document.rb +27 -0
  32. data/lib/lutaml/model/{key_value_mapping.rb → mapping/key_value_mapping.rb} +64 -16
  33. data/lib/lutaml/model/{key_value_mapping_rule.rb → mapping/key_value_mapping_rule.rb} +18 -2
  34. data/lib/lutaml/model/mapping/mapping.rb +13 -0
  35. data/lib/lutaml/model/mapping/mapping_rule.rb +300 -0
  36. data/lib/lutaml/model/schema/xml_compiler.rb +15 -15
  37. data/lib/lutaml/model/sequence.rb +2 -2
  38. data/lib/lutaml/model/serialization_adapter.rb +22 -0
  39. data/lib/lutaml/model/serialize.rb +219 -444
  40. data/lib/lutaml/model/services/logger.rb +54 -0
  41. data/lib/lutaml/model/services/transformer.rb +48 -0
  42. data/lib/lutaml/model/services.rb +2 -0
  43. data/lib/lutaml/model/toml/document.rb +11 -0
  44. data/lib/lutaml/model/toml/mapping.rb +27 -0
  45. data/lib/lutaml/model/toml/mapping_rule.rb +9 -0
  46. data/lib/lutaml/model/{toml_adapter → toml}/toml_rb_adapter.rb +3 -3
  47. data/lib/lutaml/model/toml/tomlib_adapter.rb +19 -0
  48. data/lib/lutaml/model/toml/transform.rb +8 -0
  49. data/lib/lutaml/model/toml.rb +30 -0
  50. data/lib/lutaml/model/transform/key_value_transform.rb +291 -0
  51. data/lib/lutaml/model/transform/xml_transform.rb +239 -0
  52. data/lib/lutaml/model/transform.rb +78 -0
  53. data/lib/lutaml/model/type/date.rb +1 -1
  54. data/lib/lutaml/model/type/date_time.rb +2 -2
  55. data/lib/lutaml/model/type/hash.rb +1 -1
  56. data/lib/lutaml/model/type/time.rb +2 -2
  57. data/lib/lutaml/model/type/time_without_date.rb +2 -2
  58. data/lib/lutaml/model/type/value.rb +6 -9
  59. data/lib/lutaml/model/uninitialized_class.rb +64 -0
  60. data/lib/lutaml/model/utils.rb +44 -0
  61. data/lib/lutaml/model/validation.rb +1 -0
  62. data/lib/lutaml/model/version.rb +1 -1
  63. data/lib/lutaml/model/{xml_adapter → xml}/builder/nokogiri.rb +2 -2
  64. data/lib/lutaml/model/{xml_adapter → xml}/builder/oga.rb +10 -10
  65. data/lib/lutaml/model/{xml_adapter → xml}/builder/ox.rb +1 -1
  66. data/lib/lutaml/model/{xml_adapter/xml_document.rb → xml/document.rb} +41 -21
  67. data/lib/lutaml/model/xml/element.rb +32 -0
  68. data/lib/lutaml/model/xml/mapping.rb +410 -0
  69. data/lib/lutaml/model/xml/mapping_rule.rb +141 -0
  70. data/lib/lutaml/model/xml/nokogiri_adapter.rb +232 -0
  71. data/lib/lutaml/model/{xml_adapter → xml}/oga/document.rb +1 -1
  72. data/lib/lutaml/model/{xml_adapter → xml}/oga/element.rb +3 -1
  73. data/lib/lutaml/model/xml/oga_adapter.rb +171 -0
  74. data/lib/lutaml/model/xml/ox_adapter.rb +215 -0
  75. data/lib/lutaml/model/xml/transform.rb +8 -0
  76. data/lib/lutaml/model/{xml_adapter → xml}/xml_attribute.rb +1 -1
  77. data/lib/lutaml/model/{xml_adapter → xml}/xml_element.rb +23 -10
  78. data/lib/lutaml/model/{xml_adapter → xml}/xml_namespace.rb +1 -1
  79. data/lib/lutaml/model/xml.rb +31 -0
  80. data/lib/lutaml/model/xml_adapter/element.rb +11 -25
  81. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +6 -223
  82. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +13 -163
  83. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +10 -207
  84. data/lib/lutaml/model/yaml/document.rb +10 -0
  85. data/lib/lutaml/model/yaml/mapping.rb +19 -0
  86. data/lib/lutaml/model/yaml/mapping_rule.rb +9 -0
  87. data/lib/lutaml/model/{yaml_adapter/standard_yaml_adapter.rb → yaml/standard_adapter.rb} +4 -3
  88. data/lib/lutaml/model/yaml/transform.rb +8 -0
  89. data/lib/lutaml/model/yaml.rb +21 -0
  90. data/lib/lutaml/model.rb +40 -4
  91. data/lutaml-model.gemspec +0 -4
  92. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +7 -7
  93. data/spec/fixtures/person.rb +5 -5
  94. data/spec/lutaml/model/attribute_spec.rb +37 -1
  95. data/spec/lutaml/model/cdata_spec.rb +9 -9
  96. data/spec/lutaml/model/collection_spec.rb +50 -2
  97. data/spec/lutaml/model/comparable_model_spec.rb +92 -27
  98. data/spec/lutaml/model/custom_bibtex_adapter_spec.rb +598 -0
  99. data/spec/lutaml/model/custom_vobject_adapter_spec.rb +1226 -0
  100. data/spec/lutaml/model/defaults_spec.rb +1 -1
  101. data/spec/lutaml/model/enum_spec.rb +1 -1
  102. data/spec/lutaml/model/group_spec.rb +333 -20
  103. data/spec/lutaml/model/hash/adapter_spec.rb +255 -0
  104. data/spec/lutaml/model/json_adapter_spec.rb +6 -6
  105. data/spec/lutaml/model/key_value_mapping_spec.rb +65 -3
  106. data/spec/lutaml/model/mixed_content_spec.rb +24 -24
  107. data/spec/lutaml/model/multiple_mapping_spec.rb +5 -5
  108. data/spec/lutaml/model/ordered_content_spec.rb +6 -6
  109. data/spec/lutaml/model/polymorphic_spec.rb +526 -0
  110. data/spec/lutaml/model/render_empty_spec.rb +194 -0
  111. data/spec/lutaml/model/render_nil_spec.rb +206 -22
  112. data/spec/lutaml/model/root_mappings_spec.rb +3 -3
  113. data/spec/lutaml/model/schema/xml_compiler_spec.rb +6 -6
  114. data/spec/lutaml/model/serializable_spec.rb +179 -103
  115. data/spec/lutaml/model/simple_model_spec.rb +9 -9
  116. data/spec/lutaml/model/toml_adapter_spec.rb +6 -6
  117. data/spec/lutaml/model/toml_spec.rb +51 -0
  118. data/spec/lutaml/model/transformation_spec.rb +72 -15
  119. data/spec/lutaml/model/uninitialized_class_spec.rb +96 -0
  120. data/spec/lutaml/model/value_map_spec.rb +240 -0
  121. data/spec/lutaml/model/xml/namespace/nested_with_explicit_namespace_spec.rb +85 -0
  122. data/spec/lutaml/model/xml/namespace_spec.rb +57 -0
  123. data/spec/lutaml/model/xml/xml_element_spec.rb +93 -0
  124. data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +2 -2
  125. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +2 -2
  126. data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +2 -2
  127. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +6 -6
  128. data/spec/lutaml/model/xml_adapter_spec.rb +6 -6
  129. data/spec/lutaml/model/xml_mapping_rule_spec.rb +105 -5
  130. data/spec/lutaml/model/xml_mapping_spec.rb +70 -16
  131. data/spec/lutaml/model/xml_spec.rb +63 -0
  132. data/spec/lutaml/model/yaml_adapter_spec.rb +3 -5
  133. data/spec/sample_model_spec.rb +3 -3
  134. data/spec/spec_helper.rb +3 -3
  135. metadata +76 -59
  136. data/lib/lutaml/model/json_adapter/json_document.rb +0 -20
  137. data/lib/lutaml/model/json_adapter/json_object.rb +0 -28
  138. data/lib/lutaml/model/loggable.rb +0 -15
  139. data/lib/lutaml/model/mapping_rule.rb +0 -109
  140. data/lib/lutaml/model/toml_adapter/toml_document.rb +0 -20
  141. data/lib/lutaml/model/toml_adapter/toml_object.rb +0 -28
  142. data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +0 -20
  143. data/lib/lutaml/model/toml_adapter.rb +0 -6
  144. data/lib/lutaml/model/xml_mapping.rb +0 -307
  145. data/lib/lutaml/model/xml_mapping_rule.rb +0 -122
  146. data/lib/lutaml/model/yaml_adapter/yaml_document.rb +0 -20
  147. data/lib/lutaml/model/yaml_adapter/yaml_object.rb +0 -28
  148. data/lib/lutaml/model/yaml_adapter.rb +0 -8
@@ -0,0 +1,239 @@
1
+ module Lutaml
2
+ module Model
3
+ class XmlTransform < Lutaml::Model::Transform
4
+ def data_to_model(data, _format, options = {})
5
+ instance = model_class.new
6
+ apply_xml_mapping(data, instance, options)
7
+ end
8
+
9
+ # TODO: this should be extracted from adapters and moved here to be reused
10
+ def model_to_data(model, _format, _options = {})
11
+ model
12
+ end
13
+
14
+ private
15
+
16
+ def apply_xml_mapping(doc, instance, options = {})
17
+ options = prepare_options(options)
18
+ instance.encoding = options[:encoding]
19
+ return instance unless doc
20
+
21
+ mappings = options[:mappings] || mappings_for(:xml).mappings
22
+
23
+ validate_document!(doc, options)
24
+
25
+ set_instance_ordering(instance, doc, options)
26
+ set_schema_location(instance, doc)
27
+
28
+ defaults_used = []
29
+ validate_sequence!(doc.root.order)
30
+
31
+ mappings.each do |rule|
32
+ raise "Attribute '#{rule.to}' not found in #{context}" unless valid_rule?(rule)
33
+
34
+ attr = attribute_for_rule(rule)
35
+ next if attr&.derived?
36
+
37
+ new_opts = options.dup
38
+ if rule.namespace_set?
39
+ new_opts[:default_namespace] = rule.namespace
40
+ end
41
+
42
+ value = if rule.raw_mapping?
43
+ doc.root.inner_xml
44
+ elsif rule.content_mapping?
45
+ rule.cdata ? doc.cdata : doc.text
46
+ else
47
+ val = value_for_rule(doc, rule, new_opts, instance)
48
+
49
+ if (Utils.uninitialized?(val) || val.nil?) && (instance.using_default?(rule.to) || rule.render_default)
50
+ defaults_used << rule.to
51
+ attr&.default || rule.to_value_for(instance)
52
+ else
53
+ val
54
+ end
55
+ end
56
+
57
+ value = apply_value_map(value, rule.value_map(:from, new_opts), attr)
58
+ value = normalize_xml_value(value, rule, attr, new_opts)
59
+ rule.deserialize(instance, value, attributes, context)
60
+ end
61
+
62
+ defaults_used.each do |attr_name|
63
+ instance.using_default_for(attr_name)
64
+ end
65
+
66
+ instance
67
+ end
68
+
69
+ def prepare_options(options)
70
+ opts = Utils.deep_dup(options)
71
+ opts[:default_namespace] ||= mappings_for(:xml)&.namespace_uri
72
+
73
+ opts
74
+ end
75
+
76
+ def validate_document!(doc, options)
77
+ return unless doc.is_a?(Array)
78
+
79
+ raise Lutaml::Model::CollectionTrueMissingError(
80
+ context,
81
+ options[:caller_class],
82
+ )
83
+ end
84
+
85
+ def set_instance_ordering(instance, doc, options)
86
+ return unless instance.respond_to?(:ordered=)
87
+
88
+ instance.element_order = doc.root.order
89
+ instance.ordered = mappings_for(:xml).ordered? || options[:ordered]
90
+ instance.mixed = mappings_for(:xml).mixed_content? || options[:mixed_content]
91
+ end
92
+
93
+ def set_schema_location(instance, doc)
94
+ schema_location = doc.attributes.values.find do |a|
95
+ a.unprefixed_name == "schemaLocation"
96
+ end
97
+
98
+ return if schema_location.nil?
99
+
100
+ instance.schema_location = Lutaml::Model::SchemaLocation.new(
101
+ schema_location: schema_location.value,
102
+ prefix: schema_location.namespace_prefix,
103
+ namespace: schema_location.namespace,
104
+ )
105
+ end
106
+
107
+ def value_for_rule(doc, rule, options, instance)
108
+ rule_names = rule.namespaced_names(options[:default_namespace])
109
+
110
+ if rule.attribute?
111
+ doc.root.find_attribute_value(rule_names)
112
+ else
113
+ attr = attribute_for_rule(rule)
114
+ children = doc.children.select do |child|
115
+ rule_names.include?(child.namespaced_name) && !child.text?
116
+ end
117
+
118
+ if rule.has_custom_method_for_deserialization? || attr.type == Lutaml::Model::Type::Hash
119
+ return_child = attr.type == Lutaml::Model::Type::Hash || !attr.collection? if attr
120
+ return return_child ? children.first : children
121
+ end
122
+
123
+ return handle_cdata(children) if rule.cdata
124
+
125
+ values = []
126
+
127
+ if Utils.present?(children)
128
+ instance.value_set_for(attr.name)
129
+ else
130
+ children = nil
131
+ values = Lutaml::Model::UninitializedClass.instance
132
+ end
133
+
134
+ children&.each do |child|
135
+ if !rule.has_custom_method_for_deserialization? && attr.type <= Serialize
136
+ cast_options = options.except(:mappings)
137
+ cast_options[:polymorphic] = rule.polymorphic if rule.polymorphic
138
+
139
+ values << attr.cast(child, :xml, cast_options)
140
+ elsif attr.raw?
141
+ values << inner_xml_of(child)
142
+ else
143
+ return nil if rule.render_nil_as_nil? && child.nil_element?
144
+
145
+ text = child.nil_element? ? nil : (child&.text&.+ child&.cdata)
146
+ values << text
147
+ end
148
+ end
149
+
150
+ normalized_value_for_attr(values, attr)
151
+ end
152
+ end
153
+
154
+ def handle_cdata(children)
155
+ values = children.map do |child|
156
+ child.cdata_children&.map(&:text)
157
+ end.flatten
158
+
159
+ children.count > 1 ? values : values.first
160
+ end
161
+
162
+ def normalized_value_for_attr(values, attr)
163
+ # for xml collection: true cases like
164
+ # <store><items /></store>
165
+ # <store><items xsi:nil="true"/></store>
166
+ # <store><items></items></store>
167
+ #
168
+ # these are considered empty collection
169
+ return [] if attr&.collection? && [[nil], [""]].include?(values)
170
+ return values if attr&.collection?
171
+
172
+ values.is_a?(Array) ? values.first : values
173
+ end
174
+
175
+ def normalize_xml_value(value, rule, attr, options = {})
176
+ value = [value].compact if attr&.collection? && !value.is_a?(Array) && !value.nil?
177
+
178
+ return value unless cast_value?(attr, rule)
179
+
180
+ options.merge(caller_class: self, mixed_content: rule.mixed_content)
181
+ attr.cast(
182
+ value,
183
+ :xml,
184
+ options,
185
+ )
186
+ end
187
+
188
+ def cast_value?(attr, rule)
189
+ attr &&
190
+ !rule.raw_mapping? &&
191
+ !rule.content_mapping? &&
192
+ !rule.custom_methods[:from]
193
+ end
194
+
195
+ def text_hash?(attr, value)
196
+ return false unless value.is_a?(Hash)
197
+ return value.one? && value.text? unless attr
198
+
199
+ !(attr.type <= Serialize) && attr.type != Lutaml::Model::Type::Hash
200
+ end
201
+
202
+ def ensure_utf8(value)
203
+ case value
204
+ when String
205
+ value.encode("UTF-8", invalid: :replace, undef: :replace,
206
+ replace: "")
207
+ when Array
208
+ value.map { |v| ensure_utf8(v) }
209
+ when Hash
210
+ value.transform_keys do |k|
211
+ ensure_utf8(k)
212
+ end.transform_values do |v|
213
+ ensure_utf8(v)
214
+ end
215
+ else
216
+ value
217
+ end
218
+ end
219
+
220
+ def inner_xml_of(node)
221
+ case node
222
+ when Xml::XmlElement
223
+ node.inner_xml
224
+ else
225
+ node.children.map(&:to_xml).join
226
+ end
227
+ end
228
+
229
+ def validate_sequence!(element_order)
230
+ mapping_sequence = mappings_for(:xml).element_sequence
231
+ current_order = element_order.filter_map(&:element_tag)
232
+
233
+ mapping_sequence.each do |mapping|
234
+ mapping.validate_content!(current_order)
235
+ end
236
+ end
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,78 @@
1
+ module Lutaml
2
+ module Model
3
+ class Transform
4
+ def self.data_to_model(context, data, format, options = {})
5
+ new(context).data_to_model(data, format, options)
6
+ end
7
+
8
+ def self.model_to_data(context, model, format, options = {})
9
+ new(context).model_to_data(model, format, options)
10
+ end
11
+
12
+ attr_reader :context, :attributes
13
+
14
+ def initialize(context)
15
+ @context = context
16
+ @attributes = context.attributes
17
+ end
18
+
19
+ def model_class
20
+ @context.model
21
+ end
22
+
23
+ def data_to_model(data, options = {})
24
+ raise NotImplementedError, "#{self.class.name} must implement `data_to_model`."
25
+ end
26
+
27
+ def model_to_data(model, options = {})
28
+ raise NotImplementedError, "#{self.class.name} must implement `model_to_data`."
29
+ end
30
+
31
+ protected
32
+
33
+ def apply_value_map(value, value_map, attr)
34
+ if value.nil?
35
+ value_for_option(value_map[:nil], attr)
36
+ elsif Utils.empty?(value)
37
+ value_for_option(value_map[:empty], attr, value)
38
+ elsif Utils.uninitialized?(value)
39
+ value_for_option(value_map[:omitted], attr)
40
+ else
41
+ value
42
+ end
43
+ end
44
+
45
+ def value_for_option(option, attr, empty_value = nil)
46
+ return nil if option == :nil
47
+ return empty_value || empty_object(attr) if option == :empty
48
+
49
+ Lutaml::Model::UninitializedClass.instance
50
+ end
51
+
52
+ def empty_object(attr)
53
+ return [] if attr.collection?
54
+
55
+ ""
56
+ end
57
+
58
+ def mappings_for(format)
59
+ context.mappings_for(format)
60
+ end
61
+
62
+ def valid_rule?(rule)
63
+ attribute = attribute_for_rule(rule)
64
+
65
+ !!attribute || rule.custom_methods[:from]
66
+ end
67
+
68
+ def attribute_for_rule(rule)
69
+ return attributes[rule.to] unless rule.delegate
70
+
71
+ attributes[rule.delegate].type.attributes[rule.to]
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ require_relative "transform/key_value_transform"
78
+ require_relative "transform/xml_transform"
@@ -3,7 +3,7 @@ module Lutaml
3
3
  module Type
4
4
  class Date < Value
5
5
  def self.cast(value)
6
- return nil if value.nil?
6
+ return value if value.nil? || Utils.uninitialized?(value)
7
7
 
8
8
  case value
9
9
  when ::DateTime, ::Time
@@ -6,7 +6,7 @@ module Lutaml
6
6
  # Date and time representation
7
7
  class DateTime < Value
8
8
  def self.cast(value)
9
- return nil if value.nil?
9
+ return value if value.nil? || Utils.uninitialized?(value)
10
10
 
11
11
  case value
12
12
  when ::DateTime then value
@@ -18,7 +18,7 @@ module Lutaml
18
18
  end
19
19
 
20
20
  def self.serialize(value)
21
- return nil if value.nil?
21
+ return value if value.nil? || Utils.uninitialized?(value)
22
22
 
23
23
  cast(value)&.iso8601
24
24
  end
@@ -3,7 +3,7 @@ module Lutaml
3
3
  module Type
4
4
  class Hash < Value
5
5
  def self.cast(value)
6
- return nil if value.nil?
6
+ return value if value.nil? || Utils.uninitialized?(value)
7
7
 
8
8
  hash = if value.respond_to?(:to_h)
9
9
  value.to_h
@@ -5,7 +5,7 @@ module Lutaml
5
5
  module Type
6
6
  class Time < Value
7
7
  def self.cast(value)
8
- return nil if value.nil?
8
+ return value if value.nil? || Utils.uninitialized?(value)
9
9
 
10
10
  case value
11
11
  when ::Time then value
@@ -17,7 +17,7 @@ module Lutaml
17
17
  end
18
18
 
19
19
  def self.serialize(value)
20
- return nil if value.nil?
20
+ return value if value.nil? || Utils.uninitialized?(value)
21
21
 
22
22
  value = cast(value)
23
23
  # value&.strftime("%Y-%m-%dT%H:%M:%S%:z")
@@ -11,7 +11,7 @@ module Lutaml
11
11
  # ::Time.new(1, 1, 1, time.hour, time.min, time.sec)
12
12
 
13
13
  def self.cast(value)
14
- return nil if value.nil?
14
+ return value if value.nil? || Utils.uninitialized?(value)
15
15
 
16
16
  case value
17
17
  when ::Time then value
@@ -22,7 +22,7 @@ module Lutaml
22
22
  end
23
23
 
24
24
  def self.serialize(value)
25
- return nil if value.nil?
25
+ return value if value.nil? || Utils.uninitialized?(value)
26
26
 
27
27
  value = cast(value)
28
28
  value.strftime("%H:%M:%S") # Format as HH:MM:SS
@@ -28,20 +28,17 @@ module Lutaml
28
28
  value.to_s
29
29
  end
30
30
 
31
- # Format-specific instance methods
32
- ::Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
33
- define_method(:"to_#{format}") do
34
- value
35
- end
36
- end
37
-
38
31
  # Class-level format conversion
39
32
  def self.from_format(value, format)
40
33
  new(send(:"from_#{format}", value))
41
34
  end
42
35
 
43
- # Default format-specific class methods that can be overridden
44
- ::Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
36
+ # called from config when a new format is added
37
+ def self.register_format_to_from_methods(format)
38
+ define_method(:"to_#{format}") do
39
+ value
40
+ end
41
+
45
42
  define_singleton_method(:"from_#{format}") do |value|
46
43
  cast(value)
47
44
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ module Lutaml
6
+ module Model
7
+ class UninitializedClass
8
+ include Singleton
9
+
10
+ def to_s
11
+ self
12
+ end
13
+
14
+ def inspect
15
+ "uninitialized"
16
+ end
17
+
18
+ def uninitialized?
19
+ true
20
+ end
21
+
22
+ def match?(_args)
23
+ false
24
+ end
25
+
26
+ def include?(_args)
27
+ false
28
+ end
29
+
30
+ def gsub(_, _)
31
+ self
32
+ end
33
+
34
+ def to_yaml
35
+ nil
36
+ end
37
+
38
+ def to_f
39
+ self
40
+ end
41
+
42
+ def size
43
+ 0
44
+ end
45
+
46
+ def encoding
47
+ # same as default encoding for string
48
+ "".encoding
49
+ end
50
+
51
+ def method_missing(method, *_args, &_block)
52
+ if method.end_with?("?")
53
+ false
54
+ else
55
+ super
56
+ end
57
+ end
58
+
59
+ def respond_to_missing?(method_name, _include_private = false)
60
+ method_name.end_with?("?")
61
+ end
62
+ end
63
+ end
64
+ end
@@ -34,6 +34,16 @@ module Lutaml
34
34
  .downcase
35
35
  end
36
36
 
37
+ def initialized?(value)
38
+ return true unless value.respond_to?(:initialized?)
39
+
40
+ value.initialized?
41
+ end
42
+
43
+ def uninitialized?(value)
44
+ !initialized?(value)
45
+ end
46
+
37
47
  def present?(value)
38
48
  !blank?(value)
39
49
  end
@@ -49,6 +59,40 @@ module Lutaml
49
59
  collection.empty?
50
60
  end
51
61
 
62
+ def empty?(value)
63
+ value.respond_to?(:empty?) ? value.empty? : false
64
+ end
65
+
66
+ # Check if the hash contains the given key in string or symbol format
67
+ # @param hash [Hash] the hash to check
68
+ # @param key [String, Symbol] the key to check
69
+ # @return [Boolean] true if the hash contains the key, false otherwise
70
+ # @example
71
+ # hash = { "key" => "value" }
72
+ # string_or_symbol_key?(hash, "key") # => true
73
+ # string_or_symbol_key?(hash, :key) # => true
74
+ # string_or_symbol_key?(hash, "invalid_key") # => false
75
+ def string_or_symbol_key?(hash, key)
76
+ hash.key?(key.to_s) || hash.key?(key.to_sym)
77
+ end
78
+
79
+ # Fetch the value from the hash using the key in string or symbol format
80
+ # @param hash [Hash] the hash to fetch the value from
81
+ # @param key [String, Symbol] the key to fetch the value for
82
+ # @return [Object] the value associated with the key
83
+ # @example
84
+ # hash = { "key" => "value" }
85
+ # fetch_with_string_or_symbol_key(hash, "key") # => "value"
86
+ # fetch_with_string_or_symbol_key(hash, :key) # => "value"
87
+ # fetch_with_string_or_symbol_key(hash, "invalid_key") # => nil
88
+ def fetch_with_string_or_symbol_key(hash, key)
89
+ if hash.key?(key.to_s)
90
+ hash[key.to_s]
91
+ elsif hash.key?(key.to_sym)
92
+ hash[key.to_sym]
93
+ end
94
+ end
95
+
52
96
  def add_method_if_not_defined(klass, method_name, &block)
53
97
  unless klass.method_defined?(method_name)
54
98
  klass.class_eval do
@@ -14,6 +14,7 @@ module Lutaml
14
14
  rescue Lutaml::Model::InvalidValueError,
15
15
  Lutaml::Model::CollectionCountOutOfRangeError,
16
16
  Lutaml::Model::CollectionTrueMissingError,
17
+ Lutaml::Model::PolymorphicError,
17
18
  PatternNotMatchedError => e
18
19
  errors << e
19
20
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.6.7"
5
+ VERSION = "0.7.2"
6
6
  end
7
7
  end
@@ -1,6 +1,6 @@
1
1
  module Lutaml
2
2
  module Model
3
- module XmlAdapter
3
+ module Xml
4
4
  module Builder
5
5
  class Nokogiri
6
6
  def self.build(options = {})
@@ -84,7 +84,7 @@ module Lutaml
84
84
  end
85
85
 
86
86
  def method_missing(method_name, *args, &block)
87
- if block_given?
87
+ if block
88
88
  xml.public_send(method_name, *args, &block)
89
89
  else
90
90
  xml.public_send(method_name, *args)
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- module XmlAdapter
5
+ module Xml
6
6
  module Builder
7
7
  class Oga
8
8
  def self.build(options = {}, &block)
@@ -12,7 +12,7 @@ module Lutaml
12
12
  attr_reader :document, :current_node, :encoding
13
13
 
14
14
  def initialize(options = {})
15
- @document = XmlAdapter::Oga::Document.new
15
+ @document = Xml::Oga::Document.new
16
16
  @current_node = @document
17
17
  @encoding = options[:encoding]
18
18
  yield(self) if block_given?
@@ -23,7 +23,7 @@ module Lutaml
23
23
  name = "#{@current_namespace}:#{name}"
24
24
  end
25
25
 
26
- if block_given?
26
+ if block
27
27
  element(name, attributes, &block)
28
28
  else
29
29
  element(name, attributes)
@@ -48,9 +48,9 @@ module Lutaml
48
48
 
49
49
  def add_element(oga_element, child)
50
50
  if child.is_a?(String)
51
- current_element = oga_element.is_a?(XmlAdapter::Oga::Document) ? current_node : oga_element
51
+ current_element = oga_element.is_a?(Xml::Oga::Document) ? current_node : oga_element
52
52
  add_xml_fragment(current_element, child)
53
- elsif oga_element.is_a?(XmlAdapter::Oga::Document)
53
+ elsif oga_element.is_a?(Xml::Oga::Document)
54
54
  oga_element.children.last.children << child
55
55
  else
56
56
  oga_element.children << child
@@ -62,7 +62,7 @@ module Lutaml
62
62
  name: name,
63
63
  value: value.to_s,
64
64
  )
65
- if element.is_a?(XmlAdapter::Oga::Document)
65
+ if element.is_a?(Xml::Oga::Document)
66
66
  element.children.last.attributes << attribute
67
67
  else
68
68
  element.attributes << attribute
@@ -85,7 +85,7 @@ module Lutaml
85
85
  element_name
86
86
  end
87
87
 
88
- if block_given?
88
+ if block
89
89
  element(prefixed_name, attributes, &block)
90
90
  else
91
91
  element(prefixed_name, attributes)
@@ -100,7 +100,7 @@ module Lutaml
100
100
  fragment = "<fragment>#{content}</fragment>"
101
101
  parsed_fragment = ::Oga.parse_xml(fragment)
102
102
  parsed_children = parsed_fragment.children.first.children
103
- if element.is_a?(XmlAdapter::Oga::Document)
103
+ if element.is_a?(Xml::Oga::Document)
104
104
  element.children.last.children += parsed_children
105
105
  else
106
106
  element.children += parsed_children
@@ -116,7 +116,7 @@ module Lutaml
116
116
  end
117
117
 
118
118
  def append_text_node(element, oga_text)
119
- if element.is_a?(XmlAdapter::Oga::Document)
119
+ if element.is_a?(Xml::Oga::Document)
120
120
  children = element.children
121
121
  children.empty? ? children << oga_text : children.last.children << oga_text
122
122
  else
@@ -126,7 +126,7 @@ module Lutaml
126
126
 
127
127
  def add_cdata(element, value)
128
128
  oga_cdata = ::Oga::XML::CData.new(text: value.to_s)
129
- if element.is_a?(XmlAdapter::Oga::Document)
129
+ if element.is_a?(Xml::Oga::Document)
130
130
  element.children.last.children << oga_cdata
131
131
  else
132
132
  element.children << oga_cdata
@@ -1,6 +1,6 @@
1
1
  module Lutaml
2
2
  module Model
3
- module XmlAdapter
3
+ module Xml
4
4
  module Builder
5
5
  class Ox
6
6
  def self.build(options = {})