lutaml-model 0.3.29 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-repos.json +15 -0
  3. data/.github/workflows/dependent-tests.yml +14 -0
  4. data/.rubocop_todo.yml +15 -7
  5. data/README.adoc +6 -0
  6. data/lib/lutaml/model/attribute.rb +6 -2
  7. data/lib/lutaml/model/json_adapter/json_document.rb +1 -1
  8. data/lib/lutaml/model/json_adapter/multi_json_adapter.rb +1 -1
  9. data/lib/lutaml/model/json_adapter/standard_json_adapter.rb +1 -1
  10. data/lib/lutaml/model/serialize.rb +133 -33
  11. data/lib/lutaml/model/toml_adapter/toml_document.rb +1 -1
  12. data/lib/lutaml/model/toml_adapter/toml_rb_adapter.rb +1 -1
  13. data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +1 -1
  14. data/lib/lutaml/model/type/time.rb +4 -4
  15. data/lib/lutaml/model/utils.rb +1 -1
  16. data/lib/lutaml/model/validation.rb +6 -2
  17. data/lib/lutaml/model/version.rb +1 -1
  18. data/lib/lutaml/model/xml_adapter/builder/nokogiri.rb +10 -0
  19. data/lib/lutaml/model/xml_adapter/builder/ox.rb +5 -1
  20. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +5 -3
  21. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +1 -1
  22. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +25 -10
  23. data/lib/lutaml/model/xml_adapter/xml_document.rb +12 -24
  24. data/lib/lutaml/model/yaml_adapter/standard_yaml_adapter.rb +1 -1
  25. data/lib/lutaml/model/yaml_adapter/yaml_document.rb +1 -1
  26. data/spec/fixtures/xml/latin_encoding.xml +5 -0
  27. data/spec/fixtures/xml/shift_jis.xml +4 -0
  28. data/spec/lutaml/model/custom_serialization_spec.rb +16 -0
  29. data/spec/lutaml/model/defaults_spec.rb +74 -0
  30. data/spec/lutaml/model/enum_spec.rb +131 -0
  31. data/spec/lutaml/model/mixed_content_spec.rb +190 -7
  32. data/spec/lutaml/model/render_nil_spec.rb +0 -2
  33. data/spec/lutaml/model/serializable_spec.rb +2 -2
  34. data/spec/lutaml/model/xml_mapping_spec.rb +679 -543
  35. metadata +7 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fba70c8a3fca206b3109603109812019773027580c47b2e3615d873df6e0ce2d
4
- data.tar.gz: 21313fbfb52c29c620eb1020672d337689eed963c25fe56c20baeef139e569b5
3
+ metadata.gz: fa60d5495804e6bfc0203ba1a237f38b89b9626d036beeb2be8ca7aebb22426f
4
+ data.tar.gz: 63d8d18127bee85cfaa73973f57554bc56385805dfeea1dad343c552277089d6
5
5
  SHA512:
6
- metadata.gz: 274739542b8473ae58b4fc6b78e1152aaef604a55f3c9db0ec5678743573d683b4afdd2429d97d6a24c03fdb2c14a72904bf4e057f480e6bafc635a21fde085a
7
- data.tar.gz: b1a7ad49313463676b3afe8732f94ed8ab37f79935f33449c70011bd8a8449934425aa27b9dd25d2a7e25a064afa1e665bf95f8b2eac4ec7cd63be4bcfdb7b8d
6
+ metadata.gz: 322a29941e7685a6e118d083f1b6e11ce6b8abc1cacb5508dd501f874c5ea3f2dc6f0e6719ce4895deb31c877319a3afe0e20d0820a81b8fd60d9fe59c408472
7
+ data.tar.gz: ce087acefc221ed41180658b8c40d4cff5c0196c208647b9d1c5d0d39799849860abf80b981fe753b30a1176ca8633ed696440beeddef5a4493c1e829340f250
@@ -0,0 +1,15 @@
1
+ {
2
+ "repo": [
3
+ "lutaml/genericode",
4
+ "lutaml/ogc-gml",
5
+ "metanorma/modspec-ruby",
6
+ "metanorma/rfcxml",
7
+ "metanorma/niso-jats",
8
+ "metanorma/sts-ruby",
9
+ "lutaml/metaschema",
10
+ "glossarist/termium",
11
+ "lutaml/oasis-etm",
12
+ "lutaml/ali",
13
+ "lutaml/reqif"
14
+ ]
15
+ }
@@ -0,0 +1,14 @@
1
+ name: dependent-gems-test
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ tags: [ v* ]
7
+ pull_request:
8
+ workflow_dispatch:
9
+ repository_dispatch:
10
+ types: [ release-passed ]
11
+
12
+ jobs:
13
+ rake:
14
+ uses: metanorma/ci/.github/workflows/dependent-rake.yml@main
data/.rubocop_todo.yml CHANGED
@@ -1,12 +1,12 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2024-11-25 13:01:09 UTC using RuboCop version 1.66.1.
3
+ # on 2024-12-03 11:13:42 UTC using RuboCop version 1.66.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 185
9
+ # Offense count: 224
10
10
  # This cop supports safe autocorrection (--autocorrect).
11
11
  # Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns.
12
12
  # URISchemes: http, https
@@ -51,7 +51,7 @@ Metrics/AbcSize:
51
51
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
52
52
  # AllowedMethods: refine
53
53
  Metrics/BlockLength:
54
- Max: 47
54
+ Max: 46
55
55
 
56
56
  # Offense count: 28
57
57
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
@@ -65,7 +65,7 @@ Metrics/CyclomaticComplexity:
65
65
  - 'lib/lutaml/model/xml_adapter/ox_adapter.rb'
66
66
  - 'lib/lutaml/model/xml_adapter/xml_document.rb'
67
67
 
68
- # Offense count: 55
68
+ # Offense count: 54
69
69
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
70
70
  Metrics/MethodLength:
71
71
  Max: 46
@@ -97,7 +97,15 @@ RSpec/ContextWording:
97
97
  - 'spec/lutaml/model/xml_adapter/xml_namespace_spec.rb'
98
98
  - 'spec/lutaml/model/xml_mapping_spec.rb'
99
99
 
100
- # Offense count: 127
100
+ # Offense count: 1
101
+ # This cop supports unsafe autocorrection (--autocorrect-all).
102
+ # Configuration parameters: SkipBlocks, EnforcedStyle, OnlyStaticConstants.
103
+ # SupportedStyles: described_class, explicit
104
+ RSpec/DescribedClass:
105
+ Exclude:
106
+ - 'spec/lutaml/model/xml_mapping_spec.rb'
107
+
108
+ # Offense count: 133
101
109
  # Configuration parameters: CountAsOne.
102
110
  RSpec/ExampleLength:
103
111
  Max: 54
@@ -127,7 +135,7 @@ RSpec/MultipleDescribes:
127
135
  - 'spec/lutaml/model/xml_adapter/xml_namespace_spec.rb'
128
136
  - 'spec/lutaml/model/xml_adapter_spec.rb'
129
137
 
130
- # Offense count: 153
138
+ # Offense count: 157
131
139
  RSpec/MultipleExpectations:
132
140
  Max: 14
133
141
 
@@ -136,7 +144,7 @@ RSpec/MultipleExpectations:
136
144
  RSpec/MultipleMemoizedHelpers:
137
145
  Max: 9
138
146
 
139
- # Offense count: 11
147
+ # Offense count: 15
140
148
  # Configuration parameters: AllowedGroups.
141
149
  RSpec/NestedGroups:
142
150
  Max: 4
data/README.adoc CHANGED
@@ -569,6 +569,12 @@ The values set inside the `values:` option can be of any type, but they must
569
569
  match the type of the attribute. The values are compared using the `==` operator,
570
570
  so the type must implement the `==` method.
571
571
 
572
+ Also, If all the elements in `values` directive are strings then `lutaml-model` add some enum convenience methods, for each of the value the following three methods are added
573
+
574
+ * `value1`: will return value if set
575
+ * `value1?`: will return true if value is set, false otherwise
576
+ * `value1=`: will set the value of `name_of_attribute` equal to `value1` if truthy value is given, and remove it otherwise.
577
+
572
578
  .Using the `values` directive to define acceptable values for an attribute (basic types)
573
579
  [example]
574
580
  ====
@@ -76,6 +76,10 @@ module Lutaml
76
76
  @raw
77
77
  end
78
78
 
79
+ def enum?
80
+ !enum_values.empty?
81
+ end
82
+
79
83
  def default
80
84
  value = if delegate
81
85
  type.attributes[to].default
@@ -98,7 +102,7 @@ module Lutaml
98
102
 
99
103
  def valid_value!(value)
100
104
  return true if value.nil? && !collection?
101
- return true if enum_values.empty?
105
+ return true unless enum?
102
106
 
103
107
  unless valid_value?(value)
104
108
  raise Lutaml::Model::InvalidValueError.new(name, value, enum_values)
@@ -208,7 +212,7 @@ module Lutaml
208
212
  serialize(v, format, options)
209
213
  end
210
214
  elsif type <= Serialize
211
- type.hash_representation(value, format, options)
215
+ type.public_send(:"as_#{format}", value, options)
212
216
  else
213
217
  # Convert to Value instance if not already
214
218
  value = type.new(value) unless value.is_a?(Type::Value)
@@ -7,7 +7,7 @@ module Lutaml
7
7
  module JsonAdapter
8
8
  # Base class for JSON documents
9
9
  class JsonDocument < JsonObject
10
- def self.parse(json)
10
+ def self.parse(json, _options = {})
11
11
  raise NotImplementedError, "Subclasses must implement `parse`."
12
12
  end
13
13
 
@@ -5,7 +5,7 @@ module Lutaml
5
5
  module Model
6
6
  module JsonAdapter
7
7
  class MultiJsonAdapter < JsonDocument
8
- def self.parse(json)
8
+ def self.parse(json, _options = {})
9
9
  data = MultiJson.load(json)
10
10
  new(data)
11
11
  end
@@ -5,7 +5,7 @@ module Lutaml
5
5
  module Model
6
6
  module JsonAdapter
7
7
  class StandardJsonAdapter < JsonDocument
8
- def self.parse(json)
8
+ def self.parse(json, _options = {})
9
9
  JSON.parse(json, create_additions: false)
10
10
  end
11
11
 
@@ -51,6 +51,7 @@ module Lutaml
51
51
  Utils.add_boolean_accessor_if_not_defined(klass, :ordered)
52
52
  Utils.add_boolean_accessor_if_not_defined(klass, :mixed)
53
53
  Utils.add_accessor_if_not_defined(klass, :element_order)
54
+ Utils.add_accessor_if_not_defined(klass, :encoding)
54
55
 
55
56
  Utils.add_method_if_not_defined(klass,
56
57
  :using_default_for) do |attribute_name|
@@ -80,16 +81,104 @@ module Lutaml
80
81
  attr = Attribute.new(name, type, options)
81
82
  attributes[name] = attr
82
83
 
83
- define_method(name) do
84
- instance_variable_get(:"@#{name}")
84
+ if attr.enum?
85
+ add_enum_methods_to_model(
86
+ model,
87
+ name,
88
+ options[:values],
89
+ collection: options[:collection],
90
+ )
91
+ else
92
+ define_method(name) do
93
+ instance_variable_get(:"@#{name}")
94
+ end
95
+
96
+ define_method(:"#{name}=") do |value|
97
+ value_set_for(name)
98
+ instance_variable_set(:"@#{name}", attr.cast_value(value))
99
+ end
85
100
  end
101
+ end
86
102
 
87
- define_method(:"#{name}=") do |value|
88
- value_set_for(name)
89
- instance_variable_set(:"@#{name}", attr.cast_value(value))
103
+ def add_enum_methods_to_model(klass, enum_name, values, collection: false)
104
+ add_enum_getter_if_not_defined(klass, enum_name, collection)
105
+ add_enum_setter_if_not_defined(klass, enum_name, values, collection)
106
+
107
+ return unless values.all?(::String)
108
+
109
+ values.each do |value|
110
+ Utils.add_method_if_not_defined(klass, "#{value}?") do
111
+ curr_value = public_send(:"#{enum_name}")
112
+
113
+ if collection
114
+ curr_value.include?(value)
115
+ else
116
+ curr_value == value
117
+ end
118
+ end
119
+
120
+ Utils.add_method_if_not_defined(klass, value.to_s) do
121
+ public_send(:"#{value}?")
122
+ end
123
+
124
+ Utils.add_method_if_not_defined(klass, "#{value}=") do |val|
125
+ value_set_for(enum_name)
126
+ enum_vals = public_send(:"#{enum_name}")
127
+
128
+ enum_vals = if !!val
129
+ if collection
130
+ enum_vals << value
131
+ else
132
+ [value]
133
+ end
134
+ elsif collection
135
+ enum_vals.delete(value)
136
+ enum_vals
137
+ else
138
+ []
139
+ end
140
+
141
+ instance_variable_set(:"@#{enum_name}", enum_vals)
142
+ end
143
+
144
+ Utils.add_method_if_not_defined(klass, "#{value}!") do
145
+ public_send(:"#{value}=", true)
146
+ end
90
147
  end
91
148
  end
92
149
 
150
+ def add_enum_getter_if_not_defined(klass, enum_name, collection)
151
+ Utils.add_method_if_not_defined(klass, enum_name) do
152
+ i = instance_variable_get(:"@#{enum_name}") || []
153
+
154
+ if !collection && i.is_a?(Array)
155
+ i.first
156
+ else
157
+ i.uniq
158
+ end
159
+ end
160
+ end
161
+
162
+ def add_enum_setter_if_not_defined(klass, enum_name, _values, collection)
163
+ Utils.add_method_if_not_defined(klass, "#{enum_name}=") do |value|
164
+ value = [value] unless value.is_a?(Array)
165
+
166
+ value_set_for(enum_name)
167
+
168
+ if collection
169
+ curr_value = public_send(:"#{enum_name}")
170
+
171
+ instance_variable_set(:"@#{enum_name}", curr_value + value)
172
+ else
173
+ instance_variable_set(:"@#{enum_name}", value)
174
+ end
175
+ end
176
+ end
177
+
178
+ def enums
179
+ attributes.select { |_, attr| attr.enum? }
180
+ end
181
+
93
182
  Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
94
183
  define_method(format) do |&block|
95
184
  klass = format == :xml ? XmlMapping : KeyValueMapping
@@ -101,23 +190,22 @@ module Lutaml
101
190
  end
102
191
  end
103
192
 
104
- define_method(:"from_#{format}") do |data|
193
+ define_method(:"from_#{format}") do |data, options = {}|
105
194
  adapter = Lutaml::Model::Config.send(:"#{format}_adapter")
106
195
 
107
- doc = adapter.parse(data)
108
- public_send(:"of_#{format}", doc)
196
+ doc = adapter.parse(data, options)
197
+ public_send(:"of_#{format}", doc, options)
109
198
  end
110
199
 
111
- define_method(:"of_#{format}") do |doc|
200
+ define_method(:"of_#{format}") do |doc, options = {}|
112
201
  if doc.is_a?(Array)
113
- return doc.map do |item|
114
- send(:"of_#{format}", item)
115
- end
202
+ return doc.map { |item| send(:"of_#{format}", item) }
116
203
  end
117
204
 
118
205
  if format == :xml
119
206
  doc_hash = doc.parse_element(doc.root, self, :xml)
120
- apply_mappings(doc_hash, format)
207
+ options[:encoding] = doc.encoding
208
+ apply_mappings(doc_hash, format, options)
121
209
  else
122
210
  apply_mappings(doc.to_h, format)
123
211
  end
@@ -135,7 +223,7 @@ module Lutaml
135
223
  end
136
224
  end
137
225
 
138
- define_method(:"as_#{format}") do |instance|
226
+ define_method(:"as_#{format}") do |instance, options = {}|
139
227
  if instance.is_a?(Array)
140
228
  return instance.map { |item| public_send(:"as_#{format}", item) }
141
229
  end
@@ -147,7 +235,7 @@ module Lutaml
147
235
 
148
236
  return instance if format == :xml
149
237
 
150
- hash_representation(instance, format)
238
+ hash_representation(instance, format, options)
151
239
  end
152
240
  end
153
241
 
@@ -166,7 +254,7 @@ module Lutaml
166
254
  mappings.each_with_object({}) do |rule, hash|
167
255
  name = rule.to
168
256
  next if except&.include?(name) || (only && !only.include?(name))
169
- next if !rule.render_default? && instance.using_default?(rule.to)
257
+ next if !rule.custom_methods[:to] && (!rule.render_default? && instance.using_default?(rule.to))
170
258
 
171
259
  next handle_delegate(instance, rule, hash, format) if rule.delegate
172
260
 
@@ -176,15 +264,15 @@ module Lutaml
176
264
 
177
265
  value = instance.send(name)
178
266
 
179
- next if value.nil? && !rule.render_nil
267
+ next if Utils.blank?(value) && !rule.render_nil
180
268
 
181
269
  attribute = attributes[name]
182
270
 
183
- hash[rule.from] = if rule.child_mappings
184
- generate_hash_from_child_mappings(value, rule.child_mappings)
185
- else
186
- attribute.serialize(value, format, options)
187
- end
271
+ hash[rule.from.to_s] = if rule.child_mappings
272
+ generate_hash_from_child_mappings(value, rule.child_mappings)
273
+ else
274
+ attribute.serialize(value, format, options)
275
+ end
188
276
  end
189
277
  end
190
278
 
@@ -194,7 +282,7 @@ module Lutaml
194
282
  return if value.nil? && !rule.render_nil
195
283
 
196
284
  attribute = instance.send(rule.delegate).class.attributes[name]
197
- hash[rule.from] = attribute.serialize(value, format)
285
+ hash[rule.from.to_s] = attribute.serialize(value, format)
198
286
  end
199
287
 
200
288
  def mappings_for(format)
@@ -315,6 +403,7 @@ module Lutaml
315
403
  end
316
404
 
317
405
  def apply_xml_mapping(doc, instance, options = {})
406
+ instance.encoding = options[:encoding]
318
407
  return instance unless doc
319
408
 
320
409
  if options[:default_namespace].nil?
@@ -347,6 +436,8 @@ module Lutaml
347
436
  mappings.each do |rule|
348
437
  raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
349
438
 
439
+ attr = attribute_for_rule(rule)
440
+
350
441
  value = if rule.raw_mapping?
351
442
  doc.node.inner_xml
352
443
  elsif rule.content_mapping?
@@ -355,10 +446,10 @@ module Lutaml
355
446
  doc.fetch(rule.namespaced_name(options[:default_namespace]))
356
447
  else
357
448
  defaults_used << rule.to
358
- rule.to_value_for(instance)
449
+ attr&.default || rule.to_value_for(instance)
359
450
  end
360
451
 
361
- value = normalize_xml_value(value, rule, options)
452
+ value = normalize_xml_value(value, rule, attr, options)
362
453
  rule.deserialize(instance, value, attributes, self)
363
454
  end
364
455
 
@@ -376,10 +467,10 @@ module Lutaml
376
467
 
377
468
  attr = attribute_for_rule(rule)
378
469
 
379
- value = if doc.key?(rule.name) || doc.key?(rule.name.to_sym)
380
- doc[rule.name] || doc[rule.name.to_sym]
470
+ value = if doc.key?(rule.name.to_s) || doc.key?(rule.name.to_sym)
471
+ doc[rule.name.to_s] || doc[rule.name.to_sym]
381
472
  else
382
- attr.default
473
+ attr&.default
383
474
  end
384
475
 
385
476
  if rule.custom_methods[:from]
@@ -399,9 +490,7 @@ module Lutaml
399
490
  instance
400
491
  end
401
492
 
402
- def normalize_xml_value(value, rule, options = {})
403
- attr = attribute_for_rule(rule)
404
-
493
+ def normalize_xml_value(value, rule, attr, options = {})
405
494
  value = [value].compact if attr&.collection? && !value.is_a?(Array)
406
495
 
407
496
  value = if value.is_a?(Array)
@@ -459,7 +548,7 @@ module Lutaml
459
548
  end
460
549
  end
461
550
 
462
- attr_accessor :element_order, :schema_location
551
+ attr_accessor :element_order, :schema_location, :encoding
463
552
  attr_writer :ordered, :mixed
464
553
 
465
554
  def initialize(attrs = {})
@@ -489,7 +578,9 @@ module Lutaml
489
578
  value = []
490
579
  end
491
580
 
492
- instance_variable_set(:"@#{name}", self.class.ensure_utf8(value))
581
+ default = using_default?(name)
582
+ public_send(:"#{name}=", self.class.ensure_utf8(value))
583
+ using_default_for(name) if default
493
584
  end
494
585
  end
495
586
 
@@ -538,6 +629,14 @@ module Lutaml
538
629
  hash[key.to_sym] || hash[key.to_s]
539
630
  end
540
631
 
632
+ def pretty_print_instance_variables
633
+ (instance_variables - %i[@using_default]).sort
634
+ end
635
+
636
+ def to_yaml_hash
637
+ self.class.as_yaml(self)
638
+ end
639
+
541
640
  Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
542
641
  define_method(:"to_#{format}") do |options = {}|
543
642
  adapter = Lutaml::Model::Config.public_send(:"#{format}_adapter")
@@ -548,6 +647,7 @@ module Lutaml
548
647
  options)
549
648
  end
550
649
 
650
+ options[:parse_encoding] = encoding if encoding
551
651
  adapter.new(representation).public_send(:"to_#{format}", options)
552
652
  end
553
653
  end
@@ -7,7 +7,7 @@ module Lutaml
7
7
  module TomlAdapter
8
8
  # Base class for TOML documents
9
9
  class TomlDocument < TomlObject
10
- def self.parse(toml)
10
+ def self.parse(toml, _options = {})
11
11
  raise NotImplementedError, "Subclasses must implement `parse`."
12
12
  end
13
13
 
@@ -5,7 +5,7 @@ module Lutaml
5
5
  module Model
6
6
  module TomlAdapter
7
7
  class TomlRbAdapter < TomlDocument
8
- def self.parse(toml)
8
+ def self.parse(toml, _options = {})
9
9
  data = TomlRB.parse(toml)
10
10
  new(data)
11
11
  end
@@ -5,7 +5,7 @@ module Lutaml
5
5
  module Model
6
6
  module TomlAdapter
7
7
  class TomlibAdapter < TomlDocument
8
- def self.parse(toml)
8
+ def self.parse(toml, _options = {})
9
9
  data = Tomlib.load(toml)
10
10
  new(data)
11
11
  end
@@ -34,10 +34,10 @@ module Lutaml
34
34
  # value&.iso8601
35
35
  # end
36
36
 
37
- # # YAML timestamp format (native)
38
- # def to_yaml
39
- # value
40
- # end
37
+ # YAML timestamp format (native)
38
+ def to_yaml
39
+ value&.iso8601
40
+ end
41
41
 
42
42
  # # TOML time format (HH:MM:SS.mmm)
43
43
  # def to_toml
@@ -39,7 +39,7 @@ module Lutaml
39
39
  end
40
40
 
41
41
  def blank?(value)
42
- value.respond_to?(:empty?) ? value.empty? : !value
42
+ value.respond_to?(:empty?) ? value.empty? : value.nil?
43
43
  end
44
44
 
45
45
  def add_method_if_not_defined(klass, method_name, &block)
@@ -4,9 +4,13 @@ module Lutaml
4
4
  def validate
5
5
  errors = []
6
6
  self.class.attributes.each do |name, attr|
7
- value = instance_variable_get(:"@#{name}")
7
+ value = public_send(:"#{name}")
8
8
  begin
9
- attr.validate_value!(value)
9
+ if value.respond_to?(:validate!)
10
+ value.validate!
11
+ else
12
+ attr.validate_value!(value)
13
+ end
10
14
  rescue Lutaml::Model::InvalidValueError,
11
15
  Lutaml::Model::CollectionCountOutOfRangeError,
12
16
  PatternNotMatchedError => e
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.3.29"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
@@ -51,6 +51,16 @@ module Lutaml
51
51
  end
52
52
  end
53
53
 
54
+ def add_xml_fragment(element, content)
55
+ if element.is_a?(self.class)
56
+ element = element.xml.parent
57
+ end
58
+
59
+ fragment = ::Nokogiri::XML::DocumentFragment.parse(content)
60
+
61
+ element.add_child(fragment)
62
+ end
63
+
54
64
  def add_text(element, text, cdata: false)
55
65
  return add_cdata(element, text) if cdata
56
66
 
@@ -66,10 +66,14 @@ module Lutaml
66
66
  xml.text(text)
67
67
  end
68
68
 
69
+ def add_xml_fragment(element, content)
70
+ element.raw(content)
71
+ end
72
+
69
73
  def add_text(element, text, cdata: false)
70
74
  return element.cdata(text) if cdata
71
75
 
72
- element << text
76
+ element.text(text)
73
77
  end
74
78
 
75
79
  def add_cdata(element, value)
@@ -6,10 +6,10 @@ module Lutaml
6
6
  module Model
7
7
  module XmlAdapter
8
8
  class NokogiriAdapter < XmlDocument
9
- def self.parse(xml)
10
- parsed = Nokogiri::XML(xml)
9
+ def self.parse(xml, options = {})
10
+ parsed = Nokogiri::XML(xml, nil, options[:encoding])
11
11
  root = NokogiriElement.new(parsed.root)
12
- new(root)
12
+ new(root, parsed.encoding)
13
13
  end
14
14
 
15
15
  def to_xml(options = {})
@@ -17,6 +17,8 @@ module Lutaml
17
17
 
18
18
  if options.key?(:encoding)
19
19
  builder_options[:encoding] = options[:encoding] unless options[:encoding].nil?
20
+ elsif options.key?(:parse_encoding)
21
+ builder_options[:encoding] = options[:parse_encoding]
20
22
  else
21
23
  builder_options[:encoding] = "UTF-8"
22
24
  end
@@ -5,7 +5,7 @@ module Lutaml
5
5
  module Model
6
6
  module XmlAdapter
7
7
  class OgaAdapter < XmlDocument
8
- def self.parse(xml)
8
+ def self.parse(xml, _options = {})
9
9
  parsed = Oga.parse_xml(xml)
10
10
  root = OgaElement.new(parsed)
11
11
  new(root)