lutaml-model 0.5.4 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +58 -21
  3. data/Gemfile +1 -0
  4. data/README.adoc +1112 -264
  5. data/lib/lutaml/model/attribute.rb +37 -15
  6. data/lib/lutaml/model/choice.rb +56 -0
  7. data/lib/lutaml/model/config.rb +1 -0
  8. data/lib/lutaml/model/error/choice_lower_bound_error.rb +9 -0
  9. data/lib/lutaml/model/error/choice_upper_bound_error.rb +9 -0
  10. data/lib/lutaml/model/error/import_model_with_root_error.rb +9 -0
  11. data/lib/lutaml/model/error/incorrect_sequence_error.rb +9 -0
  12. data/lib/lutaml/model/error/invalid_choice_range_error.rb +20 -0
  13. data/lib/lutaml/model/error/no_root_mapping_error.rb +9 -0
  14. data/lib/lutaml/model/error/no_root_namespace_error.rb +9 -0
  15. data/lib/lutaml/model/error/unknown_sequence_mapping_error.rb +9 -0
  16. data/lib/lutaml/model/error.rb +8 -0
  17. data/lib/lutaml/model/json_adapter/standard_json_adapter.rb +6 -1
  18. data/lib/lutaml/model/key_value_mapping.rb +3 -1
  19. data/lib/lutaml/model/key_value_mapping_rule.rb +4 -2
  20. data/lib/lutaml/model/liquefiable.rb +59 -0
  21. data/lib/lutaml/model/mapping_hash.rb +1 -1
  22. data/lib/lutaml/model/mapping_rule.rb +15 -2
  23. data/lib/lutaml/model/schema/xml_compiler.rb +68 -26
  24. data/lib/lutaml/model/schema_location.rb +7 -0
  25. data/lib/lutaml/model/sequence.rb +71 -0
  26. data/lib/lutaml/model/serialize.rb +126 -38
  27. data/lib/lutaml/model/type/decimal.rb +0 -4
  28. data/lib/lutaml/model/type/time.rb +3 -3
  29. data/lib/lutaml/model/utils.rb +19 -15
  30. data/lib/lutaml/model/validation.rb +12 -1
  31. data/lib/lutaml/model/version.rb +1 -1
  32. data/lib/lutaml/model/xml_adapter/builder/oga.rb +10 -7
  33. data/lib/lutaml/model/xml_adapter/builder/ox.rb +20 -13
  34. data/lib/lutaml/model/xml_adapter/element.rb +32 -0
  35. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +8 -8
  36. data/lib/lutaml/model/xml_adapter/oga/element.rb +14 -13
  37. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +86 -19
  38. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +19 -15
  39. data/lib/lutaml/model/xml_adapter/xml_document.rb +74 -13
  40. data/lib/lutaml/model/xml_adapter/xml_element.rb +57 -3
  41. data/lib/lutaml/model/xml_mapping.rb +49 -7
  42. data/lib/lutaml/model/xml_mapping_rule.rb +8 -3
  43. data/lib/lutaml/model.rb +1 -0
  44. data/lutaml-model.gemspec +5 -0
  45. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +75 -0
  46. data/spec/ceramic_spec.rb +39 -0
  47. data/spec/fixtures/ceramic.rb +23 -0
  48. data/spec/fixtures/xml/address_example_260.xsd +9 -0
  49. data/spec/fixtures/xml/user.xsd +10 -0
  50. data/spec/lutaml/model/cdata_spec.rb +4 -5
  51. data/spec/lutaml/model/choice_spec.rb +168 -0
  52. data/spec/lutaml/model/collection_spec.rb +1 -1
  53. data/spec/lutaml/model/custom_model_spec.rb +55 -8
  54. data/spec/lutaml/model/custom_serialization_spec.rb +74 -2
  55. data/spec/lutaml/model/defaults_spec.rb +3 -1
  56. data/spec/lutaml/model/delegation_spec.rb +7 -5
  57. data/spec/lutaml/model/enum_spec.rb +35 -0
  58. data/spec/lutaml/model/group_spec.rb +160 -0
  59. data/spec/lutaml/model/inheritance_spec.rb +25 -0
  60. data/spec/lutaml/model/liquefiable_spec.rb +121 -0
  61. data/spec/lutaml/model/mixed_content_spec.rb +80 -41
  62. data/spec/lutaml/model/multiple_mapping_spec.rb +22 -10
  63. data/spec/lutaml/model/schema/xml_compiler_spec.rb +218 -25
  64. data/spec/lutaml/model/sequence_spec.rb +216 -0
  65. data/spec/lutaml/model/transformation_spec.rb +230 -0
  66. data/spec/lutaml/model/type_spec.rb +138 -31
  67. data/spec/lutaml/model/utils_spec.rb +32 -0
  68. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +11 -7
  69. data/spec/lutaml/model/xml_mapping_rule_spec.rb +51 -0
  70. data/spec/lutaml/model/xml_mapping_spec.rb +167 -112
  71. metadata +67 -2
@@ -0,0 +1,71 @@
1
+ module Lutaml
2
+ module Model
3
+ class Sequence
4
+ attr_reader :attributes,
5
+ :model
6
+
7
+ def initialize(model)
8
+ @attributes = []
9
+ @model = model
10
+ end
11
+
12
+ def attribute(name, type, options = {})
13
+ options[:sequence] = self
14
+ @model.attribute(name, type, options)
15
+ end
16
+
17
+ def sequence(&block)
18
+ instance_eval(&block)
19
+ end
20
+
21
+ def map_element(
22
+ name,
23
+ to: nil,
24
+ render_nil: false,
25
+ render_default: false,
26
+ with: {},
27
+ delegate: nil,
28
+ cdata: false,
29
+ namespace: nil,
30
+ prefix: nil,
31
+ transform: {}
32
+ )
33
+ @attributes << @model.map_element(
34
+ name,
35
+ to: to,
36
+ render_nil: render_nil,
37
+ render_default: render_default,
38
+ with: with,
39
+ delegate: delegate,
40
+ cdata: cdata,
41
+ namespace: namespace,
42
+ prefix: prefix,
43
+ transform: transform,
44
+ )
45
+ end
46
+
47
+ def map_attribute(*)
48
+ raise Lutaml::Model::UnknownSequenceMappingError.new("map_attribute")
49
+ end
50
+
51
+ def map_content(*)
52
+ raise Lutaml::Model::UnknownSequenceMappingError.new("map_content")
53
+ end
54
+
55
+ def map_all(*)
56
+ raise Lutaml::Model::UnknownSequenceMappingError.new("map_all")
57
+ end
58
+
59
+ def validate_content!(element_order)
60
+ defined_order = @attributes.map { |rule| rule.name.to_s }
61
+ start_index = element_order.index(defined_order.first)
62
+
63
+ defined_order.each.with_index(start_index) do |element, i|
64
+ unless element_order[i] == element
65
+ raise Lutaml::Model::IncorrectSequenceError.new(element, element_order[i])
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -12,12 +12,16 @@ require_relative "comparable_model"
12
12
  require_relative "schema_location"
13
13
  require_relative "validation"
14
14
  require_relative "error"
15
+ require_relative "choice"
16
+ require_relative "sequence"
17
+ require_relative "liquefiable"
15
18
 
16
19
  module Lutaml
17
20
  module Model
18
21
  module Serialize
19
22
  include ComparableModel
20
23
  include Validation
24
+ include Lutaml::Model::Liquefiable
21
25
 
22
26
  def self.included(base)
23
27
  base.extend(ClassMethods)
@@ -25,7 +29,9 @@ module Lutaml
25
29
  end
26
30
 
27
31
  module ClassMethods
28
- attr_accessor :attributes, :mappings
32
+ include Lutaml::Model::Liquefiable::ClassMethods
33
+
34
+ attr_accessor :attributes, :mappings, :choice_attributes
29
35
 
30
36
  def inherited(subclass)
31
37
  super
@@ -40,6 +46,7 @@ module Lutaml
40
46
  def initialize_attrs(source_class)
41
47
  @mappings = Utils.deep_dup(source_class.instance_variable_get(:@mappings)) || {}
42
48
  @attributes = Utils.deep_dup(source_class.instance_variable_get(:@attributes)) || {}
49
+ @choice_attributes = Utils.deep_dup(source_class.instance_variable_get(:@choice_attributes)) || []
43
50
  instance_variable_set(:@model, self)
44
51
  end
45
52
 
@@ -81,6 +88,12 @@ module Lutaml
81
88
  value
82
89
  end
83
90
 
91
+ def choice(min: 1, max: 1, &block)
92
+ @choice_attributes << Choice.new(self, min, max).tap do |c|
93
+ c.instance_eval(&block)
94
+ end
95
+ end
96
+
84
97
  # Define an attribute for the model
85
98
  def attribute(name, type, options = {})
86
99
  attr = Attribute.new(name, type, options)
@@ -103,6 +116,33 @@ module Lutaml
103
116
  instance_variable_set(:"@#{name}", attr.cast_value(value))
104
117
  end
105
118
  end
119
+
120
+ register_drop_method(name)
121
+
122
+ attr
123
+ end
124
+
125
+ def root?
126
+ mappings_for(:xml).root?
127
+ end
128
+
129
+ def import_model_attributes(model)
130
+ raise Lutaml::Model::ImportModelWithRootError.new(model) if model.root?
131
+
132
+ @attributes.merge!(model.attributes)
133
+ end
134
+
135
+ def import_model_mappings(model)
136
+ raise Lutaml::Model::ImportModelWithRootError.new(model) if model.root?
137
+
138
+ @mappings.merge!(model.mappings)
139
+ end
140
+
141
+ def import_model(model)
142
+ raise Lutaml::Model::ImportModelWithRootError.new(model) if model.root?
143
+
144
+ import_model_attributes(model)
145
+ import_model_mappings(model)
106
146
  end
107
147
 
108
148
  def add_enum_methods_to_model(klass, enum_name, values, collection: false)
@@ -140,7 +180,7 @@ module Lutaml
140
180
  enum_vals.delete(value)
141
181
  enum_vals
142
182
  else
143
- []
183
+ instance_variable_get(:"@#{enum_name}") - [value]
144
184
  end
145
185
 
146
186
  instance_variable_set(:"@#{enum_name}", enum_vals)
@@ -190,7 +230,7 @@ module Lutaml
190
230
  mappings[format] ||= klass.new
191
231
  mappings[format].instance_eval(&block)
192
232
 
193
- if format == :xml && !mappings[format].root_element
233
+ if format == :xml && !mappings[format].root_element && !mappings[format].no_root?
194
234
  mappings[format].root(model.to_s)
195
235
  end
196
236
  end
@@ -208,9 +248,10 @@ module Lutaml
208
248
  end
209
249
 
210
250
  if format == :xml
211
- doc_hash = doc.parse_element(doc.root, self, :xml)
251
+ raise Lutaml::Model::NoRootMappingError.new(self) unless root?
252
+
212
253
  options[:encoding] = doc.encoding
213
- apply_mappings(doc_hash, format, options)
254
+ apply_mappings(doc, format, options)
214
255
  else
215
256
  apply_mappings(doc.to_h, format)
216
257
  end
@@ -276,6 +317,10 @@ module Lutaml
276
317
 
277
318
  attribute = attributes[name]
278
319
 
320
+ if export_method = rule.transform[:export] || attribute.transform_export_method
321
+ value = export_method.call(value)
322
+ end
323
+
279
324
  next hash.merge!(generate_hash_from_child_mappings(attribute, value, format, rule.root_mappings)) if rule.root_mapping?
280
325
 
281
326
  value = if rule.child_mappings
@@ -429,6 +474,7 @@ module Lutaml
429
474
  return instance if Utils.blank?(doc)
430
475
 
431
476
  options[:mappings] = mappings_for(format).mappings
477
+
432
478
  return apply_xml_mapping(doc, instance, options) if format == :xml
433
479
 
434
480
  apply_hash_mapping(doc, instance, format, options)
@@ -445,25 +491,29 @@ module Lutaml
445
491
  end
446
492
  mappings = options[:mappings] || mappings_for(:xml).mappings
447
493
 
448
- if doc.is_a?(Array)
449
- raise Lutaml::Model::CollectionTrueMissingError(self, option[:caller_class])
450
- end
494
+ raise Lutaml::Model::CollectionTrueMissingError(self, option[:caller_class]) if doc.is_a?(Array)
451
495
 
452
- if instance.respond_to?(:ordered=) && doc.is_a?(Lutaml::Model::MappingHash)
453
- instance.element_order = doc.item_order
496
+ doc_order = doc.root.order
497
+ if instance.respond_to?(:ordered=)
498
+ instance.element_order = doc_order
454
499
  instance.ordered = mappings_for(:xml).ordered? || options[:ordered]
455
500
  instance.mixed = mappings_for(:xml).mixed_content? || options[:mixed_content]
456
501
  end
457
502
 
458
- if doc["attributes"]&.key?("__schema_location")
503
+ schema_location = doc.attributes.values.find do |a|
504
+ a.unprefixed_name == "schemaLocation"
505
+ end
506
+
507
+ if !schema_location.nil?
459
508
  instance.schema_location = Lutaml::Model::SchemaLocation.new(
460
- schema_location: doc["attributes"]["__schema_location"][:schema_location],
461
- prefix: doc["attributes"]["__schema_location"][:prefix],
462
- namespace: doc["attributes"]["__schema_location"][:namespace],
509
+ schema_location: schema_location.value,
510
+ prefix: schema_location.namespace_prefix,
511
+ namespace: schema_location.namespace,
463
512
  )
464
513
  end
465
514
 
466
515
  defaults_used = []
516
+ validate_sequence!(doc_order)
467
517
 
468
518
  mappings.each do |rule|
469
519
  raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
@@ -471,12 +521,12 @@ module Lutaml
471
521
  attr = attribute_for_rule(rule)
472
522
 
473
523
  value = if rule.raw_mapping?
474
- doc.node.inner_xml
524
+ doc.root.inner_xml
475
525
  elsif rule.content_mapping?
476
- doc[rule.content_key]
477
- elsif val = value_for_rule(doc, rule, options)
526
+ rule.cdata ? doc.cdata : doc.text
527
+ elsif val = value_for_rule(doc, rule, options, instance)
478
528
  val
479
- else
529
+ elsif instance.using_default?(rule.to) || rule.render_default
480
530
  defaults_used << rule.to
481
531
  attr&.default || rule.to_value_for(instance)
482
532
  end
@@ -485,20 +535,48 @@ module Lutaml
485
535
  rule.deserialize(instance, value, attributes, self)
486
536
  end
487
537
 
488
- defaults_used.each do |attribute_name|
489
- instance.using_default_for(attribute_name)
490
- end
538
+ defaults_used.each { |attr_name| instance.using_default_for(attr_name) }
491
539
 
492
540
  instance
493
541
  end
494
542
 
495
- def value_for_rule(doc, rule, options)
543
+ def value_for_rule(doc, rule, options, instance)
496
544
  rule_names = rule.namespaced_names(options[:default_namespace])
497
- hash = rule.attribute? ? doc["attributes"] : doc["elements"]
498
- return unless hash
499
545
 
500
- value_key = rule_names.find { |name| hash.key_exist?(name) }
501
- hash.fetch(value_key) if value_key
546
+ if rule.attribute?
547
+ doc.root.find_attribute_value(rule_names)
548
+ else
549
+ attr = attribute_for_rule(rule)
550
+ children = doc.children.select do |child|
551
+ rule_names.include?(child.namespaced_name) && !child.text?
552
+ end
553
+
554
+ if rule.using_custom_methods? || attr.type == Lutaml::Model::Type::Hash
555
+ return children.first
556
+ end
557
+
558
+ if Utils.present?(children)
559
+ instance.value_set_for(attr.name)
560
+ end
561
+
562
+ if rule.cdata
563
+ values = children.map do |child|
564
+ child.cdata_children&.map(&:text)
565
+ end.flatten
566
+ return children.count > 1 ? values : values.first
567
+ end
568
+
569
+ values = children.map do |child|
570
+ if !rule.using_custom_methods? && attr.type <= Serialize
571
+ attr.cast(child, :xml, options.except(:mappings))
572
+ elsif attr.raw?
573
+ inner_xml_of(child)
574
+ else
575
+ child&.children&.first&.text
576
+ end
577
+ end
578
+ attr&.collection? ? values : values.first
579
+ end
502
580
  end
503
581
 
504
582
  def apply_hash_mapping(doc, instance, format, options = {})
@@ -546,18 +624,6 @@ module Lutaml
546
624
  def normalize_xml_value(value, rule, attr, options = {})
547
625
  value = [value].compact if attr&.collection? && !value.is_a?(Array)
548
626
 
549
- value = if value.is_a?(Array)
550
- value.map do |v|
551
- text_hash?(attr, v) ? v.text : v
552
- end
553
- elsif attr&.raw? && value
554
- value.node.children.map(&:to_xml).join
555
- elsif text_hash?(attr, value)
556
- value.text
557
- else
558
- value
559
- end
560
-
561
627
  return value unless cast_value?(attr, rule)
562
628
 
563
629
  options.merge(caller_class: self, mixed_content: rule.mixed_content)
@@ -599,6 +665,26 @@ module Lutaml
599
665
  value
600
666
  end
601
667
  end
668
+
669
+ def validate_sequence!(element_order)
670
+ mapping_sequence = mappings_for(:xml).element_sequence
671
+ current_order = element_order.filter_map(&:element_tag)
672
+
673
+ mapping_sequence.each do |mapping|
674
+ mapping.validate_content!(current_order)
675
+ end
676
+ end
677
+
678
+ private
679
+
680
+ def inner_xml_of(node)
681
+ case node
682
+ when XmlAdapter::XmlElement
683
+ node.inner_xml
684
+ else
685
+ node.children.map(&:to_xml).join
686
+ end
687
+ end
602
688
  end
603
689
 
604
690
  attr_accessor :element_order, :schema_location, :encoding
@@ -719,6 +805,8 @@ module Lutaml
719
805
  Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
720
806
  define_method(:"to_#{format}") do |options = {}|
721
807
  adapter = Lutaml::Model::Config.public_send(:"#{format}_adapter")
808
+ raise Lutaml::Model::NoRootMappingError.new(self.class) unless self.class.root?
809
+
722
810
  representation = if format == :xml
723
811
  self
724
812
  else
@@ -27,10 +27,6 @@ module Lutaml
27
27
  value.to_s("F") # Use fixed-point notation to match test expectations
28
28
  end
29
29
 
30
- def self.from_xml(value)
31
- cast(value.text)
32
- end
33
-
34
30
  def self.check_dependencies!(value)
35
31
  unless defined?(BigDecimal)
36
32
  raise TypeNotEnabledError.new("Decimal", value)
@@ -25,9 +25,9 @@ module Lutaml
25
25
  end
26
26
 
27
27
  # # xs:time format (HH:MM:SS.mmm±HH:MM)
28
- # def to_xml
29
- # value&.strftime("%H:%M:%S%:z")
30
- # end
28
+ def to_xml
29
+ value&.iso8601
30
+ end
31
31
 
32
32
  # # ISO8601 time format
33
33
  # def to_json
@@ -85,26 +85,30 @@ module Lutaml
85
85
  end
86
86
  end
87
87
 
88
- def deep_dup(hash)
89
- return hash if hash.nil?
90
-
91
- new_hash = {}
92
-
93
- hash.each do |key, value|
94
- new_hash[key] = if value.is_a?(Hash)
95
- deep_dup(value)
96
- elsif value.respond_to?(:deep_dup)
97
- value.deep_dup
98
- else
99
- value.dup
100
- end
101
- end
88
+ def deep_dup(object)
89
+ return object if object.nil?
102
90
 
103
- new_hash
91
+ case object
92
+ when Hash then deep_dup_hash(object)
93
+ when Array then deep_dup_array(object)
94
+ else deep_dup_object(object)
95
+ end
104
96
  end
105
97
 
106
98
  private
107
99
 
100
+ def deep_dup_hash(hash)
101
+ hash.transform_values { |value| deep_dup(value) }
102
+ end
103
+
104
+ def deep_dup_array(array)
105
+ array.map { |value| deep_dup(value) }
106
+ end
107
+
108
+ def deep_dup_object(object)
109
+ object.respond_to?(:deep_dup) ? object.deep_dup : object.dup
110
+ end
111
+
108
112
  def camelize_part(part)
109
113
  part.gsub(/(?:_|-|^)([a-z\d])/i) { $1.upcase }
110
114
  end
@@ -17,13 +17,24 @@ module Lutaml
17
17
  errors << e
18
18
  end
19
19
  end
20
- errors
20
+
21
+ validate_helper(errors)
21
22
  end
22
23
 
23
24
  def validate!
24
25
  errors = validate
25
26
  raise Lutaml::Model::ValidationError.new(errors) if errors.any?
26
27
  end
28
+
29
+ def validate_helper(errors)
30
+ self.class.choice_attributes.each do |attribute|
31
+ attribute.validate_content!(self)
32
+ end
33
+ errors
34
+ rescue Lutaml::Model::ChoiceUpperBoundError,
35
+ Lutaml::Model::ChoiceLowerBoundError => e
36
+ errors << e
37
+ end
27
38
  end
28
39
  end
29
40
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.5.4"
5
+ VERSION = "0.6.1"
6
6
  end
7
7
  end
@@ -6,19 +6,15 @@ module Lutaml
6
6
  module Builder
7
7
  class Oga
8
8
  def self.build(options = {}, &block)
9
- if block_given?
10
- XmlAdapter::Builder::Oga.new(options, &block)
11
- else
12
- XmlAdapter::Builder::Oga.new(options)
13
- end
9
+ new(options, &block)
14
10
  end
15
11
 
16
- attr_reader :document, :current_node, :options
12
+ attr_reader :document, :current_node, :encoding
17
13
 
18
14
  def initialize(options = {})
19
15
  @document = XmlAdapter::Oga::Document.new
20
16
  @current_node = @document
21
- @options = options
17
+ @encoding = options[:encoding]
22
18
  yield(self) if block_given?
23
19
  end
24
20
 
@@ -112,9 +108,14 @@ module Lutaml
112
108
  end
113
109
 
114
110
  def add_text(element, text, cdata: false)
111
+ text = text&.encode(encoding) if encoding && text.is_a?(String)
115
112
  return add_cdata(element, text) if cdata
116
113
 
117
114
  oga_text = ::Oga::XML::Text.new(text: text.to_s)
115
+ append_text_node(element, oga_text)
116
+ end
117
+
118
+ def append_text_node(element, oga_text)
118
119
  if element.is_a?(XmlAdapter::Oga::Document)
119
120
  children = element.children
120
121
  children.empty? ? children << oga_text : children.last.children << oga_text
@@ -166,6 +167,8 @@ module Lutaml
166
167
 
167
168
  def element_attributes(oga_element, attributes)
168
169
  oga_element.attributes = attributes.map do |name, value|
170
+ value = value.uri unless value.is_a?(String)
171
+
169
172
  ::Oga::XML::Attribute.new(
170
173
  name: name,
171
174
  value: value,
@@ -6,17 +6,18 @@ module Lutaml
6
6
  def self.build(options = {})
7
7
  if block_given?
8
8
  ::Ox::Builder.new(options) do |xml|
9
- yield(new(xml))
9
+ yield(new(xml, options))
10
10
  end
11
11
  else
12
- new(::Ox::Builder.new(options))
12
+ new(::Ox::Builder.new(options), options)
13
13
  end
14
14
  end
15
15
 
16
- attr_reader :xml
16
+ attr_reader :xml, :encoding
17
17
 
18
- def initialize(xml)
18
+ def initialize(xml, options = {})
19
19
  @xml = xml
20
+ @encoding = options[:encoding]
20
21
  @current_namespace = nil
21
22
  end
22
23
 
@@ -27,7 +28,7 @@ module Lutaml
27
28
 
28
29
  if block_given?
29
30
  xml.element(name, attributes) do |element|
30
- yield(self.class.new(element))
31
+ yield(self.class.new(element, { encoding: encoding }))
31
32
  end
32
33
  else
33
34
  xml.element(name, attributes)
@@ -44,17 +45,11 @@ module Lutaml
44
45
 
45
46
  def create_and_add_element(element_name, prefix: nil, attributes: {})
46
47
  element_name = element_name.first if element_name.is_a?(Array)
47
- prefixed_name = if prefix
48
- "#{prefix}:#{element_name}"
49
- elsif @current_namespace && !element_name.start_with?("#{@current_namespace}:")
50
- "#{@current_namespace}:#{element_name}"
51
- else
52
- element_name
53
- end
48
+ prefixed_name = set_prefixed_name(element_name, prefix)
54
49
 
55
50
  if block_given?
56
51
  xml.element(prefixed_name, attributes) do |element|
57
- yield(self.class.new(element))
52
+ yield(self.class.new(element, { encoding: encoding }))
58
53
  end
59
54
  else
60
55
  xml.element(prefixed_name, attributes)
@@ -63,6 +58,16 @@ module Lutaml
63
58
  @current_namespace = nil
64
59
  end
65
60
 
61
+ def set_prefixed_name(element_name, prefix)
62
+ if prefix
63
+ "#{prefix}:#{element_name}"
64
+ elsif @current_namespace && !element_name.start_with?("#{@current_namespace}:")
65
+ "#{@current_namespace}:#{element_name}"
66
+ else
67
+ element_name
68
+ end
69
+ end
70
+
66
71
  def <<(text)
67
72
  xml.text(text)
68
73
  end
@@ -72,6 +77,8 @@ module Lutaml
72
77
  end
73
78
 
74
79
  def add_text(element, text, cdata: false)
80
+ text = text&.encode(encoding) if encoding && text.is_a?(String)
81
+
75
82
  return element.cdata(text) if cdata
76
83
 
77
84
  element.text(text)
@@ -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)